git.delta.rocks / unique-network / refs/commits / 2050a76c8808

difftreelog

feat external-internal api collection creation segreation

Fahrrader2022-06-08parent: #0cb9e74.patch.diff
in: master

11 files changed

modifiedpallets/common/src/dispatch.rsdiffbeforeafterboth
--- a/pallets/common/src/dispatch.rs
+++ b/pallets/common/src/dispatch.rs
@@ -36,6 +36,13 @@
 			},
 			error,
 		})?;
+	handle.check_is_internal().map_err(|error| DispatchErrorWithPostInfo {
+		post_info: PostDispatchInfo {
+			actual_weight: Some(dispatch_weight::<T>()),
+			pays_fee: Pays::Yes,
+		},
+		error,
+	})?;
 	let dispatched = T::CollectionDispatch::dispatch(handle);
 	let mut result = call(dispatched.as_dyn());
 	match &mut result {
modifiedpallets/common/src/erc.rsdiffbeforeafterboth
--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -316,8 +316,9 @@
 }
 
 fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {
+	// TODO possibly delete for the lack of transaction
 	collection
-		.check_is_mutable()
+		.check_is_internal()
 		.map_err(dispatch_to_evm::<T>)?;
 	<crate::CollectionById<T>>::insert(collection.id, collection.collection.clone());
 	Ok(())
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -149,20 +149,16 @@
 			))
 	}
 	pub fn save(self) -> Result<(), DispatchError> {
-		self.check_is_mutable()?;
 		<CollectionById<T>>::insert(self.id, self.collection);
 		Ok(())
 	}
 
 	pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {
-		self.check_is_mutable()?;
 		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);
 		Ok(())
 	}
 
 	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {
-		self.check_is_mutable()?;
-
 		if self.collection.sponsorship.pending_sponsor() != Some(sender) {
 			return Ok(false);
 		}
@@ -171,11 +167,21 @@
 		Ok(true)
 	}
 
-	/// Checks that collection is can be mutate.
-	/// Now check only `external_collection` flag and if it **true**, than return `CollectionIsReadOnly` error.
-	pub fn check_is_mutable(&self) -> DispatchResult {
+	/// Checks that the collection was created with, and must be operated upon through **Unique API**.
+	/// Now check only the `external_collection` flag and if it's **true**, then return `CollectionIsExternal` error.
+	pub fn check_is_internal(&self) -> DispatchResult {
 		if self.external_collection {
-			return Err(<Error<T>>::CollectionIsReadOnly)?;
+			return Err(<Error<T>>::CollectionIsExternal)?;
+		}
+
+		Ok(())
+	}
+
+	/// Checks that the collection was created with, and must be operated upon through an **assimilated API**.
+	/// Now check only the `external_collection` flag and if it's **false**, then return `CollectionIsInternal` error.
+	pub fn check_is_external(&self) -> DispatchResult {
+		if !self.external_collection {
+			return Err(<Error<T>>::CollectionIsInternal)?;
 		}
 
 		Ok(())
@@ -449,8 +455,11 @@
 		/// Empty property keys are forbidden
 		EmptyPropertyKey,
 
-		/// Collection is read only
-		CollectionIsReadOnly,
+		/// Tried to access an external collection with an internal API
+		CollectionIsExternal,
+
+		/// Tried to access an internal collection with an external API
+		CollectionIsInternal,
 	}
 
 	#[pallet::storage]
@@ -754,6 +763,7 @@
 	pub fn init_collection(
 		owner: T::CrossAccountId,
 		data: CreateCollectionData<T::AccountId>,
+		is_external: bool,
 	) -> Result<CollectionId, DispatchError> {
 		{
 			ensure!(
@@ -797,7 +807,7 @@
 					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)
 				})
 				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,
-			external_collection: false,
+			external_collection: is_external,
 		};
 
 		let mut collection_properties = up_data_structs::CollectionProperties::get();
@@ -854,7 +864,6 @@
 		collection: CollectionHandle<T>,
 		sender: &T::CrossAccountId,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
 		ensure!(
 			collection.limits.owner_can_destroy(),
 			<Error<T>>::NoPermission,
@@ -884,7 +893,6 @@
 		sender: &T::CrossAccountId,
 		property: Property,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
 		collection.check_is_owner_or_admin(sender)?;
 
 		CollectionProperties::<T>::try_mutate(collection.id, |properties| {
@@ -930,8 +938,6 @@
 		sender: &T::CrossAccountId,
 		properties: Vec<Property>,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
-
 		for property in properties {
 			Self::set_collection_property(collection, sender, property)?;
 		}
@@ -944,7 +950,6 @@
 		sender: &T::CrossAccountId,
 		property_key: PropertyKey,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
 		collection.check_is_owner_or_admin(sender)?;
 
 		CollectionProperties::<T>::try_mutate(collection.id, |properties| {
@@ -966,8 +971,6 @@
 		sender: &T::CrossAccountId,
 		property_keys: Vec<PropertyKey>,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
-
 		for key in property_keys {
 			Self::delete_collection_property(collection, sender, key)?;
 		}
@@ -992,7 +995,6 @@
 		sender: &T::CrossAccountId,
 		property_permission: PropertyKeyPermission,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
 		collection.check_is_owner_or_admin(sender)?;
 
 		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);
@@ -1024,8 +1026,6 @@
 		sender: &T::CrossAccountId,
 		property_permissions: Vec<PropertyKeyPermission>,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
-
 		for prop_pemission in property_permissions {
 			Self::set_property_permission(collection, sender, prop_pemission)?;
 		}
@@ -1113,7 +1113,6 @@
 		user: &T::CrossAccountId,
 		allowed: bool,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
 		collection.check_is_owner_or_admin(sender)?;
 
 		// =========
@@ -1133,7 +1132,6 @@
 		user: &T::CrossAccountId,
 		admin: bool,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
 		collection.check_is_owner_or_admin(sender)?;
 
 		let was_admin = <IsAdmin<T>>::get((collection.id, user));
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -137,7 +137,7 @@
 		owner: T::CrossAccountId,
 		data: CreateCollectionData<T::AccountId>,
 	) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(owner, data)
+		<PalletCommon<T>>::init_collection(owner, data, false)
 	}
 	pub fn destroy_collection(
 		collection: FungibleHandle<T>,
@@ -168,8 +168,6 @@
 		owner: &T::CrossAccountId,
 		amount: u128,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
-
 		let total_supply = <TotalSupply<T>>::get(collection.id)
 			.checked_sub(amount)
 			.ok_or(<CommonError<T>>::TokenValueTooLow)?;
@@ -216,8 +214,6 @@
 		amount: u128,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
-
 		ensure!(
 			collection.limits.transfers_enabled(),
 			<CommonError<T>>::TransferNotAllowed,
@@ -287,8 +283,6 @@
 		data: BTreeMap<T::CrossAccountId, u128>,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
-
 		if !collection.is_owner_or_admin(sender) {
 			ensure!(
 				collection.permissions.mint_mode(),
@@ -390,7 +384,6 @@
 		spender: &T::CrossAccountId,
 		amount: u128,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
 		if collection.permissions.access() == AccessMode::AllowList {
 			collection.check_allowlist(owner)?;
 			collection.check_allowlist(spender)?;
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -304,8 +304,9 @@
 	pub fn init_collection(
 		owner: T::CrossAccountId,
 		data: CreateCollectionData<T::AccountId>,
+		is_external: bool,
 	) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(owner, data)
+		<PalletCommon<T>>::init_collection(owner, data, is_external)
 	}
 	pub fn destroy_collection(
 		collection: NonfungibleHandle<T>,
@@ -336,8 +337,6 @@
 		sender: &T::CrossAccountId,
 		token: TokenId,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
-
 		let token_data =
 			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;
 		ensure!(
@@ -458,7 +457,6 @@
 			&property.key,
 			is_token_create,
 		)?;
-		collection.check_is_mutable()?;
 
 		<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {
 			let property = property.clone();
@@ -496,7 +494,6 @@
 		token_id: TokenId,
 		property_key: PropertyKey,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
 		Self::check_token_change_permission(collection, sender, token_id, &property_key, false)?;
 
 		<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {
@@ -574,8 +571,6 @@
 		token_id: TokenId,
 		property_keys: Vec<PropertyKey>,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
-
 		for key in property_keys {
 			Self::delete_token_property(collection, sender, token_id, key)?;
 		}
@@ -622,8 +617,6 @@
 		token: TokenId,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
-
 		ensure!(
 			collection.limits.transfers_enabled(),
 			<CommonError<T>>::TransferNotAllowed
@@ -902,8 +895,6 @@
 		token: TokenId,
 		spender: Option<&T::CrossAccountId>,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
-
 		if collection.permissions.access() == AccessMode::AllowList {
 			collection.check_allowlist(sender)?;
 			if let Some(spender) = spender {
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::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46	use super::*;47	use pallet_evm::account;4849	#[pallet::config]50	pub trait Config:51		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52	{53		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54	}5556	#[pallet::storage]57	#[pallet::getter(fn collection_index)]58	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960	#[pallet::storage]61	pub type UniqueCollectionId<T: Config> =62		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364	#[pallet::storage]65	pub type RmrkInernalCollectionId<T: Config> =66		StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768	#[pallet::pallet]69	#[pallet::generate_store(pub(super) trait Store)]70	pub struct Pallet<T>(_);7172	#[pallet::event]73	#[pallet::generate_deposit(pub(super) fn deposit_event)]74	pub enum Event<T: Config> {75		CollectionCreated {76			issuer: T::AccountId,77			collection_id: RmrkCollectionId,78		},79		CollectionDestroyed {80			issuer: T::AccountId,81			collection_id: RmrkCollectionId,82		},83		IssuerChanged {84			old_issuer: T::AccountId,85			new_issuer: T::AccountId,86			collection_id: RmrkCollectionId,87		},88		CollectionLocked {89			issuer: T::AccountId,90			collection_id: RmrkCollectionId,91		},92		NftMinted {93			owner: T::AccountId,94			collection_id: RmrkCollectionId,95			nft_id: RmrkNftId,96		},97		NFTBurned {98			owner: T::AccountId,99			nft_id: RmrkNftId,100		},101		NFTSent {102			sender: T::AccountId,103			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,104			collection_id: RmrkCollectionId,105			nft_id: RmrkNftId,106			approval_required: bool,107		},108		NFTAccepted {109			sender: T::AccountId,110			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111			collection_id: RmrkCollectionId,112			nft_id: RmrkNftId,113		},114		NFTRejected {115			sender: T::AccountId,116			collection_id: RmrkCollectionId,117			nft_id: RmrkNftId,118		},119		PropertySet {120			collection_id: RmrkCollectionId,121			maybe_nft_id: Option<RmrkNftId>,122			key: RmrkKeyString,123			value: RmrkValueString,124		},125		ResourceAdded {126			nft_id: RmrkNftId,127			resource_id: RmrkResourceId,128		},129		ResourceRemoval {130			nft_id: RmrkNftId,131			resource_id: RmrkResourceId,132		},133		ResourceAccepted {134			nft_id: RmrkNftId,135			resource_id: RmrkResourceId,136		},137		ResourceRemovalAccepted {138			nft_id: RmrkNftId,139			resource_id: RmrkResourceId,140		},141		PrioritySet {142			collection_id: RmrkCollectionId,143			nft_id: RmrkNftId,144		},145	}146147	#[pallet::error]148	pub enum Error<T> {149		/* Unique-specific events */150		CorruptedCollectionType,151		NftTypeEncodeError,152		RmrkPropertyKeyIsTooLong,153		RmrkPropertyValueIsTooLong,154155		/* RMRK compatible events */156		CollectionNotEmpty,157		NoAvailableCollectionId,158		NoAvailableNftId,159		CollectionUnknown,160		NoPermission,161		NonTransferable,162		CollectionFullOrLocked,163		ResourceDoesntExist,164		CannotSendToDescendentOrSelf,165		CannotAcceptNonOwnedNft,166		CannotRejectNonOwnedNft,167		ResourceNotPending,168	}169170	#[pallet::call]171	impl<T: Config> Pallet<T> {172		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]173		#[transactional]174		pub fn create_collection(175			origin: OriginFor<T>,176			metadata: RmrkString,177			max: Option<u32>,178			symbol: RmrkCollectionSymbol,179		) -> DispatchResult {180			let sender = ensure_signed(origin)?;181182			let limits = CollectionLimits {183				owner_can_transfer: Some(false),184				token_limit: max,185				..Default::default()186			};187188			let data = CreateCollectionData {189				limits: Some(limits),190				token_prefix: symbol191					.into_inner()192					.try_into()193					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,194				permissions: Some(CollectionPermissions {195					nesting: Some(NestingRule::Owner),196					..Default::default()197				}),198				..Default::default()199			};200201			let unique_collection_id = Self::init_collection(202				T::CrossAccountId::from_sub(sender.clone()),203				data,204				[205					Self::rmrk_property(Metadata, &metadata)?,206					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,207				]208				.into_iter(),209			)?;210			let rmrk_collection_id = <CollectionIndex<T>>::get();211212			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);213			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);214215			<CollectionIndex<T>>::mutate(|n| *n += 1);216217			Self::deposit_event(Event::CollectionCreated {218				issuer: sender,219				collection_id: rmrk_collection_id,220			});221222			Ok(())223		}224225		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]226		#[transactional]227		pub fn destroy_collection(228			origin: OriginFor<T>,229			collection_id: RmrkCollectionId,230		) -> DispatchResult {231			let sender = ensure_signed(origin)?;232			let cross_sender = T::CrossAccountId::from_sub(sender.clone());233234			let collection = Self::get_typed_nft_collection(235				Self::unique_collection_id(collection_id)?,236				misc::CollectionType::Regular,237			)?;238239			<PalletNft<T>>::destroy_collection(collection, &cross_sender)240				.map_err(Self::map_unique_err_to_proxy)?;241242			Self::deposit_event(Event::CollectionDestroyed {243				issuer: sender,244				collection_id,245			});246247			Ok(())248		}249250		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]251		#[transactional]252		pub fn change_collection_issuer(253			origin: OriginFor<T>,254			collection_id: RmrkCollectionId,255			new_issuer: <T::Lookup as StaticLookup>::Source,256		) -> DispatchResult {257			let sender = ensure_signed(origin)?;258259			let new_issuer = T::Lookup::lookup(new_issuer)?;260261			Self::change_collection_owner(262				Self::unique_collection_id(collection_id)?,263				misc::CollectionType::Regular,264				sender.clone(),265				new_issuer.clone(),266			)?;267268			Self::deposit_event(Event::IssuerChanged {269				old_issuer: sender,270				new_issuer,271				collection_id,272			});273274			Ok(())275		}276277		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]278		#[transactional]279		pub fn lock_collection(280			origin: OriginFor<T>,281			collection_id: RmrkCollectionId,282		) -> DispatchResult {283			let sender = ensure_signed(origin)?;284			let cross_sender = T::CrossAccountId::from_sub(sender.clone());285286			let collection = Self::get_typed_nft_collection(287				Self::unique_collection_id(collection_id)?,288				misc::CollectionType::Regular,289			)?;290291			Self::check_collection_owner(&collection, &cross_sender)?;292293			let token_count = collection.total_supply();294295			let mut collection = collection.into_inner();296			collection.limits.token_limit = Some(token_count);297			collection.save()?;298299			Self::deposit_event(Event::CollectionLocked {300				issuer: sender,301				collection_id,302			});303304			Ok(())305		}306307		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]308		#[transactional]309		pub fn mint_nft(310			origin: OriginFor<T>,311			owner: T::AccountId,312			collection_id: RmrkCollectionId,313			recipient: Option<T::AccountId>,314			royalty_amount: Option<Permill>,315			metadata: RmrkString,316			transferable: bool,317		) -> DispatchResult {318			let sender = ensure_signed(origin)?;319			let sender = T::CrossAccountId::from_sub(sender);320			let cross_owner = T::CrossAccountId::from_sub(owner.clone());321322			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {323				recipient: recipient.unwrap_or_else(|| owner.clone()),324				amount,325			});326327			let collection = Self::get_typed_nft_collection(328				Self::unique_collection_id(collection_id)?,329				misc::CollectionType::Regular,330			)?;331332			let nft_id = Self::create_nft(333				&sender,334				&cross_owner,335				&collection,336				[337					Self::rmrk_property(TokenType, &NftType::Regular)?,338					Self::rmrk_property(Transferable, &transferable)?,339					Self::rmrk_property(PendingNftAccept, &false)?,340					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,341					Self::rmrk_property(Metadata, &metadata)?,342					Self::rmrk_property(Equipped, &false)?,343					Self::rmrk_property(344						ResourceCollection,345						&Self::init_collection(346							sender.clone(),347							CreateCollectionData {348								..Default::default()349							},350							[Self::rmrk_property(351								CollectionType,352								&misc::CollectionType::Resource,353							)?]354							.into_iter(),355						)?,356					)?, // todo possibly add limits to the collection if rmrk warrants them357					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,358				]359				.into_iter(),360			)361			.map_err(|err| match err {362				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),363				err => Self::map_unique_err_to_proxy(err),364			})?;365366			Self::deposit_event(Event::NftMinted {367				owner,368				collection_id,369				nft_id: nft_id.0,370			});371372			Ok(())373		}374375		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]376		#[transactional]377		pub fn burn_nft(378			origin: OriginFor<T>,379			collection_id: RmrkCollectionId,380			nft_id: RmrkNftId,381		) -> DispatchResult {382			let sender = ensure_signed(origin)?;383			let cross_sender = T::CrossAccountId::from_sub(sender.clone());384385			Self::destroy_nft(386				cross_sender,387				Self::unique_collection_id(collection_id)?,388				nft_id.into(),389			)390			.map_err(Self::map_unique_err_to_proxy)?;391392			Self::deposit_event(Event::NFTBurned {393				owner: sender,394				nft_id,395			});396397			Ok(())398		}399400		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]401		#[transactional]402		pub fn send(403			origin: OriginFor<T>,404			rmrk_collection_id: RmrkCollectionId,405			rmrk_nft_id: RmrkNftId,406			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,407		) -> DispatchResult {408			let sender = ensure_signed(origin.clone())?;409			let cross_sender = T::CrossAccountId::from_sub(sender.clone());410411			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;412			let nft_id = rmrk_nft_id.into();413414			let token_data =415				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;416417			let from = token_data.owner;418419			let collection =420				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;421422			ensure!(423				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,424				<Error<T>>::NonTransferable425			);426427			ensure!(428				!Self::get_nft_property_decoded(429					collection_id,430					nft_id,431					RmrkProperty::PendingNftAccept432				)?,433				<Error<T>>::NoPermission434			);435436			let target_owner;437			let approval_required;438439			match new_owner {440				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {441					target_owner = T::CrossAccountId::from_sub(account_id.clone());442					approval_required = false;443				}444				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(445					target_collection_id,446					target_nft_id,447				) => {448					let target_collection_id = Self::unique_collection_id(target_collection_id)?;449450					let target_nft_budget = budget::Value::new(NESTING_BUDGET);451452					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(453						target_collection_id,454						target_nft_id.into(),455						Some((collection_id, nft_id)),456						&target_nft_budget,457					)458					.map_err(Self::map_unique_err_to_proxy)?;459460					approval_required = cross_sender != target_nft_owner;461462					if approval_required {463						target_owner = target_nft_owner;464465						<PalletNft<T>>::set_scoped_token_property(466							collection.id,467							nft_id,468							PropertyScope::Rmrk,469							Self::rmrk_property(PendingNftAccept, &approval_required)?,470						)?;471					} else {472						target_owner = T::CrossTokenAddressMapping::token_to_address(473							target_collection_id,474							target_nft_id.into(),475						);476					}477				}478			}479480			let src_nft_budget = budget::Value::new(NESTING_BUDGET);481482			<PalletNft<T>>::transfer_from(483				&collection,484				&cross_sender,485				&from,486				&target_owner,487				nft_id,488				&src_nft_budget,489			)490			.map_err(Self::map_unique_err_to_proxy)?;491492			Self::deposit_event(Event::NFTSent {493				sender,494				recipient: new_owner,495				collection_id: rmrk_collection_id,496				nft_id: rmrk_nft_id,497				approval_required,498			});499500			Ok(())501		}502503		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]504		#[transactional]505		pub fn accept_nft(506			origin: OriginFor<T>,507			rmrk_collection_id: RmrkCollectionId,508			rmrk_nft_id: RmrkNftId,509			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,510		) -> DispatchResult {511			let sender = ensure_signed(origin.clone())?;512			let cross_sender = T::CrossAccountId::from_sub(sender.clone());513514			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;515			let nft_id = rmrk_nft_id.into();516517			let collection =518				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;519520			let new_cross_owner = match new_owner {521				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {522					T::CrossAccountId::from_sub(account_id.clone())523				}524				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(525					target_collection_id,526					target_nft_id,527				) => {528					let target_collection_id = Self::unique_collection_id(target_collection_id)?;529530					T::CrossTokenAddressMapping::token_to_address(531						target_collection_id,532						TokenId(target_nft_id),533					)534				}535			};536537			let budget = budget::Value::new(NESTING_BUDGET);538539			<PalletNft<T>>::transfer(540				&collection,541				&cross_sender,542				&new_cross_owner,543				nft_id,544				&budget,545			)546			.map_err(|err| {547				if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {548					<Error<T>>::CannotAcceptNonOwnedNft.into()549				} else {550					Self::map_unique_err_to_proxy(err)551				}552			})?;553554			<PalletNft<T>>::set_scoped_token_property(555				collection.id,556				nft_id,557				PropertyScope::Rmrk,558				Self::rmrk_property(PendingNftAccept, &false)?,559			)?;560561			Self::deposit_event(Event::NFTAccepted {562				sender,563				recipient: new_owner,564				collection_id: rmrk_collection_id,565				nft_id: rmrk_nft_id,566			});567568			Ok(())569		}570571		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]572		#[transactional]573		pub fn reject_nft(574			origin: OriginFor<T>,575			rmrk_collection_id: RmrkCollectionId,576			rmrk_nft_id: RmrkNftId,577		) -> DispatchResult {578			let sender = ensure_signed(origin)?;579			let cross_sender = T::CrossAccountId::from_sub(sender.clone());580581			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;582			let nft_id = rmrk_nft_id.into();583584			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {585				if err == <CommonError<T>>::NoPermission.into()586					|| err == <CommonError<T>>::ApprovedValueTooLow.into()587				{588					<Error<T>>::CannotRejectNonOwnedNft.into()589				} else {590					Self::map_unique_err_to_proxy(err)591				}592			})?;593594			Self::deposit_event(Event::NFTRejected {595				sender,596				collection_id: rmrk_collection_id,597				nft_id: rmrk_nft_id,598			});599600			Ok(())601		}602603		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]604		#[transactional]605		pub fn accept_resource(606			origin: OriginFor<T>,607			rmrk_collection_id: RmrkCollectionId,608			rmrk_nft_id: RmrkNftId,609			rmrk_resource_id: RmrkResourceId,610		) -> DispatchResult {611			let sender = ensure_signed(origin)?;612			let cross_sender = T::CrossAccountId::from_sub(sender);613614			let collection_id = Self::unique_collection_id(rmrk_collection_id)615				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;616617			let nft_id = rmrk_nft_id.into();618			let resource_id = rmrk_resource_id.into();619620			let budget = budget::Value::new(NESTING_BUDGET);621622			let nft_owner =623				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)624					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;625626			let resource_collection_id: CollectionId =627				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)628					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;629630			let is_pending: bool = Self::get_nft_property_decoded(631				resource_collection_id,632				resource_id,633				PendingResourceAccept,634			)635			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;636637			ensure!(is_pending, <Error<T>>::ResourceNotPending);638639			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);640641			<PalletNft<T>>::set_scoped_token_property(642				resource_collection_id,643				rmrk_resource_id.into(),644				PropertyScope::Rmrk,645				Self::rmrk_property(PendingResourceAccept, &false)?,646			)?;647648			Self::deposit_event(Event::<T>::ResourceAccepted {649				nft_id: rmrk_nft_id,650				resource_id: rmrk_resource_id,651			});652653			Ok(())654		}655656		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]657		#[transactional]658		pub fn accept_resource_removal(659			origin: OriginFor<T>,660			rmrk_collection_id: RmrkCollectionId,661			rmrk_nft_id: RmrkNftId,662			rmrk_resource_id: RmrkResourceId,663		) -> DispatchResult {664			let sender = ensure_signed(origin)?;665			let cross_sender = T::CrossAccountId::from_sub(sender);666667			let collection_id = Self::unique_collection_id(rmrk_collection_id)668				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;669670			let nft_id = rmrk_nft_id.into();671			let resource_id = rmrk_resource_id.into();672673			let budget = budget::Value::new(NESTING_BUDGET);674675			let nft_owner =676				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)677					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;678679			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);680681			let resource_collection_id: CollectionId =682				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)683					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;684685			let is_pending: bool = Self::get_nft_property_decoded(686				resource_collection_id,687				resource_id,688				PendingResourceRemoval,689			)690			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;691692			ensure!(is_pending, <Error<T>>::ResourceNotPending);693694			let resource_collection = Self::get_typed_nft_collection(695				resource_collection_id,696				misc::CollectionType::Resource,697			)?;698699			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())700				.map_err(Self::map_unique_err_to_proxy)?;701702			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {703				nft_id: rmrk_nft_id,704				resource_id: rmrk_resource_id,705			});706707			Ok(())708		}709710		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]711		#[transactional]712		pub fn set_property(713			origin: OriginFor<T>,714			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,715			maybe_nft_id: Option<RmrkNftId>,716			key: RmrkKeyString,717			value: RmrkValueString,718		) -> DispatchResult {719			let sender = ensure_signed(origin)?;720			let sender = T::CrossAccountId::from_sub(sender);721722			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;723			let budget = budget::Value::new(NESTING_BUDGET);724725			match maybe_nft_id {726				Some(nft_id) => {727					let token_id: TokenId = nft_id.into();728729					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;730					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;731732					<PalletNft<T>>::set_scoped_token_property(733						collection_id,734						token_id,735						PropertyScope::Rmrk,736						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,737					)?;738				}739				None => {740					let collection = Self::get_typed_nft_collection(741						collection_id,742						misc::CollectionType::Regular,743					)?;744745					Self::check_collection_owner(&collection, &sender)?;746747					<PalletCommon<T>>::set_scoped_collection_property(748						collection_id,749						PropertyScope::Rmrk,750						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,751					)?;752				}753			}754755			Self::deposit_event(Event::PropertySet {756				collection_id: rmrk_collection_id,757				maybe_nft_id,758				key,759				value,760			});761762			Ok(())763		}764765		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]766		#[transactional]767		pub fn set_priority(768			origin: OriginFor<T>,769			rmrk_collection_id: RmrkCollectionId,770			rmrk_nft_id: RmrkNftId,771			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,772		) -> DispatchResult {773			let sender = ensure_signed(origin)?;774			let sender = T::CrossAccountId::from_sub(sender);775776			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;777			let nft_id = rmrk_nft_id.into();778			let budget = budget::Value::new(NESTING_BUDGET);779780			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;781			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;782783			<PalletNft<T>>::set_scoped_token_property(784				collection_id,785				nft_id,786				PropertyScope::Rmrk,787				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,788			)?;789790			Self::deposit_event(Event::<T>::PrioritySet {791				collection_id: rmrk_collection_id,792				nft_id: rmrk_nft_id,793			});794795			Ok(())796		}797798		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]799		#[transactional]800		pub fn add_basic_resource(801			origin: OriginFor<T>,802			collection_id: RmrkCollectionId,803			nft_id: RmrkNftId,804			resource: RmrkBasicResource,805		) -> DispatchResult {806			let sender = ensure_signed(origin.clone())?;807808			let resource_id = Self::resource_add(809				sender,810				Self::unique_collection_id(collection_id)?,811				nft_id.into(),812				[813					Self::rmrk_property(TokenType, &NftType::Resource)?,814					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,815					Self::rmrk_property(Src, &resource.src)?,816					Self::rmrk_property(Metadata, &resource.metadata)?,817					Self::rmrk_property(License, &resource.license)?,818					Self::rmrk_property(Thumb, &resource.thumb)?,819				]820				.into_iter(),821			)?;822823			Self::deposit_event(Event::ResourceAdded {824				nft_id,825				resource_id,826			});827			Ok(())828		}829830		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]831		#[transactional]832		pub fn add_composable_resource(833			origin: OriginFor<T>,834			collection_id: RmrkCollectionId,835			nft_id: RmrkNftId,836			_resource_id: RmrkBoundedResource,837			resource: RmrkComposableResource,838		) -> DispatchResult {839			let sender = ensure_signed(origin.clone())?;840841			let resource_id = Self::resource_add(842				sender,843				Self::unique_collection_id(collection_id)?,844				nft_id.into(),845				[846					Self::rmrk_property(TokenType, &NftType::Resource)?,847					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,848					Self::rmrk_property(Parts, &resource.parts)?,849					Self::rmrk_property(Base, &resource.base)?,850					Self::rmrk_property(Src, &resource.src)?,851					Self::rmrk_property(Metadata, &resource.metadata)?,852					Self::rmrk_property(License, &resource.license)?,853					Self::rmrk_property(Thumb, &resource.thumb)?,854				]855				.into_iter(),856			)?;857858			Self::deposit_event(Event::ResourceAdded {859				nft_id,860				resource_id,861			});862			Ok(())863		}864865		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]866		#[transactional]867		pub fn add_slot_resource(868			origin: OriginFor<T>,869			collection_id: RmrkCollectionId,870			nft_id: RmrkNftId,871			resource: RmrkSlotResource,872		) -> DispatchResult {873			let sender = ensure_signed(origin.clone())?;874875			let resource_id = Self::resource_add(876				sender,877				Self::unique_collection_id(collection_id)?,878				nft_id.into(),879				[880					Self::rmrk_property(TokenType, &NftType::Resource)?,881					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,882					Self::rmrk_property(Base, &resource.base)?,883					Self::rmrk_property(Src, &resource.src)?,884					Self::rmrk_property(Metadata, &resource.metadata)?,885					Self::rmrk_property(Slot, &resource.slot)?,886					Self::rmrk_property(License, &resource.license)?,887					Self::rmrk_property(Thumb, &resource.thumb)?,888				]889				.into_iter(),890			)?;891892			Self::deposit_event(Event::ResourceAdded {893				nft_id,894				resource_id,895			});896			Ok(())897		}898899		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]900		#[transactional]901		pub fn remove_resource(902			origin: OriginFor<T>,903			collection_id: RmrkCollectionId,904			nft_id: RmrkNftId,905			resource_id: RmrkResourceId,906		) -> DispatchResult {907			let sender = ensure_signed(origin.clone())?;908909			Self::resource_remove(910				sender,911				Self::unique_collection_id(collection_id)?,912				nft_id.into(),913				resource_id.into(),914			)?;915916			Self::deposit_event(Event::ResourceRemoval {917				nft_id,918				resource_id,919			});920			Ok(())921		}922	}923}924925impl<T: Config> Pallet<T> {926	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {927		let key = rmrk_key.to_key::<T>()?;928929		let scoped_key = PropertyScope::Rmrk930			.apply(key)931			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;932933		Ok(scoped_key)934	}935936	// todo think about renaming these937	pub fn rmrk_property<E: Encode>(938		rmrk_key: RmrkProperty,939		value: &E,940	) -> Result<Property, DispatchError> {941		let key = rmrk_key.to_key::<T>()?;942943		let value = value944			.encode()945			.try_into()946			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;947948		let property = Property { key, value };949950		Ok(property)951	}952953	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {954		vec.decode()955			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())956	}957958	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>959	where960		BoundedVec<u8, S>: TryFrom<Vec<u8>>,961	{962		vec.rebind()963			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())964	}965966	fn init_collection(967		sender: T::CrossAccountId,968		data: CreateCollectionData<T::AccountId>,969		properties: impl Iterator<Item = Property>,970	) -> Result<CollectionId, DispatchError> {971		let collection_id = <PalletNft<T>>::init_collection(sender, data);972973		if let Err(DispatchError::Arithmetic(_)) = &collection_id {974			return Err(<Error<T>>::NoAvailableCollectionId.into());975		}976977		<PalletCommon<T>>::set_scoped_collection_properties(978			collection_id?,979			PropertyScope::Rmrk,980			properties,981		)?;982983		collection_id984	}985986	pub fn create_nft(987		sender: &T::CrossAccountId,988		owner: &T::CrossAccountId,989		collection: &NonfungibleHandle<T>,990		properties: impl Iterator<Item = Property>,991	) -> Result<TokenId, DispatchError> {992		let data = CreateNftExData {993			properties: BoundedVec::default(),994			owner: owner.clone(),995		};996997		let budget = budget::Value::new(NESTING_BUDGET);998999		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;10001001		let nft_id = <PalletNft<T>>::current_token_id(collection.id);10021003		<PalletNft<T>>::set_scoped_token_properties(1004			collection.id,1005			nft_id,1006			PropertyScope::Rmrk,1007			properties,1008		)?;10091010		Ok(nft_id)1011	}10121013	fn destroy_nft(1014		sender: T::CrossAccountId,1015		collection_id: CollectionId,1016		token_id: TokenId,1017	) -> DispatchResult {1018		let collection =1019			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;10201021		let token_data =1022			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;10231024		let from = token_data.owner;10251026		let budget = budget::Value::new(NESTING_BUDGET);10271028		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)1029	}10301031	fn resource_add(1032		sender: T::AccountId,1033		collection_id: CollectionId,1034		token_id: TokenId,1035		resource_properties: impl Iterator<Item = Property>,1036	) -> Result<RmrkResourceId, DispatchError> {1037		let collection =1038			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1039		ensure!(collection.owner == sender, Error::<T>::NoPermission);10401041		let sender = T::CrossAccountId::from_sub(sender);1042		let budget = budget::Value::new(NESTING_BUDGET);10431044		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1045			.map_err(Self::map_unique_err_to_proxy)?;10461047		let pending = sender != nft_owner;10481049		let resource_collection_id: CollectionId =1050			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1051		let resource_collection =1052			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;10531054		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them10551056		let resource_id = Self::create_nft(1057			&sender,1058			&nft_owner,1059			&resource_collection,1060			resource_properties.chain(1061				[1062					Self::rmrk_property(PendingResourceAccept, &pending)?,1063					Self::rmrk_property(PendingResourceRemoval, &false)?,1064				]1065				.into_iter(),1066			),1067		)1068		.map_err(|err| match err {1069			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1070			err => Self::map_unique_err_to_proxy(err),1071		})?;10721073		Ok(resource_id.0)1074	}10751076	fn resource_remove(1077		sender: T::AccountId,1078		collection_id: CollectionId,1079		nft_id: TokenId,1080		resource_id: TokenId,1081	) -> DispatchResult {1082		let collection =1083			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1084		ensure!(collection.owner == sender, Error::<T>::NoPermission);10851086		let resource_collection_id: CollectionId =1087			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1088		let resource_collection =1089			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1090		ensure!(1091			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1092			Error::<T>::ResourceDoesntExist1093		);10941095		let budget = up_data_structs::budget::Value::new(10);1096		let topmost_owner =1097			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;10981099		let sender = T::CrossAccountId::from_sub(sender);1100		if topmost_owner == sender {1101			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1102				.map_err(Self::map_unique_err_to_proxy)?;1103		} else {1104			<PalletNft<T>>::set_scoped_token_property(1105				resource_collection_id,1106				resource_id,1107				PropertyScope::Rmrk,1108				Self::rmrk_property(PendingResourceRemoval, &true)?,1109			)?;1110		}11111112		Ok(())1113	}11141115	fn change_collection_owner(1116		collection_id: CollectionId,1117		collection_type: misc::CollectionType,1118		sender: T::AccountId,1119		new_owner: T::AccountId,1120	) -> DispatchResult {1121		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1122		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;11231124		let mut collection = collection.into_inner();11251126		collection.owner = new_owner;1127		collection.save()1128	}11291130	fn check_collection_owner(1131		collection: &NonfungibleHandle<T>,1132		account: &T::CrossAccountId,1133	) -> DispatchResult {1134		collection1135			.check_is_owner(account)1136			.map_err(Self::map_unique_err_to_proxy)1137	}11381139	pub fn last_collection_idx() -> RmrkCollectionId {1140		<CollectionIndex<T>>::get()1141	}11421143	pub fn unique_collection_id(1144		rmrk_collection_id: RmrkCollectionId,1145	) -> Result<CollectionId, DispatchError> {1146		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1147			.map_err(|_| <Error<T>>::CollectionUnknown.into())1148	}11491150	pub fn rmrk_collection_id(1151		unique_collection_id: CollectionId,1152	) -> Result<RmrkCollectionId, DispatchError> {1153		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1154			.map_err(|_| <Error<T>>::CollectionUnknown.into())1155	}11561157	pub fn get_nft_collection(1158		collection_id: CollectionId,1159	) -> Result<NonfungibleHandle<T>, DispatchError> {1160		let collection = <CollectionHandle<T>>::try_get(collection_id)1161			.map_err(|_| <Error<T>>::CollectionUnknown)?;11621163		match collection.mode {1164			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1165			_ => Err(<Error<T>>::CollectionUnknown.into()),1166		}1167	}11681169	pub fn collection_exists(collection_id: CollectionId) -> bool {1170		<CollectionHandle<T>>::try_get(collection_id).is_ok()1171	}11721173	pub fn get_collection_property(1174		collection_id: CollectionId,1175		key: RmrkProperty,1176	) -> Result<PropertyValue, DispatchError> {1177		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1178			.get(&Self::rmrk_property_key(key)?)1179			.ok_or(<Error<T>>::CollectionUnknown)?1180			.clone();11811182		Ok(collection_property)1183	}11841185	pub fn get_collection_property_decoded<V: Decode>(1186		collection_id: CollectionId,1187		key: RmrkProperty,1188	) -> Result<V, DispatchError> {1189		Self::decode_property(Self::get_collection_property(collection_id, key)?)1190	}11911192	pub fn get_collection_type(1193		collection_id: CollectionId,1194	) -> Result<misc::CollectionType, DispatchError> {1195		Self::get_collection_property_decoded(collection_id, CollectionType)1196			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1197	}11981199	pub fn ensure_collection_type(1200		collection_id: CollectionId,1201		collection_type: misc::CollectionType,1202	) -> DispatchResult {1203		let actual_type = Self::get_collection_type(collection_id)?;1204		ensure!(1205			actual_type == collection_type,1206			<CommonError<T>>::NoPermission1207		);12081209		Ok(())1210	}12111212	pub fn get_typed_nft_collection(1213		collection_id: CollectionId,1214		collection_type: misc::CollectionType,1215	) -> Result<NonfungibleHandle<T>, DispatchError> {1216		Self::ensure_collection_type(collection_id, collection_type)?;12171218		Self::get_nft_collection(collection_id)1219	}12201221	pub fn get_typed_nft_collection_mapped(1222		rmrk_collection_id: RmrkCollectionId,1223		collection_type: misc::CollectionType,1224	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1225		let unique_collection_id = match collection_type {1226			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1227			_ => rmrk_collection_id.into(),1228		};12291230		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;12311232		Ok((collection, unique_collection_id))1233	}12341235	pub fn get_nft_property(1236		collection_id: CollectionId,1237		nft_id: TokenId,1238		key: RmrkProperty,1239	) -> Result<PropertyValue, DispatchError> {1240		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1241			.get(&Self::rmrk_property_key(key)?)1242			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1243			.clone();12441245		Ok(nft_property)1246	}12471248	pub fn get_nft_property_decoded<V: Decode>(1249		collection_id: CollectionId,1250		nft_id: TokenId,1251		key: RmrkProperty,1252	) -> Result<V, DispatchError> {1253		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1254	}12551256	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1257		<TokenData<T>>::contains_key((collection_id, nft_id))1258	}12591260	pub fn get_nft_type(1261		collection_id: CollectionId,1262		token_id: TokenId,1263	) -> Result<NftType, DispatchError> {1264		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1265			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1266	}12671268	pub fn ensure_nft_type(1269		collection_id: CollectionId,1270		token_id: TokenId,1271		nft_type: NftType,1272	) -> DispatchResult {1273		let actual_type = Self::get_nft_type(collection_id, token_id)?;1274		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);12751276		Ok(())1277	}12781279	pub fn ensure_nft_owner(1280		collection_id: CollectionId,1281		token_id: TokenId,1282		possible_owner: &T::CrossAccountId,1283		nesting_budget: &dyn budget::Budget,1284	) -> DispatchResult {1285		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1286			possible_owner.clone(),1287			collection_id,1288			token_id,1289			None,1290			nesting_budget,1291		)1292		.map_err(Self::map_unique_err_to_proxy)?;12931294		ensure!(is_owned, <Error<T>>::NoPermission);12951296		Ok(())1297	}12981299	pub fn filter_user_properties<Key, Value, R, Mapper>(1300		collection_id: CollectionId,1301		token_id: Option<TokenId>,1302		filter_keys: Option<Vec<RmrkPropertyKey>>,1303		mapper: Mapper,1304	) -> Result<Vec<R>, DispatchError>1305	where1306		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1307		Value: Decode + Default,1308		Mapper: Fn(Key, Value) -> R,1309	{1310		filter_keys1311			.map(|keys| {1312				let properties = keys1313					.into_iter()1314					.filter_map(|key| {1315						let key: Key = key.try_into().ok()?;13161317						let value = match token_id {1318							Some(token_id) => Self::get_nft_property_decoded(1319								collection_id,1320								token_id,1321								UserProperty(key.as_ref()),1322							),1323							None => Self::get_collection_property_decoded(1324								collection_id,1325								UserProperty(key.as_ref()),1326							),1327						}1328						.ok()?;13291330						Some(mapper(key, value))1331					})1332					.collect();13331334				Ok(properties)1335			})1336			.unwrap_or_else(|| {1337				let properties =1338					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();13391340				Ok(properties)1341			})1342	}13431344	pub fn iterate_user_properties<Key, Value, R, Mapper>(1345		collection_id: CollectionId,1346		token_id: Option<TokenId>,1347		mapper: Mapper,1348	) -> Result<impl Iterator<Item = R>, DispatchError>1349	where1350		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1351		Value: Decode + Default,1352		Mapper: Fn(Key, Value) -> R,1353	{1354		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;13551356		let properties = match token_id {1357			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1358			None => <PalletCommon<T>>::collection_properties(collection_id),1359		};13601361		let properties = properties.into_iter().filter_map(move |(key, value)| {1362			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;13631364			let key: Key = key.to_vec().try_into().ok()?;1365			let value: Value = value.decode().ok()?;13661367			Some(mapper(key, value))1368		});13691370		Ok(properties)1371	}13721373	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1374		map_unique_err_to_proxy! {1375			match err {1376				CommonError::NoPermission => NoPermission,1377				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1378				CommonError::PublicMintingNotAllowed => NoPermission,1379				CommonError::TokenNotFound => NoAvailableNftId,1380				CommonError::ApprovedValueTooLow => NoPermission,1381				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1382				StructureError::TokenNotFound => NoAvailableNftId,1383				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1384			}1385		}1386	}1387}
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::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46	use super::*;47	use pallet_evm::account;4849	#[pallet::config]50	pub trait Config:51		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52	{53		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54	}5556	#[pallet::storage]57	#[pallet::getter(fn collection_index)]58	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960	#[pallet::storage]61	pub type UniqueCollectionId<T: Config> =62		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364	#[pallet::storage]65	pub type RmrkInernalCollectionId<T: Config> =66		StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768	#[pallet::pallet]69	#[pallet::generate_store(pub(super) trait Store)]70	pub struct Pallet<T>(_);7172	#[pallet::event]73	#[pallet::generate_deposit(pub(super) fn deposit_event)]74	pub enum Event<T: Config> {75		CollectionCreated {76			issuer: T::AccountId,77			collection_id: RmrkCollectionId,78		},79		CollectionDestroyed {80			issuer: T::AccountId,81			collection_id: RmrkCollectionId,82		},83		IssuerChanged {84			old_issuer: T::AccountId,85			new_issuer: T::AccountId,86			collection_id: RmrkCollectionId,87		},88		CollectionLocked {89			issuer: T::AccountId,90			collection_id: RmrkCollectionId,91		},92		NftMinted {93			owner: T::AccountId,94			collection_id: RmrkCollectionId,95			nft_id: RmrkNftId,96		},97		NFTBurned {98			owner: T::AccountId,99			nft_id: RmrkNftId,100		},101		NFTSent {102			sender: T::AccountId,103			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,104			collection_id: RmrkCollectionId,105			nft_id: RmrkNftId,106			approval_required: bool,107		},108		NFTAccepted {109			sender: T::AccountId,110			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,111			collection_id: RmrkCollectionId,112			nft_id: RmrkNftId,113		},114		NFTRejected {115			sender: T::AccountId,116			collection_id: RmrkCollectionId,117			nft_id: RmrkNftId,118		},119		PropertySet {120			collection_id: RmrkCollectionId,121			maybe_nft_id: Option<RmrkNftId>,122			key: RmrkKeyString,123			value: RmrkValueString,124		},125		ResourceAdded {126			nft_id: RmrkNftId,127			resource_id: RmrkResourceId,128		},129		ResourceRemoval {130			nft_id: RmrkNftId,131			resource_id: RmrkResourceId,132		},133		ResourceAccepted {134			nft_id: RmrkNftId,135			resource_id: RmrkResourceId,136		},137		ResourceRemovalAccepted {138			nft_id: RmrkNftId,139			resource_id: RmrkResourceId,140		},141		PrioritySet {142			collection_id: RmrkCollectionId,143			nft_id: RmrkNftId,144		},145	}146147	#[pallet::error]148	pub enum Error<T> {149		/* Unique-specific events */150		CorruptedCollectionType,151		NftTypeEncodeError,152		RmrkPropertyKeyIsTooLong,153		RmrkPropertyValueIsTooLong,154155		/* RMRK compatible events */156		CollectionNotEmpty,157		NoAvailableCollectionId,158		NoAvailableNftId,159		CollectionUnknown,160		NoPermission,161		NonTransferable,162		CollectionFullOrLocked,163		ResourceDoesntExist,164		CannotSendToDescendentOrSelf,165		CannotAcceptNonOwnedNft,166		CannotRejectNonOwnedNft,167		ResourceNotPending,168	}169170	#[pallet::call]171	impl<T: Config> Pallet<T> {172		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]173		#[transactional]174		pub fn create_collection(175			origin: OriginFor<T>,176			metadata: RmrkString,177			max: Option<u32>,178			symbol: RmrkCollectionSymbol,179		) -> DispatchResult {180			let sender = ensure_signed(origin)?;181182			let limits = CollectionLimits {183				owner_can_transfer: Some(false),184				token_limit: max,185				..Default::default()186			};187188			let data = CreateCollectionData {189				limits: Some(limits),190				token_prefix: symbol191					.into_inner()192					.try_into()193					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,194				permissions: Some(CollectionPermissions {195					nesting: Some(NestingRule::Owner),196					..Default::default()197				}),198				..Default::default()199			};200201			let unique_collection_id = Self::init_collection(202				T::CrossAccountId::from_sub(sender.clone()),203				data,204				[205					Self::rmrk_property(Metadata, &metadata)?,206					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,207				]208				.into_iter(),209			)?;210			let rmrk_collection_id = <CollectionIndex<T>>::get();211212			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);213			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);214215			<CollectionIndex<T>>::mutate(|n| *n += 1);216217			Self::deposit_event(Event::CollectionCreated {218				issuer: sender,219				collection_id: rmrk_collection_id,220			});221222			Ok(())223		}224225		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]226		#[transactional]227		pub fn destroy_collection(228			origin: OriginFor<T>,229			collection_id: RmrkCollectionId,230		) -> DispatchResult {231			let sender = ensure_signed(origin)?;232			let cross_sender = T::CrossAccountId::from_sub(sender.clone());233234			let collection = Self::get_typed_nft_collection(235				Self::unique_collection_id(collection_id)?,236				misc::CollectionType::Regular,237			)?;238			collection.check_is_external()?;239240			<PalletNft<T>>::destroy_collection(collection, &cross_sender)241				.map_err(Self::map_unique_err_to_proxy)?;242243			Self::deposit_event(Event::CollectionDestroyed {244				issuer: sender,245				collection_id,246			});247248			Ok(())249		}250251		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]252		#[transactional]253		pub fn change_collection_issuer(254			origin: OriginFor<T>,255			collection_id: RmrkCollectionId,256			new_issuer: <T::Lookup as StaticLookup>::Source,257		) -> DispatchResult {258			let sender = ensure_signed(origin)?;259260			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;261			collection.check_is_external()?;262263			let new_issuer = T::Lookup::lookup(new_issuer)?;264265			Self::change_collection_owner(266				Self::unique_collection_id(collection_id)?,267				misc::CollectionType::Regular,268				sender.clone(),269				new_issuer.clone(),270			)?;271272			Self::deposit_event(Event::IssuerChanged {273				old_issuer: sender,274				new_issuer,275				collection_id,276			});277278			Ok(())279		}280281		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]282		#[transactional]283		pub fn lock_collection(284			origin: OriginFor<T>,285			collection_id: RmrkCollectionId,286		) -> DispatchResult {287			let sender = ensure_signed(origin)?;288			let cross_sender = T::CrossAccountId::from_sub(sender.clone());289290			let collection = Self::get_typed_nft_collection(291				Self::unique_collection_id(collection_id)?,292				misc::CollectionType::Regular,293			)?;294			collection.check_is_external()?;295296			Self::check_collection_owner(&collection, &cross_sender)?;297298			let token_count = collection.total_supply();299300			let mut collection = collection.into_inner();301			collection.limits.token_limit = Some(token_count);302			collection.save()?;303304			Self::deposit_event(Event::CollectionLocked {305				issuer: sender,306				collection_id,307			});308309			Ok(())310		}311312		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]313		#[transactional]314		pub fn mint_nft(315			origin: OriginFor<T>,316			owner: T::AccountId,317			collection_id: RmrkCollectionId,318			recipient: Option<T::AccountId>,319			royalty_amount: Option<Permill>,320			metadata: RmrkString,321			transferable: bool,322		) -> DispatchResult {323			let sender = ensure_signed(origin)?;324			let sender = T::CrossAccountId::from_sub(sender);325			let cross_owner = T::CrossAccountId::from_sub(owner.clone());326327			let collection = Self::get_typed_nft_collection(328				Self::unique_collection_id(collection_id)?,329				misc::CollectionType::Regular,330			)?;331			collection.check_is_external()?;332333			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {334				recipient: recipient.unwrap_or_else(|| owner.clone()),335				amount,336			});337338			let nft_id = Self::create_nft(339				&sender,340				&cross_owner,341				&collection,342				[343					Self::rmrk_property(TokenType, &NftType::Regular)?,344					Self::rmrk_property(Transferable, &transferable)?,345					Self::rmrk_property(PendingNftAccept, &false)?,346					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,347					Self::rmrk_property(Metadata, &metadata)?,348					Self::rmrk_property(Equipped, &false)?,349					Self::rmrk_property(350						ResourceCollection,351						&Self::init_collection(352							sender.clone(),353							CreateCollectionData {354								..Default::default()355							},356							[Self::rmrk_property(357								CollectionType,358								&misc::CollectionType::Resource,359							)?]360							.into_iter(),361						)?,362					)?, // todo possibly add limits to the collection if rmrk warrants them363					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,364				]365				.into_iter(),366			)367			.map_err(|err| match err {368				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),369				err => Self::map_unique_err_to_proxy(err),370			})?;371372			Self::deposit_event(Event::NftMinted {373				owner,374				collection_id,375				nft_id: nft_id.0,376			});377378			Ok(())379		}380381		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]382		#[transactional]383		pub fn burn_nft(384			origin: OriginFor<T>,385			collection_id: RmrkCollectionId,386			nft_id: RmrkNftId,387		) -> DispatchResult {388			let sender = ensure_signed(origin)?;389			let cross_sender = T::CrossAccountId::from_sub(sender.clone());390391			let collection = Self::get_typed_nft_collection(392				Self::unique_collection_id(collection_id)?,393				misc::CollectionType::Regular,394			)?;395			collection.check_is_external()?;396397			Self::destroy_nft(398				cross_sender,399				Self::unique_collection_id(collection_id)?,400				nft_id.into(),401			)402			.map_err(Self::map_unique_err_to_proxy)?;403404			Self::deposit_event(Event::NFTBurned {405				owner: sender,406				nft_id,407			});408409			Ok(())410		}411412		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]413		#[transactional]414		pub fn send(415			origin: OriginFor<T>,416			rmrk_collection_id: RmrkCollectionId,417			rmrk_nft_id: RmrkNftId,418			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,419		) -> DispatchResult {420			let sender = ensure_signed(origin.clone())?;421			let cross_sender = T::CrossAccountId::from_sub(sender.clone());422423			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;424			let nft_id = rmrk_nft_id.into();425426			let collection =427				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;428			collection.check_is_external()?;429430			let token_data =431				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;432433			let from = token_data.owner;434435			ensure!(436				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,437				<Error<T>>::NonTransferable438			);439440			ensure!(441				!Self::get_nft_property_decoded(442					collection_id,443					nft_id,444					RmrkProperty::PendingNftAccept445				)?,446				<Error<T>>::NoPermission447			);448449			let target_owner;450			let approval_required;451452			match new_owner {453				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {454					target_owner = T::CrossAccountId::from_sub(account_id.clone());455					approval_required = false;456				}457				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(458					target_collection_id,459					target_nft_id,460				) => {461					let target_collection_id = Self::unique_collection_id(target_collection_id)?;462463					let target_nft_budget = budget::Value::new(NESTING_BUDGET);464465					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(466						target_collection_id,467						target_nft_id.into(),468						Some((collection_id, nft_id)),469						&target_nft_budget,470					)471					.map_err(Self::map_unique_err_to_proxy)?;472473					approval_required = cross_sender != target_nft_owner;474475					if approval_required {476						target_owner = target_nft_owner;477478						<PalletNft<T>>::set_scoped_token_property(479							collection.id,480							nft_id,481							PropertyScope::Rmrk,482							Self::rmrk_property(PendingNftAccept, &approval_required)?,483						)?;484					} else {485						target_owner = T::CrossTokenAddressMapping::token_to_address(486							target_collection_id,487							target_nft_id.into(),488						);489					}490				}491			}492493			let src_nft_budget = budget::Value::new(NESTING_BUDGET);494495			<PalletNft<T>>::transfer_from(496				&collection,497				&cross_sender,498				&from,499				&target_owner,500				nft_id,501				&src_nft_budget,502			)503			.map_err(Self::map_unique_err_to_proxy)?;504505			Self::deposit_event(Event::NFTSent {506				sender,507				recipient: new_owner,508				collection_id: rmrk_collection_id,509				nft_id: rmrk_nft_id,510				approval_required,511			});512513			Ok(())514		}515516		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]517		#[transactional]518		pub fn accept_nft(519			origin: OriginFor<T>,520			rmrk_collection_id: RmrkCollectionId,521			rmrk_nft_id: RmrkNftId,522			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,523		) -> DispatchResult {524			let sender = ensure_signed(origin.clone())?;525			let cross_sender = T::CrossAccountId::from_sub(sender.clone());526527			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;528			let nft_id = rmrk_nft_id.into();529530			let collection =531				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;532			collection.check_is_external()?;533534			let new_cross_owner = match new_owner {535				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {536					T::CrossAccountId::from_sub(account_id.clone())537				}538				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(539					target_collection_id,540					target_nft_id,541				) => {542					let target_collection_id = Self::unique_collection_id(target_collection_id)?;543544					T::CrossTokenAddressMapping::token_to_address(545						target_collection_id,546						TokenId(target_nft_id),547					)548				}549			};550551			let budget = budget::Value::new(NESTING_BUDGET);552553			<PalletNft<T>>::transfer(554				&collection,555				&cross_sender,556				&new_cross_owner,557				nft_id,558				&budget,559			)560			.map_err(|err| {561				if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {562					<Error<T>>::CannotAcceptNonOwnedNft.into()563				} else {564					Self::map_unique_err_to_proxy(err)565				}566			})?;567568			<PalletNft<T>>::set_scoped_token_property(569				collection.id,570				nft_id,571				PropertyScope::Rmrk,572				Self::rmrk_property(PendingNftAccept, &false)?,573			)?;574575			Self::deposit_event(Event::NFTAccepted {576				sender,577				recipient: new_owner,578				collection_id: rmrk_collection_id,579				nft_id: rmrk_nft_id,580			});581582			Ok(())583		}584585		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]586		#[transactional]587		pub fn reject_nft(588			origin: OriginFor<T>,589			rmrk_collection_id: RmrkCollectionId,590			rmrk_nft_id: RmrkNftId,591		) -> DispatchResult {592			let sender = ensure_signed(origin)?;593			let cross_sender = T::CrossAccountId::from_sub(sender.clone());594595			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;596			let nft_id = rmrk_nft_id.into();597598			let collection =599				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;600			collection.check_is_external()?;601602			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {603				if err == <CommonError<T>>::NoPermission.into()604					|| err == <CommonError<T>>::ApprovedValueTooLow.into()605				{606					<Error<T>>::CannotRejectNonOwnedNft.into()607				} else {608					Self::map_unique_err_to_proxy(err)609				}610			})?;611612			Self::deposit_event(Event::NFTRejected {613				sender,614				collection_id: rmrk_collection_id,615				nft_id: rmrk_nft_id,616			});617618			Ok(())619		}620621		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]622		#[transactional]623		pub fn accept_resource(624			origin: OriginFor<T>,625			rmrk_collection_id: RmrkCollectionId,626			rmrk_nft_id: RmrkNftId,627			rmrk_resource_id: RmrkResourceId,628		) -> DispatchResult {629			let sender = ensure_signed(origin)?;630			let cross_sender = T::CrossAccountId::from_sub(sender);631632			let collection_id = Self::unique_collection_id(rmrk_collection_id)633				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;634			let collection =635				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;636			collection.check_is_external()?;637638			let nft_id = rmrk_nft_id.into();639			let resource_id = rmrk_resource_id.into();640641			let budget = budget::Value::new(NESTING_BUDGET);642643			let nft_owner =644				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)645					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;646647			let resource_collection_id: CollectionId =648				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)649					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;650651			let is_pending: bool = Self::get_nft_property_decoded(652				resource_collection_id,653				resource_id,654				PendingResourceAccept,655			)656			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;657658			ensure!(is_pending, <Error<T>>::ResourceNotPending);659660			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);661662			<PalletNft<T>>::set_scoped_token_property(663				resource_collection_id,664				rmrk_resource_id.into(),665				PropertyScope::Rmrk,666				Self::rmrk_property(PendingResourceAccept, &false)?,667			)?;668669			Self::deposit_event(Event::<T>::ResourceAccepted {670				nft_id: rmrk_nft_id,671				resource_id: rmrk_resource_id,672			});673674			Ok(())675		}676677		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]678		#[transactional]679		pub fn accept_resource_removal(680			origin: OriginFor<T>,681			rmrk_collection_id: RmrkCollectionId,682			rmrk_nft_id: RmrkNftId,683			rmrk_resource_id: RmrkResourceId,684		) -> DispatchResult {685			let sender = ensure_signed(origin)?;686			let cross_sender = T::CrossAccountId::from_sub(sender);687688			let collection_id = Self::unique_collection_id(rmrk_collection_id)689				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;690			let collection =691				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;692			collection.check_is_external()?;693694			let nft_id = rmrk_nft_id.into();695			let resource_id = rmrk_resource_id.into();696697			let budget = budget::Value::new(NESTING_BUDGET);698699			let nft_owner =700				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)701					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;702703			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);704705			let resource_collection_id: CollectionId =706				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)707					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;708709			let is_pending: bool = Self::get_nft_property_decoded(710				resource_collection_id,711				resource_id,712				PendingResourceRemoval,713			)714			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;715716			ensure!(is_pending, <Error<T>>::ResourceNotPending);717718			let resource_collection = Self::get_typed_nft_collection(719				resource_collection_id,720				misc::CollectionType::Resource,721			)?;722723			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())724				.map_err(Self::map_unique_err_to_proxy)?;725726			Self::deposit_event(Event::<T>::ResourceRemovalAccepted {727				nft_id: rmrk_nft_id,728				resource_id: rmrk_resource_id,729			});730731			Ok(())732		}733734		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]735		#[transactional]736		pub fn set_property(737			origin: OriginFor<T>,738			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,739			maybe_nft_id: Option<RmrkNftId>,740			key: RmrkKeyString,741			value: RmrkValueString,742		) -> DispatchResult {743			let sender = ensure_signed(origin)?;744			let sender = T::CrossAccountId::from_sub(sender);745746			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;747			let collection =748				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;749			collection.check_is_external()?;750751			let budget = budget::Value::new(NESTING_BUDGET);752753			match maybe_nft_id {754				Some(nft_id) => {755					let token_id: TokenId = nft_id.into();756757					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;758					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;759760					<PalletNft<T>>::set_scoped_token_property(761						collection_id,762						token_id,763						PropertyScope::Rmrk,764						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,765					)?;766				}767				None => {768					let collection = Self::get_typed_nft_collection(769						collection_id,770						misc::CollectionType::Regular,771					)?;772773					Self::check_collection_owner(&collection, &sender)?;774775					<PalletCommon<T>>::set_scoped_collection_property(776						collection_id,777						PropertyScope::Rmrk,778						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,779					)?;780				}781			}782783			Self::deposit_event(Event::PropertySet {784				collection_id: rmrk_collection_id,785				maybe_nft_id,786				key,787				value,788			});789790			Ok(())791		}792793		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]794		#[transactional]795		pub fn set_priority(796			origin: OriginFor<T>,797			rmrk_collection_id: RmrkCollectionId,798			rmrk_nft_id: RmrkNftId,799			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,800		) -> DispatchResult {801			let sender = ensure_signed(origin)?;802			let sender = T::CrossAccountId::from_sub(sender);803804			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;805			let nft_id = rmrk_nft_id.into();806807			let collection =808				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;809			collection.check_is_external()?;810811			let budget = budget::Value::new(NESTING_BUDGET);812813			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;814			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;815816			<PalletNft<T>>::set_scoped_token_property(817				collection_id,818				nft_id,819				PropertyScope::Rmrk,820				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,821			)?;822823			Self::deposit_event(Event::<T>::PrioritySet {824				collection_id: rmrk_collection_id,825				nft_id: rmrk_nft_id,826			});827828			Ok(())829		}830831		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]832		#[transactional]833		pub fn add_basic_resource(834			origin: OriginFor<T>,835			rmrk_collection_id: RmrkCollectionId,836			nft_id: RmrkNftId,837			resource: RmrkBasicResource,838		) -> DispatchResult {839			let sender = ensure_signed(origin.clone())?;840841			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;842			let collection =843				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;844			collection.check_is_external()?;845846			let resource_id = Self::resource_add(847				sender,848				collection_id,849				nft_id.into(),850				[851					Self::rmrk_property(TokenType, &NftType::Resource)?,852					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,853					Self::rmrk_property(Src, &resource.src)?,854					Self::rmrk_property(Metadata, &resource.metadata)?,855					Self::rmrk_property(License, &resource.license)?,856					Self::rmrk_property(Thumb, &resource.thumb)?,857				]858				.into_iter(),859			)?;860861			Self::deposit_event(Event::ResourceAdded {862				nft_id,863				resource_id,864			});865			Ok(())866		}867868		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]869		#[transactional]870		pub fn add_composable_resource(871			origin: OriginFor<T>,872			rmrk_collection_id: RmrkCollectionId,873			nft_id: RmrkNftId,874			_resource_id: RmrkBoundedResource,875			resource: RmrkComposableResource,876		) -> DispatchResult {877			let sender = ensure_signed(origin.clone())?;878879			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;880			let collection =881				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;882			collection.check_is_external()?;883884			let resource_id = Self::resource_add(885				sender,886				collection_id,887				nft_id.into(),888				[889					Self::rmrk_property(TokenType, &NftType::Resource)?,890					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,891					Self::rmrk_property(Parts, &resource.parts)?,892					Self::rmrk_property(Base, &resource.base)?,893					Self::rmrk_property(Src, &resource.src)?,894					Self::rmrk_property(Metadata, &resource.metadata)?,895					Self::rmrk_property(License, &resource.license)?,896					Self::rmrk_property(Thumb, &resource.thumb)?,897				]898				.into_iter(),899			)?;900901			Self::deposit_event(Event::ResourceAdded {902				nft_id,903				resource_id,904			});905			Ok(())906		}907908		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]909		#[transactional]910		pub fn add_slot_resource(911			origin: OriginFor<T>,912			rmrk_collection_id: RmrkCollectionId,913			nft_id: RmrkNftId,914			resource: RmrkSlotResource,915		) -> DispatchResult {916			let sender = ensure_signed(origin.clone())?;917918			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;919			let collection =920				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;921			collection.check_is_external()?;922923			let resource_id = Self::resource_add(924				sender,925				collection_id,926				nft_id.into(),927				[928					Self::rmrk_property(TokenType, &NftType::Resource)?,929					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,930					Self::rmrk_property(Base, &resource.base)?,931					Self::rmrk_property(Src, &resource.src)?,932					Self::rmrk_property(Metadata, &resource.metadata)?,933					Self::rmrk_property(Slot, &resource.slot)?,934					Self::rmrk_property(License, &resource.license)?,935					Self::rmrk_property(Thumb, &resource.thumb)?,936				]937				.into_iter(),938			)?;939940			Self::deposit_event(Event::ResourceAdded {941				nft_id,942				resource_id,943			});944			Ok(())945		}946947		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]948		#[transactional]949		pub fn remove_resource(950			origin: OriginFor<T>,951			rmrk_collection_id: RmrkCollectionId,952			nft_id: RmrkNftId,953			resource_id: RmrkResourceId,954		) -> DispatchResult {955			let sender = ensure_signed(origin.clone())?;956957			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;958			let collection =959				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;960			collection.check_is_external()?;961962			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;963964			Self::deposit_event(Event::ResourceRemoval {965				nft_id,966				resource_id,967			});968			Ok(())969		}970	}971}972973impl<T: Config> Pallet<T> {974	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {975		let key = rmrk_key.to_key::<T>()?;976977		let scoped_key = PropertyScope::Rmrk978			.apply(key)979			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;980981		Ok(scoped_key)982	}983984	// todo think about renaming these985	pub fn rmrk_property<E: Encode>(986		rmrk_key: RmrkProperty,987		value: &E,988	) -> Result<Property, DispatchError> {989		let key = rmrk_key.to_key::<T>()?;990991		let value = value992			.encode()993			.try_into()994			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;995996		let property = Property { key, value };997998		Ok(property)999	}10001001	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1002		vec.decode()1003			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1004	}10051006	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1007	where1008		BoundedVec<u8, S>: TryFrom<Vec<u8>>,1009	{1010		vec.rebind()1011			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1012	}10131014	fn init_collection(1015		sender: T::CrossAccountId,1016		data: CreateCollectionData<T::AccountId>,1017		properties: impl Iterator<Item = Property>,1018	) -> Result<CollectionId, DispatchError> {1019		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10201021		if let Err(DispatchError::Arithmetic(_)) = &collection_id {1022			return Err(<Error<T>>::NoAvailableCollectionId.into());1023		}10241025		<PalletCommon<T>>::set_scoped_collection_properties(1026			collection_id?,1027			PropertyScope::Rmrk,1028			properties,1029		)?;10301031		collection_id1032	}10331034	pub fn create_nft(1035		sender: &T::CrossAccountId,1036		owner: &T::CrossAccountId,1037		collection: &NonfungibleHandle<T>,1038		properties: impl Iterator<Item = Property>,1039	) -> Result<TokenId, DispatchError> {1040		let data = CreateNftExData {1041			properties: BoundedVec::default(),1042			owner: owner.clone(),1043		};10441045		let budget = budget::Value::new(NESTING_BUDGET);10461047		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;10481049		let nft_id = <PalletNft<T>>::current_token_id(collection.id);10501051		<PalletNft<T>>::set_scoped_token_properties(1052			collection.id,1053			nft_id,1054			PropertyScope::Rmrk,1055			properties,1056		)?;10571058		Ok(nft_id)1059	}10601061	fn destroy_nft(1062		sender: T::CrossAccountId,1063		collection_id: CollectionId,1064		token_id: TokenId,1065	) -> DispatchResult {1066		let collection =1067			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;10681069		let token_data =1070			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;10711072		let from = token_data.owner;10731074		let budget = budget::Value::new(NESTING_BUDGET);10751076		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)1077	}10781079	fn resource_add(1080		sender: T::AccountId,1081		collection_id: CollectionId,1082		token_id: TokenId,1083		resource_properties: impl Iterator<Item = Property>,1084	) -> Result<RmrkResourceId, DispatchError> {1085		let collection =1086			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1087		ensure!(collection.owner == sender, Error::<T>::NoPermission);10881089		let sender = T::CrossAccountId::from_sub(sender);1090		let budget = budget::Value::new(NESTING_BUDGET);10911092		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1093			.map_err(Self::map_unique_err_to_proxy)?;10941095		let pending = sender != nft_owner;10961097		let resource_collection_id: CollectionId =1098			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;1099		let resource_collection =1100			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;11011102		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them11031104		let resource_id = Self::create_nft(1105			&sender,1106			&nft_owner,1107			&resource_collection,1108			resource_properties.chain(1109				[1110					Self::rmrk_property(PendingResourceAccept, &pending)?,1111					Self::rmrk_property(PendingResourceRemoval, &false)?,1112				]1113				.into_iter(),1114			),1115		)1116		.map_err(|err| match err {1117			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1118			err => Self::map_unique_err_to_proxy(err),1119		})?;11201121		Ok(resource_id.0)1122	}11231124	fn resource_remove(1125		sender: T::AccountId,1126		collection_id: CollectionId,1127		nft_id: TokenId,1128		resource_id: TokenId,1129	) -> DispatchResult {1130		let collection =1131			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1132		ensure!(collection.owner == sender, Error::<T>::NoPermission);11331134		let resource_collection_id: CollectionId =1135			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1136		let resource_collection =1137			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1138		ensure!(1139			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1140			Error::<T>::ResourceDoesntExist1141		);11421143		let budget = up_data_structs::budget::Value::new(10);1144		let topmost_owner =1145			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;11461147		let sender = T::CrossAccountId::from_sub(sender);1148		if topmost_owner == sender {1149			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1150				.map_err(Self::map_unique_err_to_proxy)?;1151		} else {1152			<PalletNft<T>>::set_scoped_token_property(1153				resource_collection_id,1154				resource_id,1155				PropertyScope::Rmrk,1156				Self::rmrk_property(PendingResourceRemoval, &true)?,1157			)?;1158		}11591160		Ok(())1161	}11621163	fn change_collection_owner(1164		collection_id: CollectionId,1165		collection_type: misc::CollectionType,1166		sender: T::AccountId,1167		new_owner: T::AccountId,1168	) -> DispatchResult {1169		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1170		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;11711172		let mut collection = collection.into_inner();11731174		collection.owner = new_owner;1175		collection.save()1176	}11771178	fn check_collection_owner(1179		collection: &NonfungibleHandle<T>,1180		account: &T::CrossAccountId,1181	) -> DispatchResult {1182		collection1183			.check_is_owner(account)1184			.map_err(Self::map_unique_err_to_proxy)1185	}11861187	pub fn last_collection_idx() -> RmrkCollectionId {1188		<CollectionIndex<T>>::get()1189	}11901191	pub fn unique_collection_id(1192		rmrk_collection_id: RmrkCollectionId,1193	) -> Result<CollectionId, DispatchError> {1194		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1195			.map_err(|_| <Error<T>>::CollectionUnknown.into())1196	}11971198	pub fn rmrk_collection_id(1199		unique_collection_id: CollectionId,1200	) -> Result<RmrkCollectionId, DispatchError> {1201		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1202			.map_err(|_| <Error<T>>::CollectionUnknown.into())1203	}12041205	pub fn get_nft_collection(1206		collection_id: CollectionId,1207	) -> Result<NonfungibleHandle<T>, DispatchError> {1208		let collection = <CollectionHandle<T>>::try_get(collection_id)1209			.map_err(|_| <Error<T>>::CollectionUnknown)?;12101211		match collection.mode {1212			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1213			_ => Err(<Error<T>>::CollectionUnknown.into()),1214		}1215	}12161217	pub fn collection_exists(collection_id: CollectionId) -> bool {1218		<CollectionHandle<T>>::try_get(collection_id).is_ok()1219	}12201221	pub fn get_collection_property(1222		collection_id: CollectionId,1223		key: RmrkProperty,1224	) -> Result<PropertyValue, DispatchError> {1225		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1226			.get(&Self::rmrk_property_key(key)?)1227			.ok_or(<Error<T>>::CollectionUnknown)?1228			.clone();12291230		Ok(collection_property)1231	}12321233	pub fn get_collection_property_decoded<V: Decode>(1234		collection_id: CollectionId,1235		key: RmrkProperty,1236	) -> Result<V, DispatchError> {1237		Self::decode_property(Self::get_collection_property(collection_id, key)?)1238	}12391240	pub fn get_collection_type(1241		collection_id: CollectionId,1242	) -> Result<misc::CollectionType, DispatchError> {1243		Self::get_collection_property_decoded(collection_id, CollectionType)1244			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1245	}12461247	pub fn ensure_collection_type(1248		collection_id: CollectionId,1249		collection_type: misc::CollectionType,1250	) -> DispatchResult {1251		let actual_type = Self::get_collection_type(collection_id)?;1252		ensure!(1253			actual_type == collection_type,1254			<CommonError<T>>::NoPermission1255		);12561257		Ok(())1258	}12591260	pub fn get_typed_nft_collection(1261		collection_id: CollectionId,1262		collection_type: misc::CollectionType,1263	) -> Result<NonfungibleHandle<T>, DispatchError> {1264		Self::ensure_collection_type(collection_id, collection_type)?;12651266		Self::get_nft_collection(collection_id)1267	}12681269	pub fn get_typed_nft_collection_mapped(1270		rmrk_collection_id: RmrkCollectionId,1271		collection_type: misc::CollectionType,1272	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1273		let unique_collection_id = match collection_type {1274			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1275			_ => rmrk_collection_id.into(),1276		};12771278		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;12791280		Ok((collection, unique_collection_id))1281	}12821283	pub fn get_nft_property(1284		collection_id: CollectionId,1285		nft_id: TokenId,1286		key: RmrkProperty,1287	) -> Result<PropertyValue, DispatchError> {1288		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1289			.get(&Self::rmrk_property_key(key)?)1290			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1291			.clone();12921293		Ok(nft_property)1294	}12951296	pub fn get_nft_property_decoded<V: Decode>(1297		collection_id: CollectionId,1298		nft_id: TokenId,1299		key: RmrkProperty,1300	) -> Result<V, DispatchError> {1301		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1302	}13031304	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1305		<TokenData<T>>::contains_key((collection_id, nft_id))1306	}13071308	pub fn get_nft_type(1309		collection_id: CollectionId,1310		token_id: TokenId,1311	) -> Result<NftType, DispatchError> {1312		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1313			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1314	}13151316	pub fn ensure_nft_type(1317		collection_id: CollectionId,1318		token_id: TokenId,1319		nft_type: NftType,1320	) -> DispatchResult {1321		let actual_type = Self::get_nft_type(collection_id, token_id)?;1322		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);13231324		Ok(())1325	}13261327	pub fn ensure_nft_owner(1328		collection_id: CollectionId,1329		token_id: TokenId,1330		possible_owner: &T::CrossAccountId,1331		nesting_budget: &dyn budget::Budget,1332	) -> DispatchResult {1333		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1334			possible_owner.clone(),1335			collection_id,1336			token_id,1337			None,1338			nesting_budget,1339		)1340		.map_err(Self::map_unique_err_to_proxy)?;13411342		ensure!(is_owned, <Error<T>>::NoPermission);13431344		Ok(())1345	}13461347	pub fn filter_user_properties<Key, Value, R, Mapper>(1348		collection_id: CollectionId,1349		token_id: Option<TokenId>,1350		filter_keys: Option<Vec<RmrkPropertyKey>>,1351		mapper: Mapper,1352	) -> Result<Vec<R>, DispatchError>1353	where1354		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1355		Value: Decode + Default,1356		Mapper: Fn(Key, Value) -> R,1357	{1358		filter_keys1359			.map(|keys| {1360				let properties = keys1361					.into_iter()1362					.filter_map(|key| {1363						let key: Key = key.try_into().ok()?;13641365						let value = match token_id {1366							Some(token_id) => Self::get_nft_property_decoded(1367								collection_id,1368								token_id,1369								UserProperty(key.as_ref()),1370							),1371							None => Self::get_collection_property_decoded(1372								collection_id,1373								UserProperty(key.as_ref()),1374							),1375						}1376						.ok()?;13771378						Some(mapper(key, value))1379					})1380					.collect();13811382				Ok(properties)1383			})1384			.unwrap_or_else(|| {1385				let properties =1386					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();13871388				Ok(properties)1389			})1390	}13911392	pub fn iterate_user_properties<Key, Value, R, Mapper>(1393		collection_id: CollectionId,1394		token_id: Option<TokenId>,1395		mapper: Mapper,1396	) -> Result<impl Iterator<Item = R>, DispatchError>1397	where1398		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1399		Value: Decode + Default,1400		Mapper: Fn(Key, Value) -> R,1401	{1402		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;14031404		let properties = match token_id {1405			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1406			None => <PalletCommon<T>>::collection_properties(collection_id),1407		};14081409		let properties = properties.into_iter().filter_map(move |(key, value)| {1410			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;14111412			let key: Key = key.to_vec().try_into().ok()?;1413			let value: Value = value.decode().ok()?;14141415			Some(mapper(key, value))1416		});14171418		Ok(properties)1419	}14201421	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1422		map_unique_err_to_proxy! {1423			match err {1424				CommonError::NoPermission => NoPermission,1425				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1426				CommonError::PublicMintingNotAllowed => NoPermission,1427				CommonError::TokenNotFound => NoAvailableNftId,1428				CommonError::ApprovedValueTooLow => NoPermission,1429				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1430				StructureError::TokenNotFound => NoAvailableNftId,1431				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1432			}1433		}1434	}1435}
modifiedpallets/proxy-rmrk-equip/src/lib.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-equip/src/lib.rs
+++ b/pallets/proxy-rmrk-equip/src/lib.rs
@@ -94,7 +94,8 @@
 				..Default::default()
 			};
 
-			let collection_id_res = <PalletNft<T>>::init_collection(cross_sender.clone(), data);
+			let collection_id_res =
+				<PalletNft<T>>::init_collection(cross_sender.clone(), data, true);
 
 			if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {
 				return Err(<Error<T>>::NoAvailableBaseId.into());
@@ -155,6 +156,7 @@
 				misc::CollectionType::Base,
 			)
 			.map_err(|_| <Error<T>>::BaseDoesntExist)?;
+			collection.check_is_external()?;
 
 			if theme.name.as_slice() == b"default" {
 				<BaseHasDefaultTheme<T>>::insert(collection_id, true);
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -200,7 +200,7 @@
 		owner: T::CrossAccountId,
 		data: CreateCollectionData<T::AccountId>,
 	) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(owner, data)
+		<PalletCommon<T>>::init_collection(owner, data, false)
 	}
 	pub fn destroy_collection(
 		collection: RefungibleHandle<T>,
@@ -234,7 +234,6 @@
 	}
 
 	pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {
-		collection.check_is_mutable()?;
 		let burnt = <TokensBurnt<T>>::get(collection.id)
 			.checked_add(1)
 			.ok_or(ArithmeticError::Overflow)?;
@@ -254,7 +253,6 @@
 		token: TokenId,
 		amount: u128,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
 		let total_supply = <TotalSupply<T>>::get((collection.id, token))
 			.checked_sub(amount)
 			.ok_or(<CommonError<T>>::TokenValueTooLow)?;
@@ -327,7 +325,6 @@
 		amount: u128,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
 		ensure!(
 			collection.limits.transfers_enabled(),
 			<CommonError<T>>::TransferNotAllowed
@@ -576,7 +573,6 @@
 		token: TokenId,
 		amount: u128,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
 		if collection.permissions.access() == AccessMode::AllowList {
 			collection.check_allowlist(sender)?;
 			collection.check_allowlist(spender)?;
modifiedpallets/unique/src/eth/mod.rsdiffbeforeafterboth
--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -92,8 +92,9 @@
 			..Default::default()
 		};
 
-		let collection_id = <pallet_nonfungible::Pallet<T>>::init_collection(caller.clone(), data)
-			.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
+		let collection_id =
+			<pallet_nonfungible::Pallet<T>>::init_collection(caller.clone(), data, false)
+				.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
 
 		let address = pallet_common::eth::collection_id_to_address(collection_id);
 		Ok(address)
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -304,7 +304,7 @@
 		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
-			collection.check_is_mutable()?;
+			collection.check_is_internal()?;
 
 			// =========
 
@@ -339,6 +339,7 @@
 
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			collection.check_is_internal()?;
 
 			<PalletCommon<T>>::toggle_allowlist(
 				&collection,
@@ -373,6 +374,7 @@
 
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			collection.check_is_internal()?;
 
 			<PalletCommon<T>>::toggle_allowlist(
 				&collection,
@@ -407,7 +409,7 @@
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
-			target_collection.check_is_mutable()?;
+			target_collection.check_is_internal()?;
 			target_collection.check_is_owner(&sender)?;
 
 			target_collection.owner = new_owner.clone();
@@ -437,6 +439,7 @@
 		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			collection.check_is_internal()?;
 
 			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(
 				collection_id,
@@ -463,6 +466,7 @@
 		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			collection.check_is_internal()?;
 
 			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(
 				collection_id,
@@ -488,6 +492,7 @@
 
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			target_collection.check_is_owner(&sender)?;
+			target_collection.check_is_internal()?;
 
 			target_collection.set_sponsor(new_sponsor.clone())?;
 
@@ -512,6 +517,7 @@
 			let sender = ensure_signed(origin)?;
 
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			target_collection.check_is_internal()?;
 			ensure!(
 				target_collection.confirm_sponsorship(&sender)?,
 				Error::<T>::ConfirmUnsetSponsorFail
@@ -540,6 +546,7 @@
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			target_collection.check_is_internal()?;
 			target_collection.check_is_owner(&sender)?;
 
 			target_collection.sponsorship = SponsorshipState::Disabled;
@@ -704,6 +711,7 @@
 		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			target_collection.check_is_internal()?;
 			target_collection.check_is_owner(&sender)?;
 
 			// =========
@@ -858,6 +866,7 @@
 		) -> DispatchResult {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			target_collection.check_is_internal()?;
 			target_collection.check_is_owner(&sender)?;
 			let old_limit = &target_collection.limits;
 
@@ -879,6 +888,7 @@
 		) -> DispatchResult {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			target_collection.check_is_internal()?;
 			target_collection.check_is_owner(&sender)?;
 			let old_limit = &target_collection.permissions;
 
modifiedruntime/common/src/dispatch.rsdiffbeforeafterboth
--- a/runtime/common/src/dispatch.rs
+++ b/runtime/common/src/dispatch.rs
@@ -35,7 +35,7 @@
 		data: CreateCollectionData<T::AccountId>,
 	) -> DispatchResult {
 		let _id = match data.mode {
-			CollectionMode::NFT => <PalletNonfungible<T>>::init_collection(sender, data)?,
+			CollectionMode::NFT => <PalletNonfungible<T>>::init_collection(sender, data, false)?,
 			CollectionMode::Fungible(decimal_points) => {
 				// check params
 				ensure!(