git.delta.rocks / unique-network / refs/commits / 87988d2bdf5f

difftreelog

feat(rmrk) make resources lazy

Daniel Shiposha2022-06-09parent: #74dc066.patch.diff
in: master

2 files changed

modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
before · pallets/proxy-rmrk-core/src/lib.rs
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			)667			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;668669			Self::deposit_event(Event::NFTRejected {670				sender,671				collection_id: rmrk_collection_id,672				nft_id: rmrk_nft_id,673			});674675			Ok(())676		}677678		/// accept the addition of a new resource to an existing NFT679		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]680		#[transactional]681		pub fn accept_resource(682			origin: OriginFor<T>,683			rmrk_collection_id: RmrkCollectionId,684			rmrk_nft_id: RmrkNftId,685			rmrk_resource_id: RmrkResourceId,686		) -> DispatchResult {687			let sender = ensure_signed(origin)?;688			let cross_sender = T::CrossAccountId::from_sub(sender);689690			let collection_id = Self::unique_collection_id(rmrk_collection_id)691				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;692			let collection =693				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;694			collection.check_is_external()?;695696			let nft_id = rmrk_nft_id.into();697			let resource_id = rmrk_resource_id.into();698699			let budget = budget::Value::new(NESTING_BUDGET);700701			let nft_owner =702				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)703					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;704705			let resource_collection_id: CollectionId =706				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)707					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;708709			let is_pending: bool = Self::get_nft_property_decoded(710				resource_collection_id,711				resource_id,712				PendingResourceAccept,713			)714			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;715716			ensure!(is_pending, <Error<T>>::ResourceNotPending);717718			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);719720			<PalletNft<T>>::set_scoped_token_property(721				resource_collection_id,722				rmrk_resource_id.into(),723				PropertyScope::Rmrk,724				Self::rmrk_property(PendingResourceAccept, &false)?,725			)?;726727			Self::deposit_event(Event::<T>::ResourceAccepted {728				nft_id: rmrk_nft_id,729				resource_id: rmrk_resource_id,730			});731732			Ok(())733		}734735		/// accept the removal of a resource of an existing NFT736		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]737		#[transactional]738		pub fn accept_resource_removal(739			origin: OriginFor<T>,740			rmrk_collection_id: RmrkCollectionId,741			rmrk_nft_id: RmrkNftId,742			rmrk_resource_id: RmrkResourceId,743		) -> DispatchResult {744			let sender = ensure_signed(origin)?;745			let cross_sender = T::CrossAccountId::from_sub(sender);746747			let collection_id = Self::unique_collection_id(rmrk_collection_id)748				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;749			let collection =750				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;751			collection.check_is_external()?;752753			let nft_id = rmrk_nft_id.into();754			let resource_id = rmrk_resource_id.into();755756			let budget = budget::Value::new(NESTING_BUDGET);757758			let nft_owner =759				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)760					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;761762			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);763764			let resource_collection_id: CollectionId =765				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)766					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;767768			let is_pending: bool = Self::get_nft_property_decoded(769				resource_collection_id,770				resource_id,771				PendingResourceRemoval,772			)773			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;774775			ensure!(is_pending, <Error<T>>::ResourceNotPending);776777			let resource_collection = Self::get_typed_nft_collection(778				resource_collection_id,779				misc::CollectionType::Resource,780			)?;781782			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())783				.map_err(Self::map_unique_err_to_proxy)?;784785			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {786				nft_id: rmrk_nft_id,787				resource_id: rmrk_resource_id,788			});789790			Ok(())791		}792793		/// set a custom value on an NFT794		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]795		#[transactional]796		pub fn set_property(797			origin: OriginFor<T>,798			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,799			maybe_nft_id: Option<RmrkNftId>,800			key: RmrkKeyString,801			value: RmrkValueString,802		) -> DispatchResult {803			let sender = ensure_signed(origin)?;804			let sender = T::CrossAccountId::from_sub(sender);805806			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;807			let collection =808				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;809			collection.check_is_external()?;810811			let budget = budget::Value::new(NESTING_BUDGET);812813			match maybe_nft_id {814				Some(nft_id) => {815					let token_id: TokenId = nft_id.into();816817					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;818					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;819820					<PalletNft<T>>::set_scoped_token_property(821						collection_id,822						token_id,823						PropertyScope::Rmrk,824						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,825					)?;826				}827				None => {828					let collection = Self::get_typed_nft_collection(829						collection_id,830						misc::CollectionType::Regular,831					)?;832833					Self::check_collection_owner(&collection, &sender)?;834835					<PalletCommon<T>>::set_scoped_collection_property(836						collection_id,837						PropertyScope::Rmrk,838						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,839					)?;840				}841			}842843			Self::deposit_event(Event::PropertySet {844				collection_id: rmrk_collection_id,845				maybe_nft_id,846				key,847				value,848			});849850			Ok(())851		}852853		/// set a different order of resource priority854		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]855		#[transactional]856		pub fn set_priority(857			origin: OriginFor<T>,858			rmrk_collection_id: RmrkCollectionId,859			rmrk_nft_id: RmrkNftId,860			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,861		) -> DispatchResult {862			let sender = ensure_signed(origin)?;863			let sender = T::CrossAccountId::from_sub(sender);864865			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;866			let nft_id = rmrk_nft_id.into();867868			let collection =869				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;870			collection.check_is_external()?;871872			let budget = budget::Value::new(NESTING_BUDGET);873874			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;875			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;876877			<PalletNft<T>>::set_scoped_token_property(878				collection_id,879				nft_id,880				PropertyScope::Rmrk,881				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,882			)?;883884			Self::deposit_event(Event::<T>::PrioritySet {885				collection_id: rmrk_collection_id,886				nft_id: rmrk_nft_id,887			});888889			Ok(())890		}891892		/// Create basic resource893		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]894		#[transactional]895		pub fn add_basic_resource(896			origin: OriginFor<T>,897			rmrk_collection_id: RmrkCollectionId,898			nft_id: RmrkNftId,899			resource: RmrkBasicResource,900		) -> DispatchResult {901			let sender = ensure_signed(origin.clone())?;902903			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;904			let collection =905				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;906			collection.check_is_external()?;907908			let resource_id = Self::resource_add(909				sender,910				collection_id,911				nft_id.into(),912				[913					Self::rmrk_property(TokenType, &NftType::Resource)?,914					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,915					Self::rmrk_property(Src, &resource.src)?,916					Self::rmrk_property(Metadata, &resource.metadata)?,917					Self::rmrk_property(License, &resource.license)?,918					Self::rmrk_property(Thumb, &resource.thumb)?,919				]920				.into_iter(),921			)?;922923			Self::deposit_event(Event::ResourceAdded {924				nft_id,925				resource_id,926			});927			Ok(())928		}929930		/// Create composable resource931		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]932		#[transactional]933		pub fn add_composable_resource(934			origin: OriginFor<T>,935			rmrk_collection_id: RmrkCollectionId,936			nft_id: RmrkNftId,937			_resource_id: RmrkBoundedResource,938			resource: RmrkComposableResource,939		) -> DispatchResult {940			let sender = ensure_signed(origin.clone())?;941942			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;943			let collection =944				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;945			collection.check_is_external()?;946947			let resource_id = Self::resource_add(948				sender,949				collection_id,950				nft_id.into(),951				[952					Self::rmrk_property(TokenType, &NftType::Resource)?,953					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,954					Self::rmrk_property(Parts, &resource.parts)?,955					Self::rmrk_property(Base, &resource.base)?,956					Self::rmrk_property(Src, &resource.src)?,957					Self::rmrk_property(Metadata, &resource.metadata)?,958					Self::rmrk_property(License, &resource.license)?,959					Self::rmrk_property(Thumb, &resource.thumb)?,960				]961				.into_iter(),962			)?;963964			Self::deposit_event(Event::ResourceAdded {965				nft_id,966				resource_id,967			});968			Ok(())969		}970971		/// Create slot resource972		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]973		#[transactional]974		pub fn add_slot_resource(975			origin: OriginFor<T>,976			rmrk_collection_id: RmrkCollectionId,977			nft_id: RmrkNftId,978			resource: RmrkSlotResource,979		) -> DispatchResult {980			let sender = ensure_signed(origin.clone())?;981982			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;983			let collection =984				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;985			collection.check_is_external()?;986987			let resource_id = Self::resource_add(988				sender,989				collection_id,990				nft_id.into(),991				[992					Self::rmrk_property(TokenType, &NftType::Resource)?,993					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,994					Self::rmrk_property(Base, &resource.base)?,995					Self::rmrk_property(Src, &resource.src)?,996					Self::rmrk_property(Metadata, &resource.metadata)?,997					Self::rmrk_property(Slot, &resource.slot)?,998					Self::rmrk_property(License, &resource.license)?,999					Self::rmrk_property(Thumb, &resource.thumb)?,1000				]1001				.into_iter(),1002			)?;10031004			Self::deposit_event(Event::ResourceAdded {1005				nft_id,1006				resource_id,1007			});1008			Ok(())1009		}10101011		/// remove resource1012		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]1013		#[transactional]1014		pub fn remove_resource(1015			origin: OriginFor<T>,1016			rmrk_collection_id: RmrkCollectionId,1017			nft_id: RmrkNftId,1018			resource_id: RmrkResourceId,1019		) -> DispatchResult {1020			let sender = ensure_signed(origin.clone())?;10211022			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1023			let collection =1024				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1025			collection.check_is_external()?;10261027			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10281029			Self::deposit_event(Event::ResourceRemoval {1030				nft_id,1031				resource_id,1032			});1033			Ok(())1034		}1035	}1036}10371038impl<T: Config> Pallet<T> {1039	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1040		let key = rmrk_key.to_key::<T>()?;10411042		let scoped_key = PropertyScope::Rmrk1043			.apply(key)1044			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10451046		Ok(scoped_key)1047	}10481049	// todo think about renaming these1050	pub fn rmrk_property<E: Encode>(1051		rmrk_key: RmrkProperty,1052		value: &E,1053	) -> Result<Property, DispatchError> {1054		let key = rmrk_key.to_key::<T>()?;10551056		let value = value1057			.encode()1058			.try_into()1059			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10601061		let property = Property { key, value };10621063		Ok(property)1064	}10651066	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1067		vec.decode()1068			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1069	}10701071	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1072	where1073		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1074	{1075		vec.rebind()1076			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1077	}10781079	fn init_collection(1080		sender: T::CrossAccountId,1081		data: CreateCollectionData<T::AccountId>,1082		properties: impl Iterator<Item = Property>,1083	) -> Result<CollectionId, DispatchError> {1084		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10851086		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1087			return Err(<Error<T>>::NoAvailableCollectionId.into());1088		}10891090		<PalletCommon<T>>::set_scoped_collection_properties(1091			collection_id?,1092			PropertyScope::Rmrk,1093			properties,1094		)?;10951096		collection_id1097	}10981099	pub fn create_nft(1100		sender: &T::CrossAccountId,1101		owner: &T::CrossAccountId,1102		collection: &NonfungibleHandle<T>,1103		properties: impl Iterator<Item = Property>,1104	) -> Result<TokenId, DispatchError> {1105		let data = CreateNftExData {1106			properties: BoundedVec::default(),1107			owner: owner.clone(),1108		};11091110		let budget = budget::Value::new(NESTING_BUDGET);11111112		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;11131114		let nft_id = <PalletNft<T>>::current_token_id(collection.id);11151116		<PalletNft<T>>::set_scoped_token_properties(1117			collection.id,1118			nft_id,1119			PropertyScope::Rmrk,1120			properties,1121		)?;11221123		Ok(nft_id)1124	}11251126	fn destroy_nft(1127		sender: T::CrossAccountId,1128		collection_id: CollectionId,1129		token_id: TokenId,1130		max_burns: u32,1131		error_if_not_owned: Error<T>,1132	) -> DispatchResultWithPostInfo {1133		let collection =1134			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11351136		let token_data =1137			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11381139		let from = token_data.owner;11401141		let owner_check_budget = budget::Value::new(NESTING_BUDGET);11421143		ensure!(1144			<PalletStructure<T>>::check_indirectly_owned(1145				sender.clone(),1146				collection_id,1147				token_id,1148				None,1149				&owner_check_budget1150			)?,1151			error_if_not_owned,1152		);11531154		let burns_budget = budget::Value::new(max_burns);1155		let breadth_budget = budget::Value::new(max_burns);11561157		<PalletNft<T>>::burn_recursively(1158			&collection,1159			&from,1160			token_id,1161			&burns_budget,1162			&breadth_budget,1163		)1164	}11651166	fn resource_add(1167		sender: T::AccountId,1168		collection_id: CollectionId,1169		token_id: TokenId,1170		resource_properties: impl Iterator<Item = Property>,1171	) -> Result<RmrkResourceId, DispatchError> {1172		let collection =1173			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1174		ensure!(collection.owner == sender, Error::<T>::NoPermission);11751176		let sender = T::CrossAccountId::from_sub(sender);1177		let budget = budget::Value::new(NESTING_BUDGET);11781179		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1180			.map_err(Self::map_unique_err_to_proxy)?;11811182		let pending = sender != nft_owner;11831184		let resource_collection_id: CollectionId =1185			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1186		let resource_collection =1187			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;11881189		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them11901191		let resource_id = Self::create_nft(1192			&sender,1193			&nft_owner,1194			&resource_collection,1195			resource_properties.chain(1196				[1197					Self::rmrk_property(PendingResourceAccept, &pending)?,1198					Self::rmrk_property(PendingResourceRemoval, &false)?,1199				]1200				.into_iter(),1201			),1202		)1203		.map_err(|err| match err {1204			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1205			err => Self::map_unique_err_to_proxy(err),1206		})?;12071208		Ok(resource_id.0)1209	}12101211	fn resource_remove(1212		sender: T::AccountId,1213		collection_id: CollectionId,1214		nft_id: TokenId,1215		resource_id: TokenId,1216	) -> DispatchResult {1217		let collection =1218			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1219		ensure!(collection.owner == sender, Error::<T>::NoPermission);12201221		let resource_collection_id: CollectionId =1222			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1223		let resource_collection =1224			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1225		ensure!(1226			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1227			Error::<T>::ResourceDoesntExist1228		);12291230		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1231		let topmost_owner =1232			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12331234		let sender = T::CrossAccountId::from_sub(sender);1235		if topmost_owner == sender {1236			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1237				.map_err(Self::map_unique_err_to_proxy)?;1238		} else {1239			<PalletNft<T>>::set_scoped_token_property(1240				resource_collection_id,1241				resource_id,1242				PropertyScope::Rmrk,1243				Self::rmrk_property(PendingResourceRemoval, &true)?,1244			)?;1245		}12461247		Ok(())1248	}12491250	fn change_collection_owner(1251		collection_id: CollectionId,1252		collection_type: misc::CollectionType,1253		sender: T::AccountId,1254		new_owner: T::AccountId,1255	) -> DispatchResult {1256		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1257		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12581259		let mut collection = collection.into_inner();12601261		collection.owner = new_owner;1262		collection.save()1263	}12641265	fn check_collection_owner(1266		collection: &NonfungibleHandle<T>,1267		account: &T::CrossAccountId,1268	) -> DispatchResult {1269		collection1270			.check_is_owner(account)1271			.map_err(Self::map_unique_err_to_proxy)1272	}12731274	pub fn last_collection_idx() -> RmrkCollectionId {1275		<CollectionIndex<T>>::get()1276	}12771278	pub fn unique_collection_id(1279		rmrk_collection_id: RmrkCollectionId,1280	) -> Result<CollectionId, DispatchError> {1281		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1282			.map_err(|_| <Error<T>>::CollectionUnknown.into())1283	}12841285	pub fn rmrk_collection_id(1286		unique_collection_id: CollectionId,1287	) -> Result<RmrkCollectionId, DispatchError> {1288		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1289			.map_err(|_| <Error<T>>::CollectionUnknown.into())1290	}12911292	pub fn get_nft_collection(1293		collection_id: CollectionId,1294	) -> Result<NonfungibleHandle<T>, DispatchError> {1295		let collection = <CollectionHandle<T>>::try_get(collection_id)1296			.map_err(|_| <Error<T>>::CollectionUnknown)?;12971298		match collection.mode {1299			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1300			_ => Err(<Error<T>>::CollectionUnknown.into()),1301		}1302	}13031304	pub fn collection_exists(collection_id: CollectionId) -> bool {1305		<CollectionHandle<T>>::try_get(collection_id).is_ok()1306	}13071308	pub fn get_collection_property(1309		collection_id: CollectionId,1310		key: RmrkProperty,1311	) -> Result<PropertyValue, DispatchError> {1312		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1313			.get(&Self::rmrk_property_key(key)?)1314			.ok_or(<Error<T>>::CollectionUnknown)?1315			.clone();13161317		Ok(collection_property)1318	}13191320	pub fn get_collection_property_decoded<V: Decode>(1321		collection_id: CollectionId,1322		key: RmrkProperty,1323	) -> Result<V, DispatchError> {1324		Self::decode_property(Self::get_collection_property(collection_id, key)?)1325	}13261327	pub fn get_collection_type(1328		collection_id: CollectionId,1329	) -> Result<misc::CollectionType, DispatchError> {1330		Self::get_collection_property_decoded(collection_id, CollectionType)1331			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1332	}13331334	pub fn ensure_collection_type(1335		collection_id: CollectionId,1336		collection_type: misc::CollectionType,1337	) -> DispatchResult {1338		let actual_type = Self::get_collection_type(collection_id)?;1339		ensure!(1340			actual_type == collection_type,1341			<CommonError<T>>::NoPermission1342		);13431344		Ok(())1345	}13461347	pub fn get_typed_nft_collection(1348		collection_id: CollectionId,1349		collection_type: misc::CollectionType,1350	) -> Result<NonfungibleHandle<T>, DispatchError> {1351		Self::ensure_collection_type(collection_id, collection_type)?;13521353		Self::get_nft_collection(collection_id)1354	}13551356	pub fn get_typed_nft_collection_mapped(1357		rmrk_collection_id: RmrkCollectionId,1358		collection_type: misc::CollectionType,1359	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1360		let unique_collection_id = match collection_type {1361			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1362			_ => rmrk_collection_id.into(),1363		};13641365		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13661367		Ok((collection, unique_collection_id))1368	}13691370	pub fn get_nft_property(1371		collection_id: CollectionId,1372		nft_id: TokenId,1373		key: RmrkProperty,1374	) -> Result<PropertyValue, DispatchError> {1375		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1376			.get(&Self::rmrk_property_key(key)?)1377			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1378			.clone();13791380		Ok(nft_property)1381	}13821383	pub fn get_nft_property_decoded<V: Decode>(1384		collection_id: CollectionId,1385		nft_id: TokenId,1386		key: RmrkProperty,1387	) -> Result<V, DispatchError> {1388		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1389	}13901391	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1392		<TokenData<T>>::contains_key((collection_id, nft_id))1393	}13941395	pub fn get_nft_type(1396		collection_id: CollectionId,1397		token_id: TokenId,1398	) -> Result<NftType, DispatchError> {1399		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1400			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1401	}14021403	pub fn ensure_nft_type(1404		collection_id: CollectionId,1405		token_id: TokenId,1406		nft_type: NftType,1407	) -> DispatchResult {1408		let actual_type = Self::get_nft_type(collection_id, token_id)?;1409		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14101411		Ok(())1412	}14131414	pub fn ensure_nft_owner(1415		collection_id: CollectionId,1416		token_id: TokenId,1417		possible_owner: &T::CrossAccountId,1418		nesting_budget: &dyn budget::Budget,1419	) -> DispatchResult {1420		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1421			possible_owner.clone(),1422			collection_id,1423			token_id,1424			None,1425			nesting_budget,1426		)1427		.map_err(Self::map_unique_err_to_proxy)?;14281429		ensure!(is_owned, <Error<T>>::NoPermission);14301431		Ok(())1432	}14331434	pub fn filter_user_properties<Key, Value, R, Mapper>(1435		collection_id: CollectionId,1436		token_id: Option<TokenId>,1437		filter_keys: Option<Vec<RmrkPropertyKey>>,1438		mapper: Mapper,1439	) -> Result<Vec<R>, DispatchError>1440	where1441		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1442		Value: Decode + Default,1443		Mapper: Fn(Key, Value) -> R,1444	{1445		filter_keys1446			.map(|keys| {1447				let properties = keys1448					.into_iter()1449					.filter_map(|key| {1450						let key: Key = key.try_into().ok()?;14511452						let value = match token_id {1453							Some(token_id) => Self::get_nft_property_decoded(1454								collection_id,1455								token_id,1456								UserProperty(key.as_ref()),1457							),1458							None => Self::get_collection_property_decoded(1459								collection_id,1460								UserProperty(key.as_ref()),1461							),1462						}1463						.ok()?;14641465						Some(mapper(key, value))1466					})1467					.collect();14681469				Ok(properties)1470			})1471			.unwrap_or_else(|| {1472				let properties =1473					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();14741475				Ok(properties)1476			})1477	}14781479	pub fn iterate_user_properties<Key, Value, R, Mapper>(1480		collection_id: CollectionId,1481		token_id: Option<TokenId>,1482		mapper: Mapper,1483	) -> Result<impl Iterator<Item = R>, DispatchError>1484	where1485		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1486		Value: Decode + Default,1487		Mapper: Fn(Key, Value) -> R,1488	{1489		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;14901491		let properties = match token_id {1492			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1493			None => <PalletCommon<T>>::collection_properties(collection_id),1494		};14951496		let properties = properties.into_iter().filter_map(move |(key, value)| {1497			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;14981499			let key: Key = key.to_vec().try_into().ok()?;1500			let value: Value = value.decode().ok()?;15011502			Some(mapper(key, value))1503		});15041505		Ok(properties)1506	}15071508	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1509		map_unique_err_to_proxy! {1510			match err {1511				CommonError::NoPermission => NoPermission,1512				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1513				CommonError::PublicMintingNotAllowed => NoPermission,1514				CommonError::TokenNotFound => NoAvailableNftId,1515				CommonError::ApprovedValueTooLow => NoPermission,1516				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1517				StructureError::TokenNotFound => NoAvailableNftId,1518				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1519			}1520		}1521	}1522}
after · pallets/proxy-rmrk-core/src/lib.rs
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,161		UnableToDecodeRmrkData,162163		/* RMRK compatible events */164		CollectionNotEmpty,165		NoAvailableCollectionId,166		NoAvailableNftId,167		CollectionUnknown,168		NoPermission,169		NonTransferable,170		CollectionFullOrLocked,171		ResourceDoesntExist,172		CannotSendToDescendentOrSelf,173		CannotAcceptNonOwnedNft,174		CannotRejectNonOwnedNft,175		ResourceNotPending,176	}177178	#[pallet::call]179	impl<T: Config> Pallet<T> {180		/// Create a collection181		#[transactional]182		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]183		pub fn create_collection(184			origin: OriginFor<T>,185			metadata: RmrkString,186			max: Option<u32>,187			symbol: RmrkCollectionSymbol,188		) -> DispatchResult {189			let sender = ensure_signed(origin)?;190191			let limits = CollectionLimits {192				owner_can_transfer: Some(false),193				token_limit: max,194				..Default::default()195			};196197			let data = CreateCollectionData {198				limits: Some(limits),199				token_prefix: symbol200					.into_inner()201					.try_into()202					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,203				permissions: Some(CollectionPermissions {204					nesting: Some(NestingPermissions {205						token_owner: true,206						admin: false,207						restricted: None,208209						permissive: false,210					}),211					..Default::default()212				}),213				..Default::default()214			};215216			let unique_collection_id = Self::init_collection(217				T::CrossAccountId::from_sub(sender.clone()),218				data,219				[220					Self::rmrk_property(Metadata, &metadata)?,221					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,222				]223				.into_iter(),224			)?;225			let rmrk_collection_id = <CollectionIndex<T>>::get();226227			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);228			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);229230			<CollectionIndex<T>>::mutate(|n| *n += 1);231232			Self::deposit_event(Event::CollectionCreated {233				issuer: sender,234				collection_id: rmrk_collection_id,235			});236237			Ok(())238		}239240		/// destroy collection241		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]242		#[transactional]243		pub fn destroy_collection(244			origin: OriginFor<T>,245			collection_id: RmrkCollectionId,246		) -> DispatchResult {247			let sender = ensure_signed(origin)?;248			let cross_sender = T::CrossAccountId::from_sub(sender.clone());249250			let collection = Self::get_typed_nft_collection(251				Self::unique_collection_id(collection_id)?,252				misc::CollectionType::Regular,253			)?;254			collection.check_is_external()?;255256			<PalletNft<T>>::destroy_collection(collection, &cross_sender)257				.map_err(Self::map_unique_err_to_proxy)?;258259			Self::deposit_event(Event::CollectionDestroyed {260				issuer: sender,261				collection_id,262			});263264			Ok(())265		}266267		/// Change the issuer of a collection268		///269		/// Parameters:270		/// - `origin`: sender of the transaction271		/// - `collection_id`: collection id of the nft to change issuer of272		/// - `new_issuer`: Collection's new issuer273		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]274		#[transactional]275		pub fn change_collection_issuer(276			origin: OriginFor<T>,277			collection_id: RmrkCollectionId,278			new_issuer: <T::Lookup as StaticLookup>::Source,279		) -> DispatchResult {280			let sender = ensure_signed(origin)?;281282			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;283			collection.check_is_external()?;284285			let new_issuer = T::Lookup::lookup(new_issuer)?;286287			Self::change_collection_owner(288				Self::unique_collection_id(collection_id)?,289				misc::CollectionType::Regular,290				sender.clone(),291				new_issuer.clone(),292			)?;293294			Self::deposit_event(Event::IssuerChanged {295				old_issuer: sender,296				new_issuer,297				collection_id,298			});299300			Ok(())301		}302303		/// lock collection304		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]305		#[transactional]306		pub fn lock_collection(307			origin: OriginFor<T>,308			collection_id: RmrkCollectionId,309		) -> DispatchResult {310			let sender = ensure_signed(origin)?;311			let cross_sender = T::CrossAccountId::from_sub(sender.clone());312313			let collection = Self::get_typed_nft_collection(314				Self::unique_collection_id(collection_id)?,315				misc::CollectionType::Regular,316			)?;317			collection.check_is_external()?;318319			Self::check_collection_owner(&collection, &cross_sender)?;320321			let token_count = collection.total_supply();322323			let mut collection = collection.into_inner();324			collection.limits.token_limit = Some(token_count);325			collection.save()?;326327			Self::deposit_event(Event::CollectionLocked {328				issuer: sender,329				collection_id,330			});331332			Ok(())333		}334335		/// Mints an NFT in the specified collection336		/// Sets metadata and the royalty attribute337		///338		/// Parameters:339		/// - `collection_id`: The class of the asset to be minted.340		/// - `nft_id`: The nft value of the asset to be minted.341		/// - `recipient`: Receiver of the royalty342		/// - `royalty`: Permillage reward from each trade for the Recipient343		/// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash344		/// - `transferable`: Ability to transfer this NFT345		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]346		#[transactional]347		pub fn mint_nft(348			origin: OriginFor<T>,349			owner: T::AccountId,350			collection_id: RmrkCollectionId,351			recipient: Option<T::AccountId>,352			royalty_amount: Option<Permill>,353			metadata: RmrkString,354			transferable: bool,355		) -> DispatchResult {356			let sender = ensure_signed(origin)?;357			let sender = T::CrossAccountId::from_sub(sender);358			let cross_owner = T::CrossAccountId::from_sub(owner.clone());359360			let collection = Self::get_typed_nft_collection(361				Self::unique_collection_id(collection_id)?,362				misc::CollectionType::Regular,363			)?;364			collection.check_is_external()?;365366			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {367				recipient: recipient.unwrap_or_else(|| owner.clone()),368				amount,369			});370371			let nft_id = Self::create_nft(372				&sender,373				&cross_owner,374				&collection,375				[376					Self::rmrk_property(TokenType, &NftType::Regular)?,377					Self::rmrk_property(Transferable, &transferable)?,378					Self::rmrk_property(PendingNftAccept, &false)?,379					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,380					Self::rmrk_property(Metadata, &metadata)?,381					Self::rmrk_property(Equipped, &false)?,382					Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,383					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,384				]385				.into_iter(),386			)387			.map_err(|err| match err {388				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),389				err => Self::map_unique_err_to_proxy(err),390			})?;391392			Self::deposit_event(Event::NftMinted {393				owner,394				collection_id,395				nft_id: nft_id.0,396			});397398			Ok(())399		}400401		/// burn nft402		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]403		#[transactional]404		pub fn burn_nft(405			origin: OriginFor<T>,406			collection_id: RmrkCollectionId,407			nft_id: RmrkNftId,408			max_burns: u32,409		) -> DispatchResult {410			let sender = ensure_signed(origin)?;411			let cross_sender = T::CrossAccountId::from_sub(sender.clone());412413			let collection = Self::get_typed_nft_collection(414				Self::unique_collection_id(collection_id)?,415				misc::CollectionType::Regular,416			)?;417			collection.check_is_external()?;418419			Self::destroy_nft(420				cross_sender,421				Self::unique_collection_id(collection_id)?,422				nft_id.into(),423				max_burns,424				<Error<T>>::NoPermission,425			)426			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;427428			Self::deposit_event(Event::NFTBurned {429				owner: sender,430				nft_id,431			});432433			Ok(())434		}435436		/// Transfers a NFT from an Account or NFT A to another Account or NFT B437		///438		/// Parameters:439		/// - `origin`: sender of the transaction440		/// - `rmrk_collection_id`: collection id of the nft to be transferred441		/// - `rmrk_nft_id`: nft id of the nft to be transferred442		/// - `new_owner`: new owner of the nft which can be either an account or a NFT443		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]444		#[transactional]445		pub fn send(446			origin: OriginFor<T>,447			rmrk_collection_id: RmrkCollectionId,448			rmrk_nft_id: RmrkNftId,449			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,450		) -> DispatchResult {451			let sender = ensure_signed(origin.clone())?;452			let cross_sender = T::CrossAccountId::from_sub(sender.clone());453454			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;455			let nft_id = rmrk_nft_id.into();456457			let collection =458				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;459			collection.check_is_external()?;460461			let token_data =462				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;463464			let from = token_data.owner;465466			ensure!(467				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,468				<Error<T>>::NonTransferable469			);470471			ensure!(472				!Self::get_nft_property_decoded(473					collection_id,474					nft_id,475					RmrkProperty::PendingNftAccept476				)?,477				<Error<T>>::NoPermission478			);479480			let target_owner;481			let approval_required;482483			match new_owner {484				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {485					target_owner = T::CrossAccountId::from_sub(account_id.clone());486					approval_required = false;487				}488				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(489					target_collection_id,490					target_nft_id,491				) => {492					let target_collection_id = Self::unique_collection_id(target_collection_id)?;493494					let target_nft_budget = budget::Value::new(NESTING_BUDGET);495496					let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(497						target_collection_id,498						target_nft_id.into(),499						Some((collection_id, nft_id)),500						&target_nft_budget,501					)502					.map_err(Self::map_unique_err_to_proxy)?;503504					approval_required = cross_sender != target_nft_owner;505506					if approval_required {507						target_owner = target_nft_owner;508509						<PalletNft<T>>::set_scoped_token_property(510							collection.id,511							nft_id,512							PropertyScope::Rmrk,513							Self::rmrk_property(PendingNftAccept, &approval_required)?,514						)?;515					} else {516						target_owner = T::CrossTokenAddressMapping::token_to_address(517							target_collection_id,518							target_nft_id.into(),519						);520					}521				}522			}523524			let src_nft_budget = budget::Value::new(NESTING_BUDGET);525526			<PalletNft<T>>::transfer_from(527				&collection,528				&cross_sender,529				&from,530				&target_owner,531				nft_id,532				&src_nft_budget,533			)534			.map_err(Self::map_unique_err_to_proxy)?;535536			Self::deposit_event(Event::NFTSent {537				sender,538				recipient: new_owner,539				collection_id: rmrk_collection_id,540				nft_id: rmrk_nft_id,541				approval_required,542			});543544			Ok(())545		}546547		/// Accepts an NFT sent from another account to self or owned NFT548		///549		/// Parameters:550		/// - `origin`: sender of the transaction551		/// - `rmrk_collection_id`: collection id of the nft to be accepted552		/// - `rmrk_nft_id`: nft id of the nft to be accepted553		/// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was554		///   sent to555		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]556		#[transactional]557		pub fn accept_nft(558			origin: OriginFor<T>,559			rmrk_collection_id: RmrkCollectionId,560			rmrk_nft_id: RmrkNftId,561			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,562		) -> DispatchResult {563			let sender = ensure_signed(origin.clone())?;564			let cross_sender = T::CrossAccountId::from_sub(sender.clone());565566			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;567			let nft_id = rmrk_nft_id.into();568569			let collection =570				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;571			collection.check_is_external()?;572573			let new_cross_owner = match new_owner {574				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {575					T::CrossAccountId::from_sub(account_id.clone())576				}577				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(578					target_collection_id,579					target_nft_id,580				) => {581					let target_collection_id = Self::unique_collection_id(target_collection_id)?;582583					T::CrossTokenAddressMapping::token_to_address(584						target_collection_id,585						TokenId(target_nft_id),586					)587				}588			};589590			let budget = budget::Value::new(NESTING_BUDGET);591592			<PalletNft<T>>::transfer(593				&collection,594				&cross_sender,595				&new_cross_owner,596				nft_id,597				&budget,598			)599			.map_err(|err| {600				if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {601					<Error<T>>::CannotAcceptNonOwnedNft.into()602				} else {603					Self::map_unique_err_to_proxy(err)604				}605			})?;606607			<PalletNft<T>>::set_scoped_token_property(608				collection.id,609				nft_id,610				PropertyScope::Rmrk,611				Self::rmrk_property(PendingNftAccept, &false)?,612			)?;613614			Self::deposit_event(Event::NFTAccepted {615				sender,616				recipient: new_owner,617				collection_id: rmrk_collection_id,618				nft_id: rmrk_nft_id,619			});620621			Ok(())622		}623624		/// Rejects an NFT sent from another account to self or owned NFT625		///626		/// Parameters:627		/// - `origin`: sender of the transaction628		/// - `rmrk_collection_id`: collection id of the nft to be accepted629		/// - `rmrk_nft_id`: nft id of the nft to be accepted630		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]631		#[transactional]632		pub fn reject_nft(633			origin: OriginFor<T>,634			rmrk_collection_id: RmrkCollectionId,635			rmrk_nft_id: RmrkNftId,636			max_burns: u32,637		) -> DispatchResult {638			let sender = ensure_signed(origin)?;639			let cross_sender = T::CrossAccountId::from_sub(sender.clone());640641			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;642			let nft_id = rmrk_nft_id.into();643644			let collection =645				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;646			collection.check_is_external()?;647648			Self::destroy_nft(649				cross_sender,650				collection_id,651				nft_id,652				max_burns,653				<Error<T>>::CannotRejectNonOwnedNft,654			)655			.map_err(|err| Self::map_unique_err_to_proxy(err.error))?;656657			Self::deposit_event(Event::NFTRejected {658				sender,659				collection_id: rmrk_collection_id,660				nft_id: rmrk_nft_id,661			});662663			Ok(())664		}665666		/// accept the addition of a new resource to an existing NFT667		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]668		#[transactional]669		pub fn accept_resource(670			origin: OriginFor<T>,671			rmrk_collection_id: RmrkCollectionId,672			rmrk_nft_id: RmrkNftId,673			rmrk_resource_id: RmrkResourceId,674		) -> DispatchResult {675			let sender = ensure_signed(origin)?;676			let cross_sender = T::CrossAccountId::from_sub(sender);677678			let collection_id = Self::unique_collection_id(rmrk_collection_id)679				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;680			let collection =681				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;682			collection.check_is_external()?;683684			let nft_id = rmrk_nft_id.into();685			let resource_id = rmrk_resource_id.into();686687			let budget = budget::Value::new(NESTING_BUDGET);688689			let nft_owner =690				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)691					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;692693			let resource_collection_id: Option<CollectionId> =694				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)695					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;696				697			let resource_collection_id = resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;698699			let is_pending: bool = Self::get_nft_property_decoded(700				resource_collection_id,701				resource_id,702				PendingResourceAccept,703			)704			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;705706			ensure!(is_pending, <Error<T>>::ResourceNotPending);707708			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);709710			<PalletNft<T>>::set_scoped_token_property(711				resource_collection_id,712				rmrk_resource_id.into(),713				PropertyScope::Rmrk,714				Self::rmrk_property(PendingResourceAccept, &false)?,715			)?;716717			Self::deposit_event(Event::<T>::ResourceAccepted {718				nft_id: rmrk_nft_id,719				resource_id: rmrk_resource_id,720			});721722			Ok(())723		}724725		/// accept the removal of a resource of an existing NFT726		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]727		#[transactional]728		pub fn accept_resource_removal(729			origin: OriginFor<T>,730			rmrk_collection_id: RmrkCollectionId,731			rmrk_nft_id: RmrkNftId,732			rmrk_resource_id: RmrkResourceId,733		) -> DispatchResult {734			let sender = ensure_signed(origin)?;735			let cross_sender = T::CrossAccountId::from_sub(sender);736737			let collection_id = Self::unique_collection_id(rmrk_collection_id)738				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;739			let collection =740				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;741			collection.check_is_external()?;742743			let nft_id = rmrk_nft_id.into();744			let resource_id = rmrk_resource_id.into();745746			let budget = budget::Value::new(NESTING_BUDGET);747748			let nft_owner =749				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)750					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;751752			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);753754			let resource_collection_id: Option<CollectionId> =755				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)756					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;757758			let resource_collection_id = resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;759760			let is_pending: bool = Self::get_nft_property_decoded(761				resource_collection_id,762				resource_id,763				PendingResourceRemoval,764			)765			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;766767			ensure!(is_pending, <Error<T>>::ResourceNotPending);768769			let resource_collection = Self::get_typed_nft_collection(770				resource_collection_id,771				misc::CollectionType::Resource,772			)?;773774			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())775				.map_err(Self::map_unique_err_to_proxy)?;776777			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {778				nft_id: rmrk_nft_id,779				resource_id: rmrk_resource_id,780			});781782			Ok(())783		}784785		/// set a custom value on an NFT786		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]787		#[transactional]788		pub fn set_property(789			origin: OriginFor<T>,790			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,791			maybe_nft_id: Option<RmrkNftId>,792			key: RmrkKeyString,793			value: RmrkValueString,794		) -> DispatchResult {795			let sender = ensure_signed(origin)?;796			let sender = T::CrossAccountId::from_sub(sender);797798			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;799			let collection =800				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;801			collection.check_is_external()?;802803			let budget = budget::Value::new(NESTING_BUDGET);804805			match maybe_nft_id {806				Some(nft_id) => {807					let token_id: TokenId = nft_id.into();808809					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;810					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;811812					<PalletNft<T>>::set_scoped_token_property(813						collection_id,814						token_id,815						PropertyScope::Rmrk,816						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,817					)?;818				}819				None => {820					let collection = Self::get_typed_nft_collection(821						collection_id,822						misc::CollectionType::Regular,823					)?;824825					Self::check_collection_owner(&collection, &sender)?;826827					<PalletCommon<T>>::set_scoped_collection_property(828						collection_id,829						PropertyScope::Rmrk,830						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,831					)?;832				}833			}834835			Self::deposit_event(Event::PropertySet {836				collection_id: rmrk_collection_id,837				maybe_nft_id,838				key,839				value,840			});841842			Ok(())843		}844845		/// set a different order of resource priority846		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]847		#[transactional]848		pub fn set_priority(849			origin: OriginFor<T>,850			rmrk_collection_id: RmrkCollectionId,851			rmrk_nft_id: RmrkNftId,852			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,853		) -> DispatchResult {854			let sender = ensure_signed(origin)?;855			let sender = T::CrossAccountId::from_sub(sender);856857			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;858			let nft_id = rmrk_nft_id.into();859860			let collection =861				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;862			collection.check_is_external()?;863864			let budget = budget::Value::new(NESTING_BUDGET);865866			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;867			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;868869			<PalletNft<T>>::set_scoped_token_property(870				collection_id,871				nft_id,872				PropertyScope::Rmrk,873				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,874			)?;875876			Self::deposit_event(Event::<T>::PrioritySet {877				collection_id: rmrk_collection_id,878				nft_id: rmrk_nft_id,879			});880881			Ok(())882		}883884		/// Create basic resource885		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]886		#[transactional]887		pub fn add_basic_resource(888			origin: OriginFor<T>,889			rmrk_collection_id: RmrkCollectionId,890			nft_id: RmrkNftId,891			resource: RmrkBasicResource,892		) -> DispatchResult {893			let sender = ensure_signed(origin.clone())?;894895			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;896			let collection =897				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;898			collection.check_is_external()?;899900			let resource_id = Self::resource_add(901				sender,902				collection_id,903				nft_id.into(),904				[905					Self::rmrk_property(TokenType, &NftType::Resource)?,906					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,907					Self::rmrk_property(Src, &resource.src)?,908					Self::rmrk_property(Metadata, &resource.metadata)?,909					Self::rmrk_property(License, &resource.license)?,910					Self::rmrk_property(Thumb, &resource.thumb)?,911				]912				.into_iter(),913			)?;914915			Self::deposit_event(Event::ResourceAdded {916				nft_id,917				resource_id,918			});919			Ok(())920		}921922		/// Create composable resource923		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]924		#[transactional]925		pub fn add_composable_resource(926			origin: OriginFor<T>,927			rmrk_collection_id: RmrkCollectionId,928			nft_id: RmrkNftId,929			_resource_id: RmrkBoundedResource,930			resource: RmrkComposableResource,931		) -> DispatchResult {932			let sender = ensure_signed(origin.clone())?;933934			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;935			let collection =936				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;937			collection.check_is_external()?;938939			let resource_id = Self::resource_add(940				sender,941				collection_id,942				nft_id.into(),943				[944					Self::rmrk_property(TokenType, &NftType::Resource)?,945					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,946					Self::rmrk_property(Parts, &resource.parts)?,947					Self::rmrk_property(Base, &resource.base)?,948					Self::rmrk_property(Src, &resource.src)?,949					Self::rmrk_property(Metadata, &resource.metadata)?,950					Self::rmrk_property(License, &resource.license)?,951					Self::rmrk_property(Thumb, &resource.thumb)?,952				]953				.into_iter(),954			)?;955956			Self::deposit_event(Event::ResourceAdded {957				nft_id,958				resource_id,959			});960			Ok(())961		}962963		/// Create slot resource964		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]965		#[transactional]966		pub fn add_slot_resource(967			origin: OriginFor<T>,968			rmrk_collection_id: RmrkCollectionId,969			nft_id: RmrkNftId,970			resource: RmrkSlotResource,971		) -> DispatchResult {972			let sender = ensure_signed(origin.clone())?;973974			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;975			let collection =976				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;977			collection.check_is_external()?;978979			let resource_id = Self::resource_add(980				sender,981				collection_id,982				nft_id.into(),983				[984					Self::rmrk_property(TokenType, &NftType::Resource)?,985					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,986					Self::rmrk_property(Base, &resource.base)?,987					Self::rmrk_property(Src, &resource.src)?,988					Self::rmrk_property(Metadata, &resource.metadata)?,989					Self::rmrk_property(Slot, &resource.slot)?,990					Self::rmrk_property(License, &resource.license)?,991					Self::rmrk_property(Thumb, &resource.thumb)?,992				]993				.into_iter(),994			)?;995996			Self::deposit_event(Event::ResourceAdded {997				nft_id,998				resource_id,999			});1000			Ok(())1001		}10021003		/// remove resource1004		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]1005		#[transactional]1006		pub fn remove_resource(1007			origin: OriginFor<T>,1008			rmrk_collection_id: RmrkCollectionId,1009			nft_id: RmrkNftId,1010			resource_id: RmrkResourceId,1011		) -> DispatchResult {1012			let sender = ensure_signed(origin.clone())?;10131014			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1015			let collection =1016				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1017			collection.check_is_external()?;10181019			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10201021			Self::deposit_event(Event::ResourceRemoval {1022				nft_id,1023				resource_id,1024			});1025			Ok(())1026		}1027	}1028}10291030impl<T: Config> Pallet<T> {1031	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1032		let key = rmrk_key.to_key::<T>()?;10331034		let scoped_key = PropertyScope::Rmrk1035			.apply(key)1036			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10371038		Ok(scoped_key)1039	}10401041	// todo think about renaming these1042	pub fn rmrk_property<E: Encode>(1043		rmrk_key: RmrkProperty,1044		value: &E,1045	) -> Result<Property, DispatchError> {1046		let key = rmrk_key.to_key::<T>()?;10471048		let value = value1049			.encode()1050			.try_into()1051			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10521053		let property = Property { key, value };10541055		Ok(property)1056	}10571058	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1059		vec.decode()1060			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1061	}10621063	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1064	where1065		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1066	{1067		vec.rebind()1068			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1069	}10701071	fn init_collection(1072		sender: T::CrossAccountId,1073		data: CreateCollectionData<T::AccountId>,1074		properties: impl Iterator<Item = Property>,1075	) -> Result<CollectionId, DispatchError> {1076		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10771078		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1079			return Err(<Error<T>>::NoAvailableCollectionId.into());1080		}10811082		<PalletCommon<T>>::set_scoped_collection_properties(1083			collection_id?,1084			PropertyScope::Rmrk,1085			properties,1086		)?;10871088		collection_id1089	}10901091	pub fn create_nft(1092		sender: &T::CrossAccountId,1093		owner: &T::CrossAccountId,1094		collection: &NonfungibleHandle<T>,1095		properties: impl Iterator<Item = Property>,1096	) -> Result<TokenId, DispatchError> {1097		let data = CreateNftExData {1098			properties: BoundedVec::default(),1099			owner: owner.clone(),1100		};11011102		let budget = budget::Value::new(NESTING_BUDGET);11031104		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;11051106		let nft_id = <PalletNft<T>>::current_token_id(collection.id);11071108		<PalletNft<T>>::set_scoped_token_properties(1109			collection.id,1110			nft_id,1111			PropertyScope::Rmrk,1112			properties,1113		)?;11141115		Ok(nft_id)1116	}11171118	fn destroy_nft(1119		sender: T::CrossAccountId,1120		collection_id: CollectionId,1121		token_id: TokenId,1122		max_burns: u32,1123		error_if_not_owned: Error<T>,1124	) -> DispatchResultWithPostInfo {1125		let collection =1126			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11271128		let token_data =1129			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11301131		let from = token_data.owner;11321133		let owner_check_budget = budget::Value::new(NESTING_BUDGET);11341135		ensure!(1136			<PalletStructure<T>>::check_indirectly_owned(1137				sender.clone(),1138				collection_id,1139				token_id,1140				None,1141				&owner_check_budget1142			)?,1143			error_if_not_owned,1144		);11451146		let burns_budget = budget::Value::new(max_burns);1147		let breadth_budget = budget::Value::new(max_burns);11481149		<PalletNft<T>>::burn_recursively(1150			&collection,1151			&from,1152			token_id,1153			&burns_budget,1154			&breadth_budget,1155		)1156	}11571158	fn resource_add(1159		sender: T::AccountId,1160		collection_id: CollectionId,1161		token_id: TokenId,1162		resource_properties: impl Iterator<Item = Property>,1163	) -> Result<RmrkResourceId, DispatchError> {1164		let collection =1165			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1166		ensure!(collection.owner == sender, Error::<T>::NoPermission);11671168		let sender = T::CrossAccountId::from_sub(sender);1169		let budget = budget::Value::new(NESTING_BUDGET);11701171		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1172			.map_err(Self::map_unique_err_to_proxy)?;11731174		let pending = sender != nft_owner;11751176		let resource_collection_id: Option<CollectionId> =1177			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;11781179		let resource_collection_id = match resource_collection_id {1180			Some(id) => id,1181			None => {1182				let resource_collection_id = Self::init_collection(1183					sender.clone(),1184					CreateCollectionData {1185						..Default::default()1186					},1187					[1188						Self::rmrk_property(1189							CollectionType,1190							&misc::CollectionType::Resource,1191						)?1192					]1193					.into_iter(),1194				)?;11951196				<PalletNft<T>>::set_scoped_token_property(1197					collection_id,1198					token_id,1199					PropertyScope::Rmrk,1200					Self::rmrk_property(ResourceCollection, &Some(resource_collection_id))?1201				)?;12021203				resource_collection_id1204			}1205		};12061207		let resource_collection =1208			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;12091210		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them12111212		let resource_id = Self::create_nft(1213			&sender,1214			&nft_owner,1215			&resource_collection,1216			resource_properties.chain(1217				[1218					Self::rmrk_property(PendingResourceAccept, &pending)?,1219					Self::rmrk_property(PendingResourceRemoval, &false)?,1220				]1221				.into_iter(),1222			),1223		)1224		.map_err(|err| match err {1225			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1226			err => Self::map_unique_err_to_proxy(err),1227		})?;12281229		Ok(resource_id.0)1230	}12311232	fn resource_remove(1233		sender: T::AccountId,1234		collection_id: CollectionId,1235		nft_id: TokenId,1236		resource_id: TokenId,1237	) -> DispatchResult {1238		let collection =1239			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1240		ensure!(collection.owner == sender, Error::<T>::NoPermission);12411242		let resource_collection_id: Option<CollectionId> =1243			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;12441245		let resource_collection_id = resource_collection_id.ok_or(Error::<T>::ResourceDoesntExist)?;1246		1247		let resource_collection =1248			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1249		ensure!(1250			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1251			Error::<T>::ResourceDoesntExist1252		);12531254		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1255		let topmost_owner =1256			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12571258		let sender = T::CrossAccountId::from_sub(sender);1259		if topmost_owner == sender {1260			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1261				.map_err(Self::map_unique_err_to_proxy)?;1262		} else {1263			<PalletNft<T>>::set_scoped_token_property(1264				resource_collection_id,1265				resource_id,1266				PropertyScope::Rmrk,1267				Self::rmrk_property(PendingResourceRemoval, &true)?,1268			)?;1269		}12701271		Ok(())1272	}12731274	fn change_collection_owner(1275		collection_id: CollectionId,1276		collection_type: misc::CollectionType,1277		sender: T::AccountId,1278		new_owner: T::AccountId,1279	) -> DispatchResult {1280		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1281		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12821283		let mut collection = collection.into_inner();12841285		collection.owner = new_owner;1286		collection.save()1287	}12881289	fn check_collection_owner(1290		collection: &NonfungibleHandle<T>,1291		account: &T::CrossAccountId,1292	) -> DispatchResult {1293		collection1294			.check_is_owner(account)1295			.map_err(Self::map_unique_err_to_proxy)1296	}12971298	pub fn last_collection_idx() -> RmrkCollectionId {1299		<CollectionIndex<T>>::get()1300	}13011302	pub fn unique_collection_id(1303		rmrk_collection_id: RmrkCollectionId,1304	) -> Result<CollectionId, DispatchError> {1305		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1306			.map_err(|_| <Error<T>>::CollectionUnknown.into())1307	}13081309	pub fn rmrk_collection_id(1310		unique_collection_id: CollectionId,1311	) -> Result<RmrkCollectionId, DispatchError> {1312		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1313			.map_err(|_| <Error<T>>::CollectionUnknown.into())1314	}13151316	pub fn get_nft_collection(1317		collection_id: CollectionId,1318	) -> Result<NonfungibleHandle<T>, DispatchError> {1319		let collection = <CollectionHandle<T>>::try_get(collection_id)1320			.map_err(|_| <Error<T>>::CollectionUnknown)?;13211322		match collection.mode {1323			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1324			_ => Err(<Error<T>>::CollectionUnknown.into()),1325		}1326	}13271328	pub fn collection_exists(collection_id: CollectionId) -> bool {1329		<CollectionHandle<T>>::try_get(collection_id).is_ok()1330	}13311332	pub fn get_collection_property(1333		collection_id: CollectionId,1334		key: RmrkProperty,1335	) -> Result<PropertyValue, DispatchError> {1336		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1337			.get(&Self::rmrk_property_key(key)?)1338			.ok_or(<Error<T>>::CollectionUnknown)?1339			.clone();13401341		Ok(collection_property)1342	}13431344	pub fn get_collection_property_decoded<V: Decode>(1345		collection_id: CollectionId,1346		key: RmrkProperty,1347	) -> Result<V, DispatchError> {1348		Self::decode_property(Self::get_collection_property(collection_id, key)?)1349	}13501351	pub fn get_collection_type(1352		collection_id: CollectionId,1353	) -> Result<misc::CollectionType, DispatchError> {1354		Self::get_collection_property_decoded(collection_id, CollectionType)1355			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1356	}13571358	pub fn ensure_collection_type(1359		collection_id: CollectionId,1360		collection_type: misc::CollectionType,1361	) -> DispatchResult {1362		let actual_type = Self::get_collection_type(collection_id)?;1363		ensure!(1364			actual_type == collection_type,1365			<CommonError<T>>::NoPermission1366		);13671368		Ok(())1369	}13701371	pub fn get_typed_nft_collection(1372		collection_id: CollectionId,1373		collection_type: misc::CollectionType,1374	) -> Result<NonfungibleHandle<T>, DispatchError> {1375		Self::ensure_collection_type(collection_id, collection_type)?;13761377		Self::get_nft_collection(collection_id)1378	}13791380	pub fn get_typed_nft_collection_mapped(1381		rmrk_collection_id: RmrkCollectionId,1382		collection_type: misc::CollectionType,1383	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1384		let unique_collection_id = match collection_type {1385			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1386			_ => rmrk_collection_id.into(),1387		};13881389		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13901391		Ok((collection, unique_collection_id))1392	}13931394	pub fn get_nft_property(1395		collection_id: CollectionId,1396		nft_id: TokenId,1397		key: RmrkProperty,1398	) -> Result<PropertyValue, DispatchError> {1399		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1400			.get(&Self::rmrk_property_key(key)?)1401			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1402			.clone();14031404		Ok(nft_property)1405	}14061407	pub fn get_nft_property_decoded<V: Decode>(1408		collection_id: CollectionId,1409		nft_id: TokenId,1410		key: RmrkProperty,1411	) -> Result<V, DispatchError> {1412		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1413	}14141415	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1416		<TokenData<T>>::contains_key((collection_id, nft_id))1417	}14181419	pub fn get_nft_type(1420		collection_id: CollectionId,1421		token_id: TokenId,1422	) -> Result<NftType, DispatchError> {1423		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1424			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1425	}14261427	pub fn ensure_nft_type(1428		collection_id: CollectionId,1429		token_id: TokenId,1430		nft_type: NftType,1431	) -> DispatchResult {1432		let actual_type = Self::get_nft_type(collection_id, token_id)?;1433		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14341435		Ok(())1436	}14371438	pub fn ensure_nft_owner(1439		collection_id: CollectionId,1440		token_id: TokenId,1441		possible_owner: &T::CrossAccountId,1442		nesting_budget: &dyn budget::Budget,1443	) -> DispatchResult {1444		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1445			possible_owner.clone(),1446			collection_id,1447			token_id,1448			None,1449			nesting_budget,1450		)1451		.map_err(Self::map_unique_err_to_proxy)?;14521453		ensure!(is_owned, <Error<T>>::NoPermission);14541455		Ok(())1456	}14571458	pub fn filter_user_properties<Key, Value, R, Mapper>(1459		collection_id: CollectionId,1460		token_id: Option<TokenId>,1461		filter_keys: Option<Vec<RmrkPropertyKey>>,1462		mapper: Mapper,1463	) -> Result<Vec<R>, DispatchError>1464	where1465		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1466		Value: Decode + Default,1467		Mapper: Fn(Key, Value) -> R,1468	{1469		filter_keys1470			.map(|keys| {1471				let properties = keys1472					.into_iter()1473					.filter_map(|key| {1474						let key: Key = key.try_into().ok()?;14751476						let value = match token_id {1477							Some(token_id) => Self::get_nft_property_decoded(1478								collection_id,1479								token_id,1480								UserProperty(key.as_ref()),1481							),1482							None => Self::get_collection_property_decoded(1483								collection_id,1484								UserProperty(key.as_ref()),1485							),1486						}1487						.ok()?;14881489						Some(mapper(key, value))1490					})1491					.collect();14921493				Ok(properties)1494			})1495			.unwrap_or_else(|| {1496				let properties =1497					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();14981499				Ok(properties)1500			})1501	}15021503	pub fn iterate_user_properties<Key, Value, R, Mapper>(1504		collection_id: CollectionId,1505		token_id: Option<TokenId>,1506		mapper: Mapper,1507	) -> Result<impl Iterator<Item = R>, DispatchError>1508	where1509		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1510		Value: Decode + Default,1511		Mapper: Fn(Key, Value) -> R,1512	{1513		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15141515		let properties = match token_id {1516			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1517			None => <PalletCommon<T>>::collection_properties(collection_id),1518		};15191520		let properties = properties.into_iter().filter_map(move |(key, value)| {1521			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15221523			let key: Key = key.to_vec().try_into().ok()?;1524			let value: Value = value.decode().ok()?;15251526			Some(mapper(key, value))1527		});15281529		Ok(properties)1530	}15311532	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1533		map_unique_err_to_proxy! {1534			match err {1535				CommonError::NoPermission => NoPermission,1536				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1537				CommonError::PublicMintingNotAllowed => NoPermission,1538				CommonError::TokenNotFound => NoAvailableNftId,1539				CommonError::ApprovedValueTooLow => NoPermission,1540				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1541				StructureError::TokenNotFound => NoAvailableNftId,1542				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1543			}1544		}1545	}1546}
modifiedruntime/common/src/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -339,7 +339,13 @@
                     let nft_id = TokenId(nft_id);
                     if RmrkCore::ensure_nft_type(collection_id, nft_id, NftType::Regular).is_err() { return Ok(Vec::new()); }
 
-                    let res_collection_id: CollectionId = RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::ResourceCollection)?;
+                    let res_collection_id: Option<CollectionId> = RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::ResourceCollection)?;
+
+                    let res_collection_id = match res_collection_id {
+                        Some(id) => id,
+                        None => return Ok(Vec::new())
+                    };
+
                     let resource_collection = RmrkCore::get_typed_nft_collection(res_collection_id, CollectionType::Resource)?;
 
                     let resources = resource_collection