git.delta.rocks / unique-network / refs/commits / 4f0a4460dc55

difftreelog

cargo fmt

Daniel Shiposha2022-06-10parent: #5f8748c.patch.diff
in: master

2 files changed

modifiedpallets/proxy-rmrk-core/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/benchmarking.rs
+++ b/pallets/proxy-rmrk-core/src/benchmarking.rs
@@ -54,16 +54,27 @@
 }
 
 fn create_max_collection<T: Config>(owner: &T::AccountId) -> DispatchResult {
-	<T as pallet_common::Config>::Currency::deposit_creating(owner, T::CollectionCreationPrice::get());
+	<T as pallet_common::Config>::Currency::deposit_creating(
+		owner,
+		T::CollectionCreationPrice::get(),
+	);
 
 	let metadata = create_data();
 	let max = None;
 	let symbol = create_data();
 
-	<Pallet<T>>::create_collection(RawOrigin::Signed(owner.clone()).into(), metadata, max, symbol)
+	<Pallet<T>>::create_collection(
+		RawOrigin::Signed(owner.clone()).into(),
+		metadata,
+		max,
+		symbol,
+	)
 }
 
-fn create_max_nft<T: Config>(owner: &T::AccountId, collection_id: RmrkCollectionId) -> DispatchResult {
+fn create_max_nft<T: Config>(
+	owner: &T::AccountId,
+	collection_id: RmrkCollectionId,
+) -> DispatchResult {
 	let royalty_recipient = Some(owner.clone());
 	let royalty_amount = Some(Permill::from_percent(25));
 	let metadata = create_data();
@@ -76,20 +87,20 @@
 		royalty_recipient,
 		royalty_amount,
 		metadata,
-		transferable
+		transferable,
 	)
 }
 
 struct NftBuilder {
 	collection_id: RmrkCollectionId,
-	current_nft_id: RmrkNftId
+	current_nft_id: RmrkNftId,
 }
 
 impl NftBuilder {
 	fn new(collection_id: RmrkCollectionId) -> Self {
 		Self {
 			collection_id,
-			current_nft_id: 0
+			current_nft_id: 0,
 		}
 	}
 
@@ -100,19 +111,24 @@
 		Ok(self.current_nft_id)
 	}
 
-	fn build_tower<T: Config>(&mut self, owner: &T::AccountId, height: u32) -> Result<RmrkNftId, DispatchError> {
+	fn build_tower<T: Config>(
+		&mut self,
+		owner: &T::AccountId,
+		height: u32,
+	) -> Result<RmrkNftId, DispatchError> {
 		self.build::<T>(owner)?;
 
 		let mut prev_nft_id = self.current_nft_id;
 
 		for _ in 0..height {
 			self.build::<T>(owner)?;
-			
-			let new_owner = <RmrkAccountIdOrCollectionNftTuple<T::AccountId>>::CollectionAndNftTuple(
-				self.collection_id,
-				prev_nft_id
-			);
 
+			let new_owner =
+				<RmrkAccountIdOrCollectionNftTuple<T::AccountId>>::CollectionAndNftTuple(
+					self.collection_id,
+					prev_nft_id,
+				);
+
 			<Pallet<T>>::send(
 				RawOrigin::Signed(owner.clone()).into(),
 				self.collection_id,
@@ -140,7 +156,7 @@
 
 	destroy_collection {
 		let caller = account("caller", 0, SEED);
-		
+
 		create_max_collection::<T>(&caller)?;
 		let collection_id = 0;
 	}: _(RawOrigin::Signed(caller), collection_id)
@@ -169,7 +185,7 @@
 		create_max_collection::<T>(&caller)?;
 		let collection_id = 0;
 		let owner = caller.clone();
-		
+
 		let royalty_recipient = Some(caller.clone());
 		let royalty_amount = Some(Permill::from_percent(25));
 		let metadata = create_data();
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::*;4748pub const 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		#[transactional]242		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]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		#[transactional]274		#[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]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		#[transactional]305		#[pallet::weight(<SelfWeightOf<T>>::lock_collection())]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		#[transactional]346		#[pallet::weight(<SelfWeightOf<T>>::mint_nft())]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		#[transactional]444		#[pallet::weight(<SelfWeightOf<T>>::send())]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		#[transactional]556		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]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		#[transactional]668		#[pallet::weight(<SelfWeightOf<T>>::accept_resource())]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		#[transactional]727		#[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]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			let resource_data =775				<TokenData<T>>::get((resource_collection_id, resource_id)).ok_or(<Error<T>>::ResourceDoesntExist)?;776	777			let resource_owner = resource_data.owner;778779			<PalletNft<T>>::burn(&resource_collection, &resource_owner, rmrk_resource_id.into())780				.map_err(Self::map_unique_err_to_proxy)?;781782			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {783				nft_id: rmrk_nft_id,784				resource_id: rmrk_resource_id,785			});786787			Ok(())788		}789790		/// set a custom value on an NFT791		#[transactional]792		#[pallet::weight(<SelfWeightOf<T>>::set_property())]793		pub fn set_property(794			origin: OriginFor<T>,795			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,796			maybe_nft_id: Option<RmrkNftId>,797			key: RmrkKeyString,798			value: RmrkValueString,799		) -> DispatchResult {800			let sender = ensure_signed(origin)?;801			let sender = T::CrossAccountId::from_sub(sender);802803			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;804			let collection =805				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;806			collection.check_is_external()?;807808			let budget = budget::Value::new(NESTING_BUDGET);809810			match maybe_nft_id {811				Some(nft_id) => {812					let token_id: TokenId = nft_id.into();813814					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;815					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;816817					<PalletNft<T>>::set_scoped_token_property(818						collection_id,819						token_id,820						PropertyScope::Rmrk,821						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,822					)?;823				}824				None => {825					let collection = Self::get_typed_nft_collection(826						collection_id,827						misc::CollectionType::Regular,828					)?;829830					Self::check_collection_owner(&collection, &sender)?;831832					<PalletCommon<T>>::set_scoped_collection_property(833						collection_id,834						PropertyScope::Rmrk,835						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,836					)?;837				}838			}839840			Self::deposit_event(Event::PropertySet {841				collection_id: rmrk_collection_id,842				maybe_nft_id,843				key,844				value,845			});846847			Ok(())848		}849850		/// set a different order of resource priority851		#[transactional]852		#[pallet::weight(<SelfWeightOf<T>>::set_priority())]853		pub fn set_priority(854			origin: OriginFor<T>,855			rmrk_collection_id: RmrkCollectionId,856			rmrk_nft_id: RmrkNftId,857			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,858		) -> DispatchResult {859			let sender = ensure_signed(origin)?;860			let sender = T::CrossAccountId::from_sub(sender);861862			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;863			let nft_id = rmrk_nft_id.into();864865			let collection =866				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;867			collection.check_is_external()?;868869			let budget = budget::Value::new(NESTING_BUDGET);870871			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;872			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;873874			<PalletNft<T>>::set_scoped_token_property(875				collection_id,876				nft_id,877				PropertyScope::Rmrk,878				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,879			)?;880881			Self::deposit_event(Event::<T>::PrioritySet {882				collection_id: rmrk_collection_id,883				nft_id: rmrk_nft_id,884			});885886			Ok(())887		}888889		/// Create basic resource890		#[transactional]891		#[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]892		pub fn add_basic_resource(893			origin: OriginFor<T>,894			rmrk_collection_id: RmrkCollectionId,895			nft_id: RmrkNftId,896			resource: RmrkBasicResource,897		) -> DispatchResult {898			let sender = ensure_signed(origin.clone())?;899900			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;901			let collection =902				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;903			collection.check_is_external()?;904905			let resource_id = Self::resource_add(906				sender,907				collection_id,908				nft_id.into(),909				[910					Self::rmrk_property(TokenType, &NftType::Resource)?,911					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,912					Self::rmrk_property(Src, &resource.src)?,913					Self::rmrk_property(Metadata, &resource.metadata)?,914					Self::rmrk_property(License, &resource.license)?,915					Self::rmrk_property(Thumb, &resource.thumb)?,916				]917				.into_iter(),918			)?;919920			Self::deposit_event(Event::ResourceAdded {921				nft_id,922				resource_id,923			});924			Ok(())925		}926927		/// Create composable resource928		#[transactional]929		#[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]930		pub fn add_composable_resource(931			origin: OriginFor<T>,932			rmrk_collection_id: RmrkCollectionId,933			nft_id: RmrkNftId,934			resource: RmrkComposableResource,935		) -> DispatchResult {936			let sender = ensure_signed(origin.clone())?;937938			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;939			let collection =940				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;941			collection.check_is_external()?;942943			let resource_id = Self::resource_add(944				sender,945				collection_id,946				nft_id.into(),947				[948					Self::rmrk_property(TokenType, &NftType::Resource)?,949					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,950					Self::rmrk_property(Parts, &resource.parts)?,951					Self::rmrk_property(Base, &resource.base)?,952					Self::rmrk_property(Src, &resource.src)?,953					Self::rmrk_property(Metadata, &resource.metadata)?,954					Self::rmrk_property(License, &resource.license)?,955					Self::rmrk_property(Thumb, &resource.thumb)?,956				]957				.into_iter(),958			)?;959960			Self::deposit_event(Event::ResourceAdded {961				nft_id,962				resource_id,963			});964			Ok(())965		}966967		/// Create slot resource968		#[transactional]969		#[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]970		pub fn add_slot_resource(971			origin: OriginFor<T>,972			rmrk_collection_id: RmrkCollectionId,973			nft_id: RmrkNftId,974			resource: RmrkSlotResource,975		) -> DispatchResult {976			let sender = ensure_signed(origin.clone())?;977978			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;979			let collection =980				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;981			collection.check_is_external()?;982983			let resource_id = Self::resource_add(984				sender,985				collection_id,986				nft_id.into(),987				[988					Self::rmrk_property(TokenType, &NftType::Resource)?,989					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,990					Self::rmrk_property(Base, &resource.base)?,991					Self::rmrk_property(Src, &resource.src)?,992					Self::rmrk_property(Metadata, &resource.metadata)?,993					Self::rmrk_property(Slot, &resource.slot)?,994					Self::rmrk_property(License, &resource.license)?,995					Self::rmrk_property(Thumb, &resource.thumb)?,996				]997				.into_iter(),998			)?;9991000			Self::deposit_event(Event::ResourceAdded {1001				nft_id,1002				resource_id,1003			});1004			Ok(())1005		}10061007		/// remove resource1008		#[transactional]1009		#[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1010		pub fn remove_resource(1011			origin: OriginFor<T>,1012			rmrk_collection_id: RmrkCollectionId,1013			nft_id: RmrkNftId,1014			resource_id: RmrkResourceId,1015		) -> DispatchResult {1016			let sender = ensure_signed(origin.clone())?;10171018			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1019			let collection =1020				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1021			collection.check_is_external()?;10221023			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10241025			Self::deposit_event(Event::ResourceRemoval {1026				nft_id,1027				resource_id,1028			});1029			Ok(())1030		}1031	}1032}10331034impl<T: Config> Pallet<T> {1035	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1036		let key = rmrk_key.to_key::<T>()?;10371038		let scoped_key = PropertyScope::Rmrk1039			.apply(key)1040			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10411042		Ok(scoped_key)1043	}10441045	// todo think about renaming these1046	pub fn rmrk_property<E: Encode>(1047		rmrk_key: RmrkProperty,1048		value: &E,1049	) -> Result<Property, DispatchError> {1050		let key = rmrk_key.to_key::<T>()?;10511052		let value = value1053			.encode()1054			.try_into()1055			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10561057		let property = Property { key, value };10581059		Ok(property)1060	}10611062	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1063		vec.decode()1064			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1065	}10661067	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1068	where1069		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1070	{1071		vec.rebind()1072			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1073	}10741075	fn init_collection(1076		sender: T::CrossAccountId,1077		data: CreateCollectionData<T::AccountId>,1078		properties: impl Iterator<Item = Property>,1079	) -> Result<CollectionId, DispatchError> {1080		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10811082		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1083			return Err(<Error<T>>::NoAvailableCollectionId.into());1084		}10851086		<PalletCommon<T>>::set_scoped_collection_properties(1087			collection_id?,1088			PropertyScope::Rmrk,1089			properties,1090		)?;10911092		collection_id1093	}10941095	pub fn create_nft(1096		sender: &T::CrossAccountId,1097		owner: &T::CrossAccountId,1098		collection: &NonfungibleHandle<T>,1099		properties: impl Iterator<Item = Property>,1100	) -> Result<TokenId, DispatchError> {1101		let data = CreateNftExData {1102			properties: BoundedVec::default(),1103			owner: owner.clone(),1104		};11051106		let budget = budget::Value::new(NESTING_BUDGET);11071108		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;11091110		let nft_id = <PalletNft<T>>::current_token_id(collection.id);11111112		<PalletNft<T>>::set_scoped_token_properties(1113			collection.id,1114			nft_id,1115			PropertyScope::Rmrk,1116			properties,1117		)?;11181119		Ok(nft_id)1120	}11211122	fn destroy_nft(1123		sender: T::CrossAccountId,1124		collection_id: CollectionId,1125		token_id: TokenId,1126		max_burns: u32,1127		error_if_not_owned: Error<T>,1128	) -> DispatchResultWithPostInfo {1129		let collection =1130			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11311132		let token_data =1133			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11341135		let from = token_data.owner;11361137		let owner_check_budget = budget::Value::new(NESTING_BUDGET);11381139		ensure!(1140			<PalletStructure<T>>::check_indirectly_owned(1141				sender.clone(),1142				collection_id,1143				token_id,1144				None,1145				&owner_check_budget1146			)?,1147			error_if_not_owned,1148		);11491150		let burns_budget = budget::Value::new(max_burns);1151		let breadth_budget = budget::Value::new(max_burns);11521153		<PalletNft<T>>::burn_recursively(1154			&collection,1155			&from,1156			token_id,1157			&burns_budget,1158			&breadth_budget,1159		)1160	}11611162	fn resource_add(1163		sender: T::AccountId,1164		collection_id: CollectionId,1165		token_id: TokenId,1166		resource_properties: impl Iterator<Item = Property>,1167	) -> Result<RmrkResourceId, DispatchError> {1168		let collection =1169			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1170		ensure!(collection.owner == sender, Error::<T>::NoPermission);11711172		let sender = T::CrossAccountId::from_sub(sender);1173		let budget = budget::Value::new(NESTING_BUDGET);11741175		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1176			.map_err(Self::map_unique_err_to_proxy)?;11771178		let pending = sender != nft_owner;11791180		let resource_collection_id: Option<CollectionId> =1181			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;11821183		let resource_collection_id = match resource_collection_id {1184			Some(id) => id,1185			None => {1186				let resource_collection_id = Self::init_collection(1187					sender.clone(),1188					CreateCollectionData {1189						..Default::default()1190					},1191					[1192						Self::rmrk_property(1193							CollectionType,1194							&misc::CollectionType::Resource,1195						)?1196					]1197					.into_iter(),1198				)?;11991200				<PalletNft<T>>::set_scoped_token_property(1201					collection_id,1202					token_id,1203					PropertyScope::Rmrk,1204					Self::rmrk_property(ResourceCollection, &Some(resource_collection_id))?1205				)?;12061207				resource_collection_id1208			}1209		};12101211		let resource_collection =1212			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;12131214		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them12151216		let resource_id = Self::create_nft(1217			&sender,1218			&nft_owner,1219			&resource_collection,1220			resource_properties.chain(1221				[1222					Self::rmrk_property(PendingResourceAccept, &pending)?,1223					Self::rmrk_property(PendingResourceRemoval, &false)?,1224				]1225				.into_iter(),1226			),1227		)1228		.map_err(|err| match err {1229			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1230			err => Self::map_unique_err_to_proxy(err),1231		})?;12321233		Ok(resource_id.0)1234	}12351236	fn resource_remove(1237		sender: T::AccountId,1238		collection_id: CollectionId,1239		nft_id: TokenId,1240		resource_id: TokenId,1241	) -> DispatchResult {1242		let collection =1243			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1244		ensure!(collection.owner == sender, Error::<T>::NoPermission);12451246		let resource_collection_id: Option<CollectionId> =1247			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;12481249		let resource_collection_id = resource_collection_id.ok_or(Error::<T>::ResourceDoesntExist)?;1250		1251		let resource_collection =1252			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1253		ensure!(1254			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1255			Error::<T>::ResourceDoesntExist1256		);12571258		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1259		let topmost_owner =1260			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12611262		let sender = T::CrossAccountId::from_sub(sender);1263		if topmost_owner == sender {1264			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1265				.map_err(Self::map_unique_err_to_proxy)?;1266		} else {1267			<PalletNft<T>>::set_scoped_token_property(1268				resource_collection_id,1269				resource_id,1270				PropertyScope::Rmrk,1271				Self::rmrk_property(PendingResourceRemoval, &true)?,1272			)?;1273		}12741275		Ok(())1276	}12771278	fn change_collection_owner(1279		collection_id: CollectionId,1280		collection_type: misc::CollectionType,1281		sender: T::AccountId,1282		new_owner: T::AccountId,1283	) -> DispatchResult {1284		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1285		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12861287		let mut collection = collection.into_inner();12881289		collection.owner = new_owner;1290		collection.save()1291	}12921293	fn check_collection_owner(1294		collection: &NonfungibleHandle<T>,1295		account: &T::CrossAccountId,1296	) -> DispatchResult {1297		collection1298			.check_is_owner(account)1299			.map_err(Self::map_unique_err_to_proxy)1300	}13011302	pub fn last_collection_idx() -> RmrkCollectionId {1303		<CollectionIndex<T>>::get()1304	}13051306	pub fn unique_collection_id(1307		rmrk_collection_id: RmrkCollectionId,1308	) -> Result<CollectionId, DispatchError> {1309		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1310			.map_err(|_| <Error<T>>::CollectionUnknown.into())1311	}13121313	pub fn rmrk_collection_id(1314		unique_collection_id: CollectionId,1315	) -> Result<RmrkCollectionId, DispatchError> {1316		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1317			.map_err(|_| <Error<T>>::CollectionUnknown.into())1318	}13191320	pub fn get_nft_collection(1321		collection_id: CollectionId,1322	) -> Result<NonfungibleHandle<T>, DispatchError> {1323		let collection = <CollectionHandle<T>>::try_get(collection_id)1324			.map_err(|_| <Error<T>>::CollectionUnknown)?;13251326		match collection.mode {1327			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1328			_ => Err(<Error<T>>::CollectionUnknown.into()),1329		}1330	}13311332	pub fn collection_exists(collection_id: CollectionId) -> bool {1333		<CollectionHandle<T>>::try_get(collection_id).is_ok()1334	}13351336	pub fn get_collection_property(1337		collection_id: CollectionId,1338		key: RmrkProperty,1339	) -> Result<PropertyValue, DispatchError> {1340		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1341			.get(&Self::rmrk_property_key(key)?)1342			.ok_or(<Error<T>>::CollectionUnknown)?1343			.clone();13441345		Ok(collection_property)1346	}13471348	pub fn get_collection_property_decoded<V: Decode>(1349		collection_id: CollectionId,1350		key: RmrkProperty,1351	) -> Result<V, DispatchError> {1352		Self::decode_property(Self::get_collection_property(collection_id, key)?)1353	}13541355	pub fn get_collection_type(1356		collection_id: CollectionId,1357	) -> Result<misc::CollectionType, DispatchError> {1358		Self::get_collection_property_decoded(collection_id, CollectionType)1359			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1360	}13611362	pub fn ensure_collection_type(1363		collection_id: CollectionId,1364		collection_type: misc::CollectionType,1365	) -> DispatchResult {1366		let actual_type = Self::get_collection_type(collection_id)?;1367		ensure!(1368			actual_type == collection_type,1369			<CommonError<T>>::NoPermission1370		);13711372		Ok(())1373	}13741375	pub fn get_typed_nft_collection(1376		collection_id: CollectionId,1377		collection_type: misc::CollectionType,1378	) -> Result<NonfungibleHandle<T>, DispatchError> {1379		Self::ensure_collection_type(collection_id, collection_type)?;13801381		Self::get_nft_collection(collection_id)1382	}13831384	pub fn get_typed_nft_collection_mapped(1385		rmrk_collection_id: RmrkCollectionId,1386		collection_type: misc::CollectionType,1387	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1388		let unique_collection_id = match collection_type {1389			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1390			_ => rmrk_collection_id.into(),1391		};13921393		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13941395		Ok((collection, unique_collection_id))1396	}13971398	pub fn get_nft_property(1399		collection_id: CollectionId,1400		nft_id: TokenId,1401		key: RmrkProperty,1402	) -> Result<PropertyValue, DispatchError> {1403		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1404			.get(&Self::rmrk_property_key(key)?)1405			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1406			.clone();14071408		Ok(nft_property)1409	}14101411	pub fn get_nft_property_decoded<V: Decode>(1412		collection_id: CollectionId,1413		nft_id: TokenId,1414		key: RmrkProperty,1415	) -> Result<V, DispatchError> {1416		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1417	}14181419	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1420		<TokenData<T>>::contains_key((collection_id, nft_id))1421	}14221423	pub fn get_nft_type(1424		collection_id: CollectionId,1425		token_id: TokenId,1426	) -> Result<NftType, DispatchError> {1427		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1428			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1429	}14301431	pub fn ensure_nft_type(1432		collection_id: CollectionId,1433		token_id: TokenId,1434		nft_type: NftType,1435	) -> DispatchResult {1436		let actual_type = Self::get_nft_type(collection_id, token_id)?;1437		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14381439		Ok(())1440	}14411442	pub fn ensure_nft_owner(1443		collection_id: CollectionId,1444		token_id: TokenId,1445		possible_owner: &T::CrossAccountId,1446		nesting_budget: &dyn budget::Budget,1447	) -> DispatchResult {1448		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1449			possible_owner.clone(),1450			collection_id,1451			token_id,1452			None,1453			nesting_budget,1454		)1455		.map_err(Self::map_unique_err_to_proxy)?;14561457		ensure!(is_owned, <Error<T>>::NoPermission);14581459		Ok(())1460	}14611462	pub fn filter_user_properties<Key, Value, R, Mapper>(1463		collection_id: CollectionId,1464		token_id: Option<TokenId>,1465		filter_keys: Option<Vec<RmrkPropertyKey>>,1466		mapper: Mapper,1467	) -> Result<Vec<R>, DispatchError>1468	where1469		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1470		Value: Decode + Default,1471		Mapper: Fn(Key, Value) -> R,1472	{1473		filter_keys1474			.map(|keys| {1475				let properties = keys1476					.into_iter()1477					.filter_map(|key| {1478						let key: Key = key.try_into().ok()?;14791480						let value = match token_id {1481							Some(token_id) => Self::get_nft_property_decoded(1482								collection_id,1483								token_id,1484								UserProperty(key.as_ref()),1485							),1486							None => Self::get_collection_property_decoded(1487								collection_id,1488								UserProperty(key.as_ref()),1489							),1490						}1491						.ok()?;14921493						Some(mapper(key, value))1494					})1495					.collect();14961497				Ok(properties)1498			})1499			.unwrap_or_else(|| {1500				let properties =1501					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15021503				Ok(properties)1504			})1505	}15061507	pub fn iterate_user_properties<Key, Value, R, Mapper>(1508		collection_id: CollectionId,1509		token_id: Option<TokenId>,1510		mapper: Mapper,1511	) -> Result<impl Iterator<Item = R>, DispatchError>1512	where1513		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1514		Value: Decode + Default,1515		Mapper: Fn(Key, Value) -> R,1516	{1517		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15181519		let properties = match token_id {1520			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1521			None => <PalletCommon<T>>::collection_properties(collection_id),1522		};15231524		let properties = properties.into_iter().filter_map(move |(key, value)| {1525			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15261527			let key: Key = key.to_vec().try_into().ok()?;1528			let value: Value = value.decode().ok()?;15291530			Some(mapper(key, value))1531		});15321533		Ok(properties)1534	}15351536	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1537		map_unique_err_to_proxy! {1538			match err {1539				CommonError::NoPermission => NoPermission,1540				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1541				CommonError::PublicMintingNotAllowed => NoPermission,1542				CommonError::TokenNotFound => NoAvailableNftId,1543				CommonError::ApprovedValueTooLow => NoPermission,1544				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1545				StructureError::TokenNotFound => NoAvailableNftId,1546				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1547			}1548		}1549	}1550}
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::*;4748pub const 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		#[transactional]242		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]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		#[transactional]274		#[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]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		#[transactional]305		#[pallet::weight(<SelfWeightOf<T>>::lock_collection())]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		#[transactional]346		#[pallet::weight(<SelfWeightOf<T>>::mint_nft())]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		#[transactional]444		#[pallet::weight(<SelfWeightOf<T>>::send())]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		#[transactional]556		#[pallet::weight(<SelfWeightOf<T>>::accept_nft())]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		#[transactional]668		#[pallet::weight(<SelfWeightOf<T>>::accept_resource())]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)?;696697			let resource_collection_id =698				resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;699700			let is_pending: bool = Self::get_nft_property_decoded(701				resource_collection_id,702				resource_id,703				PendingResourceAccept,704			)705			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;706707			ensure!(is_pending, <Error<T>>::ResourceNotPending);708709			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);710711			<PalletNft<T>>::set_scoped_token_property(712				resource_collection_id,713				rmrk_resource_id.into(),714				PropertyScope::Rmrk,715				Self::rmrk_property(PendingResourceAccept, &false)?,716			)?;717718			Self::deposit_event(Event::<T>::ResourceAccepted {719				nft_id: rmrk_nft_id,720				resource_id: rmrk_resource_id,721			});722723			Ok(())724		}725726		/// accept the removal of a resource of an existing NFT727		#[transactional]728		#[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]729		pub fn accept_resource_removal(730			origin: OriginFor<T>,731			rmrk_collection_id: RmrkCollectionId,732			rmrk_nft_id: RmrkNftId,733			rmrk_resource_id: RmrkResourceId,734		) -> DispatchResult {735			let sender = ensure_signed(origin)?;736			let cross_sender = T::CrossAccountId::from_sub(sender);737738			let collection_id = Self::unique_collection_id(rmrk_collection_id)739				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;740			let collection =741				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;742			collection.check_is_external()?;743744			let nft_id = rmrk_nft_id.into();745			let resource_id = rmrk_resource_id.into();746747			let budget = budget::Value::new(NESTING_BUDGET);748749			let nft_owner =750				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)751					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;752753			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);754755			let resource_collection_id: Option<CollectionId> =756				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)757					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;758759			let resource_collection_id =760				resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;761762			let is_pending: bool = Self::get_nft_property_decoded(763				resource_collection_id,764				resource_id,765				PendingResourceRemoval,766			)767			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;768769			ensure!(is_pending, <Error<T>>::ResourceNotPending);770771			let resource_collection = Self::get_typed_nft_collection(772				resource_collection_id,773				misc::CollectionType::Resource,774			)?;775776			let resource_data = <TokenData<T>>::get((resource_collection_id, resource_id))777				.ok_or(<Error<T>>::ResourceDoesntExist)?;778779			let resource_owner = resource_data.owner;780781			<PalletNft<T>>::burn(782				&resource_collection,783				&resource_owner,784				rmrk_resource_id.into(),785			)786			.map_err(Self::map_unique_err_to_proxy)?;787788			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {789				nft_id: rmrk_nft_id,790				resource_id: rmrk_resource_id,791			});792793			Ok(())794		}795796		/// set a custom value on an NFT797		#[transactional]798		#[pallet::weight(<SelfWeightOf<T>>::set_property())]799		pub fn set_property(800			origin: OriginFor<T>,801			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,802			maybe_nft_id: Option<RmrkNftId>,803			key: RmrkKeyString,804			value: RmrkValueString,805		) -> DispatchResult {806			let sender = ensure_signed(origin)?;807			let sender = T::CrossAccountId::from_sub(sender);808809			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;810			let collection =811				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;812			collection.check_is_external()?;813814			let budget = budget::Value::new(NESTING_BUDGET);815816			match maybe_nft_id {817				Some(nft_id) => {818					let token_id: TokenId = nft_id.into();819820					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;821					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;822823					<PalletNft<T>>::set_scoped_token_property(824						collection_id,825						token_id,826						PropertyScope::Rmrk,827						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,828					)?;829				}830				None => {831					let collection = Self::get_typed_nft_collection(832						collection_id,833						misc::CollectionType::Regular,834					)?;835836					Self::check_collection_owner(&collection, &sender)?;837838					<PalletCommon<T>>::set_scoped_collection_property(839						collection_id,840						PropertyScope::Rmrk,841						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,842					)?;843				}844			}845846			Self::deposit_event(Event::PropertySet {847				collection_id: rmrk_collection_id,848				maybe_nft_id,849				key,850				value,851			});852853			Ok(())854		}855856		/// set a different order of resource priority857		#[transactional]858		#[pallet::weight(<SelfWeightOf<T>>::set_priority())]859		pub fn set_priority(860			origin: OriginFor<T>,861			rmrk_collection_id: RmrkCollectionId,862			rmrk_nft_id: RmrkNftId,863			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,864		) -> DispatchResult {865			let sender = ensure_signed(origin)?;866			let sender = T::CrossAccountId::from_sub(sender);867868			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;869			let nft_id = rmrk_nft_id.into();870871			let collection =872				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;873			collection.check_is_external()?;874875			let budget = budget::Value::new(NESTING_BUDGET);876877			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;878			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;879880			<PalletNft<T>>::set_scoped_token_property(881				collection_id,882				nft_id,883				PropertyScope::Rmrk,884				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,885			)?;886887			Self::deposit_event(Event::<T>::PrioritySet {888				collection_id: rmrk_collection_id,889				nft_id: rmrk_nft_id,890			});891892			Ok(())893		}894895		/// Create basic resource896		#[transactional]897		#[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]898		pub fn add_basic_resource(899			origin: OriginFor<T>,900			rmrk_collection_id: RmrkCollectionId,901			nft_id: RmrkNftId,902			resource: RmrkBasicResource,903		) -> DispatchResult {904			let sender = ensure_signed(origin.clone())?;905906			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;907			let collection =908				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;909			collection.check_is_external()?;910911			let resource_id = Self::resource_add(912				sender,913				collection_id,914				nft_id.into(),915				[916					Self::rmrk_property(TokenType, &NftType::Resource)?,917					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,918					Self::rmrk_property(Src, &resource.src)?,919					Self::rmrk_property(Metadata, &resource.metadata)?,920					Self::rmrk_property(License, &resource.license)?,921					Self::rmrk_property(Thumb, &resource.thumb)?,922				]923				.into_iter(),924			)?;925926			Self::deposit_event(Event::ResourceAdded {927				nft_id,928				resource_id,929			});930			Ok(())931		}932933		/// Create composable resource934		#[transactional]935		#[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]936		pub fn add_composable_resource(937			origin: OriginFor<T>,938			rmrk_collection_id: RmrkCollectionId,939			nft_id: RmrkNftId,940			resource: RmrkComposableResource,941		) -> DispatchResult {942			let sender = ensure_signed(origin.clone())?;943944			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;945			let collection =946				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;947			collection.check_is_external()?;948949			let resource_id = Self::resource_add(950				sender,951				collection_id,952				nft_id.into(),953				[954					Self::rmrk_property(TokenType, &NftType::Resource)?,955					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,956					Self::rmrk_property(Parts, &resource.parts)?,957					Self::rmrk_property(Base, &resource.base)?,958					Self::rmrk_property(Src, &resource.src)?,959					Self::rmrk_property(Metadata, &resource.metadata)?,960					Self::rmrk_property(License, &resource.license)?,961					Self::rmrk_property(Thumb, &resource.thumb)?,962				]963				.into_iter(),964			)?;965966			Self::deposit_event(Event::ResourceAdded {967				nft_id,968				resource_id,969			});970			Ok(())971		}972973		/// Create slot resource974		#[transactional]975		#[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]976		pub fn add_slot_resource(977			origin: OriginFor<T>,978			rmrk_collection_id: RmrkCollectionId,979			nft_id: RmrkNftId,980			resource: RmrkSlotResource,981		) -> DispatchResult {982			let sender = ensure_signed(origin.clone())?;983984			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;985			let collection =986				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;987			collection.check_is_external()?;988989			let resource_id = Self::resource_add(990				sender,991				collection_id,992				nft_id.into(),993				[994					Self::rmrk_property(TokenType, &NftType::Resource)?,995					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,996					Self::rmrk_property(Base, &resource.base)?,997					Self::rmrk_property(Src, &resource.src)?,998					Self::rmrk_property(Metadata, &resource.metadata)?,999					Self::rmrk_property(Slot, &resource.slot)?,1000					Self::rmrk_property(License, &resource.license)?,1001					Self::rmrk_property(Thumb, &resource.thumb)?,1002				]1003				.into_iter(),1004			)?;10051006			Self::deposit_event(Event::ResourceAdded {1007				nft_id,1008				resource_id,1009			});1010			Ok(())1011		}10121013		/// remove resource1014		#[transactional]1015		#[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1016		pub fn remove_resource(1017			origin: OriginFor<T>,1018			rmrk_collection_id: RmrkCollectionId,1019			nft_id: RmrkNftId,1020			resource_id: RmrkResourceId,1021		) -> DispatchResult {1022			let sender = ensure_signed(origin.clone())?;10231024			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1025			let collection =1026				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1027			collection.check_is_external()?;10281029			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10301031			Self::deposit_event(Event::ResourceRemoval {1032				nft_id,1033				resource_id,1034			});1035			Ok(())1036		}1037	}1038}10391040impl<T: Config> Pallet<T> {1041	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1042		let key = rmrk_key.to_key::<T>()?;10431044		let scoped_key = PropertyScope::Rmrk1045			.apply(key)1046			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10471048		Ok(scoped_key)1049	}10501051	// todo think about renaming these1052	pub fn rmrk_property<E: Encode>(1053		rmrk_key: RmrkProperty,1054		value: &E,1055	) -> Result<Property, DispatchError> {1056		let key = rmrk_key.to_key::<T>()?;10571058		let value = value1059			.encode()1060			.try_into()1061			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10621063		let property = Property { key, value };10641065		Ok(property)1066	}10671068	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1069		vec.decode()1070			.map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1071	}10721073	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1074	where1075		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1076	{1077		vec.rebind()1078			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1079	}10801081	fn init_collection(1082		sender: T::CrossAccountId,1083		data: CreateCollectionData<T::AccountId>,1084		properties: impl Iterator<Item = Property>,1085	) -> Result<CollectionId, DispatchError> {1086		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10871088		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1089			return Err(<Error<T>>::NoAvailableCollectionId.into());1090		}10911092		<PalletCommon<T>>::set_scoped_collection_properties(1093			collection_id?,1094			PropertyScope::Rmrk,1095			properties,1096		)?;10971098		collection_id1099	}11001101	pub fn create_nft(1102		sender: &T::CrossAccountId,1103		owner: &T::CrossAccountId,1104		collection: &NonfungibleHandle<T>,1105		properties: impl Iterator<Item = Property>,1106	) -> Result<TokenId, DispatchError> {1107		let data = CreateNftExData {1108			properties: BoundedVec::default(),1109			owner: owner.clone(),1110		};11111112		let budget = budget::Value::new(NESTING_BUDGET);11131114		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;11151116		let nft_id = <PalletNft<T>>::current_token_id(collection.id);11171118		<PalletNft<T>>::set_scoped_token_properties(1119			collection.id,1120			nft_id,1121			PropertyScope::Rmrk,1122			properties,1123		)?;11241125		Ok(nft_id)1126	}11271128	fn destroy_nft(1129		sender: T::CrossAccountId,1130		collection_id: CollectionId,1131		token_id: TokenId,1132		max_burns: u32,1133		error_if_not_owned: Error<T>,1134	) -> DispatchResultWithPostInfo {1135		let collection =1136			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11371138		let token_data =1139			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11401141		let from = token_data.owner;11421143		let owner_check_budget = budget::Value::new(NESTING_BUDGET);11441145		ensure!(1146			<PalletStructure<T>>::check_indirectly_owned(1147				sender.clone(),1148				collection_id,1149				token_id,1150				None,1151				&owner_check_budget1152			)?,1153			error_if_not_owned,1154		);11551156		let burns_budget = budget::Value::new(max_burns);1157		let breadth_budget = budget::Value::new(max_burns);11581159		<PalletNft<T>>::burn_recursively(1160			&collection,1161			&from,1162			token_id,1163			&burns_budget,1164			&breadth_budget,1165		)1166	}11671168	fn resource_add(1169		sender: T::AccountId,1170		collection_id: CollectionId,1171		token_id: TokenId,1172		resource_properties: impl Iterator<Item = Property>,1173	) -> Result<RmrkResourceId, DispatchError> {1174		let collection =1175			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1176		ensure!(collection.owner == sender, Error::<T>::NoPermission);11771178		let sender = T::CrossAccountId::from_sub(sender);1179		let budget = budget::Value::new(NESTING_BUDGET);11801181		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1182			.map_err(Self::map_unique_err_to_proxy)?;11831184		let pending = sender != nft_owner;11851186		let resource_collection_id: Option<CollectionId> =1187			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;11881189		let resource_collection_id = match resource_collection_id {1190			Some(id) => id,1191			None => {1192				let resource_collection_id = Self::init_collection(1193					sender.clone(),1194					CreateCollectionData {1195						..Default::default()1196					},1197					[Self::rmrk_property(1198						CollectionType,1199						&misc::CollectionType::Resource,1200					)?]1201					.into_iter(),1202				)?;12031204				<PalletNft<T>>::set_scoped_token_property(1205					collection_id,1206					token_id,1207					PropertyScope::Rmrk,1208					Self::rmrk_property(ResourceCollection, &Some(resource_collection_id))?,1209				)?;12101211				resource_collection_id1212			}1213		};12141215		let resource_collection =1216			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;12171218		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them12191220		let resource_id = Self::create_nft(1221			&sender,1222			&nft_owner,1223			&resource_collection,1224			resource_properties.chain(1225				[1226					Self::rmrk_property(PendingResourceAccept, &pending)?,1227					Self::rmrk_property(PendingResourceRemoval, &false)?,1228				]1229				.into_iter(),1230			),1231		)1232		.map_err(|err| match err {1233			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1234			err => Self::map_unique_err_to_proxy(err),1235		})?;12361237		Ok(resource_id.0)1238	}12391240	fn resource_remove(1241		sender: T::AccountId,1242		collection_id: CollectionId,1243		nft_id: TokenId,1244		resource_id: TokenId,1245	) -> DispatchResult {1246		let collection =1247			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1248		ensure!(collection.owner == sender, Error::<T>::NoPermission);12491250		let resource_collection_id: Option<CollectionId> =1251			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;12521253		let resource_collection_id =1254			resource_collection_id.ok_or(Error::<T>::ResourceDoesntExist)?;12551256		let resource_collection =1257			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1258		ensure!(1259			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1260			Error::<T>::ResourceDoesntExist1261		);12621263		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1264		let topmost_owner =1265			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12661267		let sender = T::CrossAccountId::from_sub(sender);1268		if topmost_owner == sender {1269			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1270				.map_err(Self::map_unique_err_to_proxy)?;1271		} else {1272			<PalletNft<T>>::set_scoped_token_property(1273				resource_collection_id,1274				resource_id,1275				PropertyScope::Rmrk,1276				Self::rmrk_property(PendingResourceRemoval, &true)?,1277			)?;1278		}12791280		Ok(())1281	}12821283	fn change_collection_owner(1284		collection_id: CollectionId,1285		collection_type: misc::CollectionType,1286		sender: T::AccountId,1287		new_owner: T::AccountId,1288	) -> DispatchResult {1289		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1290		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12911292		let mut collection = collection.into_inner();12931294		collection.owner = new_owner;1295		collection.save()1296	}12971298	fn check_collection_owner(1299		collection: &NonfungibleHandle<T>,1300		account: &T::CrossAccountId,1301	) -> DispatchResult {1302		collection1303			.check_is_owner(account)1304			.map_err(Self::map_unique_err_to_proxy)1305	}13061307	pub fn last_collection_idx() -> RmrkCollectionId {1308		<CollectionIndex<T>>::get()1309	}13101311	pub fn unique_collection_id(1312		rmrk_collection_id: RmrkCollectionId,1313	) -> Result<CollectionId, DispatchError> {1314		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1315			.map_err(|_| <Error<T>>::CollectionUnknown.into())1316	}13171318	pub fn rmrk_collection_id(1319		unique_collection_id: CollectionId,1320	) -> Result<RmrkCollectionId, DispatchError> {1321		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1322			.map_err(|_| <Error<T>>::CollectionUnknown.into())1323	}13241325	pub fn get_nft_collection(1326		collection_id: CollectionId,1327	) -> Result<NonfungibleHandle<T>, DispatchError> {1328		let collection = <CollectionHandle<T>>::try_get(collection_id)1329			.map_err(|_| <Error<T>>::CollectionUnknown)?;13301331		match collection.mode {1332			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1333			_ => Err(<Error<T>>::CollectionUnknown.into()),1334		}1335	}13361337	pub fn collection_exists(collection_id: CollectionId) -> bool {1338		<CollectionHandle<T>>::try_get(collection_id).is_ok()1339	}13401341	pub fn get_collection_property(1342		collection_id: CollectionId,1343		key: RmrkProperty,1344	) -> Result<PropertyValue, DispatchError> {1345		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1346			.get(&Self::rmrk_property_key(key)?)1347			.ok_or(<Error<T>>::CollectionUnknown)?1348			.clone();13491350		Ok(collection_property)1351	}13521353	pub fn get_collection_property_decoded<V: Decode>(1354		collection_id: CollectionId,1355		key: RmrkProperty,1356	) -> Result<V, DispatchError> {1357		Self::decode_property(Self::get_collection_property(collection_id, key)?)1358	}13591360	pub fn get_collection_type(1361		collection_id: CollectionId,1362	) -> Result<misc::CollectionType, DispatchError> {1363		Self::get_collection_property_decoded(collection_id, CollectionType)1364			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1365	}13661367	pub fn ensure_collection_type(1368		collection_id: CollectionId,1369		collection_type: misc::CollectionType,1370	) -> DispatchResult {1371		let actual_type = Self::get_collection_type(collection_id)?;1372		ensure!(1373			actual_type == collection_type,1374			<CommonError<T>>::NoPermission1375		);13761377		Ok(())1378	}13791380	pub fn get_typed_nft_collection(1381		collection_id: CollectionId,1382		collection_type: misc::CollectionType,1383	) -> Result<NonfungibleHandle<T>, DispatchError> {1384		Self::ensure_collection_type(collection_id, collection_type)?;13851386		Self::get_nft_collection(collection_id)1387	}13881389	pub fn get_typed_nft_collection_mapped(1390		rmrk_collection_id: RmrkCollectionId,1391		collection_type: misc::CollectionType,1392	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1393		let unique_collection_id = match collection_type {1394			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1395			_ => rmrk_collection_id.into(),1396		};13971398		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13991400		Ok((collection, unique_collection_id))1401	}14021403	pub fn get_nft_property(1404		collection_id: CollectionId,1405		nft_id: TokenId,1406		key: RmrkProperty,1407	) -> Result<PropertyValue, DispatchError> {1408		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1409			.get(&Self::rmrk_property_key(key)?)1410			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1411			.clone();14121413		Ok(nft_property)1414	}14151416	pub fn get_nft_property_decoded<V: Decode>(1417		collection_id: CollectionId,1418		nft_id: TokenId,1419		key: RmrkProperty,1420	) -> Result<V, DispatchError> {1421		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1422	}14231424	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1425		<TokenData<T>>::contains_key((collection_id, nft_id))1426	}14271428	pub fn get_nft_type(1429		collection_id: CollectionId,1430		token_id: TokenId,1431	) -> Result<NftType, DispatchError> {1432		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1433			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1434	}14351436	pub fn ensure_nft_type(1437		collection_id: CollectionId,1438		token_id: TokenId,1439		nft_type: NftType,1440	) -> DispatchResult {1441		let actual_type = Self::get_nft_type(collection_id, token_id)?;1442		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14431444		Ok(())1445	}14461447	pub fn ensure_nft_owner(1448		collection_id: CollectionId,1449		token_id: TokenId,1450		possible_owner: &T::CrossAccountId,1451		nesting_budget: &dyn budget::Budget,1452	) -> DispatchResult {1453		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1454			possible_owner.clone(),1455			collection_id,1456			token_id,1457			None,1458			nesting_budget,1459		)1460		.map_err(Self::map_unique_err_to_proxy)?;14611462		ensure!(is_owned, <Error<T>>::NoPermission);14631464		Ok(())1465	}14661467	pub fn filter_user_properties<Key, Value, R, Mapper>(1468		collection_id: CollectionId,1469		token_id: Option<TokenId>,1470		filter_keys: Option<Vec<RmrkPropertyKey>>,1471		mapper: Mapper,1472	) -> Result<Vec<R>, DispatchError>1473	where1474		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1475		Value: Decode + Default,1476		Mapper: Fn(Key, Value) -> R,1477	{1478		filter_keys1479			.map(|keys| {1480				let properties = keys1481					.into_iter()1482					.filter_map(|key| {1483						let key: Key = key.try_into().ok()?;14841485						let value = match token_id {1486							Some(token_id) => Self::get_nft_property_decoded(1487								collection_id,1488								token_id,1489								UserProperty(key.as_ref()),1490							),1491							None => Self::get_collection_property_decoded(1492								collection_id,1493								UserProperty(key.as_ref()),1494							),1495						}1496						.ok()?;14971498						Some(mapper(key, value))1499					})1500					.collect();15011502				Ok(properties)1503			})1504			.unwrap_or_else(|| {1505				let properties =1506					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15071508				Ok(properties)1509			})1510	}15111512	pub fn iterate_user_properties<Key, Value, R, Mapper>(1513		collection_id: CollectionId,1514		token_id: Option<TokenId>,1515		mapper: Mapper,1516	) -> Result<impl Iterator<Item = R>, DispatchError>1517	where1518		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1519		Value: Decode + Default,1520		Mapper: Fn(Key, Value) -> R,1521	{1522		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15231524		let properties = match token_id {1525			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1526			None => <PalletCommon<T>>::collection_properties(collection_id),1527		};15281529		let properties = properties.into_iter().filter_map(move |(key, value)| {1530			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15311532			let key: Key = key.to_vec().try_into().ok()?;1533			let value: Value = value.decode().ok()?;15341535			Some(mapper(key, value))1536		});15371538		Ok(properties)1539	}15401541	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1542		map_unique_err_to_proxy! {1543			match err {1544				CommonError::NoPermission => NoPermission,1545				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1546				CommonError::PublicMintingNotAllowed => NoPermission,1547				CommonError::TokenNotFound => NoAvailableNftId,1548				CommonError::ApprovedValueTooLow => NoPermission,1549				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1550				StructureError::TokenNotFound => NoAvailableNftId,1551				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1552			}1553		}1554	}1555}