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

difftreelog

feat(rmrk) add accept_resource_removal

Daniel Shiposha2022-06-05parent: #0913f40.patch.diff
in: master

1 file changed

modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
before · pallets/proxy-rmrk-core/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;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		PropertySet {102			collection_id: RmrkCollectionId,103			maybe_nft_id: Option<RmrkNftId>,104			key: RmrkKeyString,105			value: RmrkValueString,106		},107		ResourceAdded {108			nft_id: RmrkNftId,109			resource_id: RmrkResourceId,110		},111		ResourceRemoval {112			nft_id: RmrkNftId,113			resource_id: RmrkResourceId,114		},115	}116117	#[pallet::error]118	pub enum Error<T> {119		/* Unique-specific events */120		CorruptedCollectionType,121		NftTypeEncodeError,122		RmrkPropertyKeyIsTooLong,123		RmrkPropertyValueIsTooLong,124125		/* RMRK compatible events */126		CollectionNotEmpty,127		NoAvailableCollectionId,128		NoAvailableNftId,129		CollectionUnknown,130		NoPermission,131		NonTransferable,132		CollectionFullOrLocked,133		ResourceDoesntExist,134		CannotSendToDescendentOrSelf,135		CannotAcceptNonOwnedNft,136		CannotRejectNonOwnedNft,137		ResourceNotPending,138	}139140	#[pallet::call]141	impl<T: Config> Pallet<T> {142		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]143		#[transactional]144		pub fn create_collection(145			origin: OriginFor<T>,146			metadata: RmrkString,147			max: Option<u32>,148			symbol: RmrkCollectionSymbol,149		) -> DispatchResult {150			let sender = ensure_signed(origin)?;151152			let limits = CollectionLimits {153				owner_can_transfer: Some(false),154				token_limit: max,155				..Default::default()156			};157158			let data = CreateCollectionData {159				limits: Some(limits),160				token_prefix: symbol161					.into_inner()162					.try_into()163					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,164				permissions: Some(CollectionPermissions {165					nesting: Some(NestingRule::Owner),166					..Default::default()167				}),168				..Default::default()169			};170171			let unique_collection_id = Self::init_collection(172				T::CrossAccountId::from_sub(sender.clone()),173				data,174				[175					Self::rmrk_property(Metadata, &metadata)?,176					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,177				]178				.into_iter(),179			)?;180			let rmrk_collection_id = <CollectionIndex<T>>::get();181182			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);183			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);184185			<CollectionIndex<T>>::mutate(|n| *n += 1);186187			Self::deposit_event(Event::CollectionCreated {188				issuer: sender,189				collection_id: rmrk_collection_id,190			});191192			Ok(())193		}194195		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]196		#[transactional]197		pub fn destroy_collection(198			origin: OriginFor<T>,199			collection_id: RmrkCollectionId,200		) -> DispatchResult {201			let sender = ensure_signed(origin)?;202			let cross_sender = T::CrossAccountId::from_sub(sender.clone());203204			let collection = Self::get_typed_nft_collection(205				Self::unique_collection_id(collection_id)?,206				misc::CollectionType::Regular,207			)?;208209			<PalletNft<T>>::destroy_collection(collection, &cross_sender)210				.map_err(Self::map_unique_err_to_proxy)?;211212			Self::deposit_event(Event::CollectionDestroyed {213				issuer: sender,214				collection_id,215			});216217			Ok(())218		}219220		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]221		#[transactional]222		pub fn change_collection_issuer(223			origin: OriginFor<T>,224			collection_id: RmrkCollectionId,225			new_issuer: <T::Lookup as StaticLookup>::Source,226		) -> DispatchResult {227			let sender = ensure_signed(origin)?;228229			let new_issuer = T::Lookup::lookup(new_issuer)?;230231			Self::change_collection_owner(232				Self::unique_collection_id(collection_id)?,233				misc::CollectionType::Regular,234				sender.clone(),235				new_issuer.clone(),236			)?;237238			Self::deposit_event(Event::IssuerChanged {239				old_issuer: sender,240				new_issuer,241				collection_id,242			});243244			Ok(())245		}246247		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]248		#[transactional]249		pub fn lock_collection(250			origin: OriginFor<T>,251			collection_id: RmrkCollectionId,252		) -> DispatchResult {253			let sender = ensure_signed(origin)?;254			let cross_sender = T::CrossAccountId::from_sub(sender.clone());255256			let collection = Self::get_typed_nft_collection(257				Self::unique_collection_id(collection_id)?,258				misc::CollectionType::Regular,259			)?;260261			Self::check_collection_owner(&collection, &cross_sender)?;262263			let token_count = collection.total_supply();264265			let mut collection = collection.into_inner();266			collection.limits.token_limit = Some(token_count);267			collection.save()?;268269			Self::deposit_event(Event::CollectionLocked {270				issuer: sender,271				collection_id,272			});273274			Ok(())275		}276277		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]278		#[transactional]279		pub fn mint_nft(280			origin: OriginFor<T>,281			owner: T::AccountId,282			collection_id: RmrkCollectionId,283			recipient: Option<T::AccountId>,284			royalty_amount: Option<Permill>,285			metadata: RmrkString,286			transferable: bool,287		) -> DispatchResult {288			let sender = ensure_signed(origin)?;289			let sender = T::CrossAccountId::from_sub(sender);290			let cross_owner = T::CrossAccountId::from_sub(owner.clone());291292			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {293				recipient: recipient.unwrap_or_else(|| owner.clone()),294				amount,295			});296297			let collection = Self::get_typed_nft_collection(298				Self::unique_collection_id(collection_id)?,299				misc::CollectionType::Regular,300			)?;301302			let nft_id = Self::create_nft(303				&sender,304				&cross_owner,305				&collection,306				[307					Self::rmrk_property(TokenType, &NftType::Regular)?,308					Self::rmrk_property(Transferable, &transferable)?,309					Self::rmrk_property(PendingNftAccept, &false)?,310					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,311					Self::rmrk_property(Metadata, &metadata)?,312					Self::rmrk_property(Equipped, &false)?,313					Self::rmrk_property(314						ResourceCollection,315						&Self::init_collection(316							sender.clone(),317							CreateCollectionData {318								..Default::default()319							},320							[Self::rmrk_property(321								CollectionType,322								&misc::CollectionType::Resource,323							)?]324							.into_iter(),325						)?,326					)?, // todo possibly add limits to the collection if rmrk warrants them327					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,328				]329				.into_iter(),330			)331			.map_err(|err| match err {332				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),333				err => Self::map_unique_err_to_proxy(err),334			})?;335336			Self::deposit_event(Event::NftMinted {337				owner,338				collection_id,339				nft_id: nft_id.0,340			});341342			Ok(())343		}344345		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]346		#[transactional]347		pub fn burn_nft(348			origin: OriginFor<T>,349			collection_id: RmrkCollectionId,350			nft_id: RmrkNftId,351		) -> DispatchResult {352			let sender = ensure_signed(origin)?;353			let cross_sender = T::CrossAccountId::from_sub(sender.clone());354355			Self::destroy_nft(356				cross_sender,357				Self::unique_collection_id(collection_id)?,358				nft_id.into(),359			)360			.map_err(Self::map_unique_err_to_proxy)?;361362			Self::deposit_event(Event::NFTBurned {363				owner: sender,364				nft_id,365			});366367			Ok(())368		}369370		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]371		#[transactional]372		pub fn send(373			origin: OriginFor<T>,374			rmrk_collection_id: RmrkCollectionId,375			rmrk_nft_id: RmrkNftId,376			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,377		) -> DispatchResult {378			let sender = ensure_signed(origin.clone())?;379			let cross_sender = T::CrossAccountId::from_sub(sender);380381			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;382			let nft_id = rmrk_nft_id.into();383384			let token_data =385				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;386387			let from = token_data.owner;388389			let collection =390				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;391392			if !Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)? {393				return Err(<Error<T>>::NonTransferable.into());394			}395396			if Self::get_nft_property_decoded(397				collection_id,398				nft_id,399				RmrkProperty::PendingNftAccept,400			)? {401				return Err(<Error<T>>::NoPermission.into());402			}403404			let target_owner;405406			match new_owner {407				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {408					target_owner = T::CrossAccountId::from_sub(account_id);409				}410				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(411					target_collection_id,412					target_nft_id,413				) => {414					let target_collection_id = Self::unique_collection_id(target_collection_id)?;415416					let target_nft_budget = budget::Value::new(NESTING_BUDGET);417418					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(419						target_collection_id,420						target_nft_id.into(),421						Some((collection_id, nft_id)),422						&target_nft_budget,423					)424					.map_err(Self::map_unique_err_to_proxy)?;425426					let is_approval_required = cross_sender != target_nft_owner;427428					if is_approval_required {429						target_owner = target_nft_owner;430431						<PalletNft<T>>::set_scoped_token_property(432							collection.id,433							nft_id,434							PropertyScope::Rmrk,435							Self::rmrk_property(PendingNftAccept, &is_approval_required)?,436						)?;437					} else {438						target_owner = T::CrossTokenAddressMapping::token_to_address(439							target_collection_id,440							target_nft_id.into(),441						);442					}443				}444			}445446			let src_nft_budget = budget::Value::new(NESTING_BUDGET);447448			<PalletNft<T>>::transfer_from(449				&collection,450				&cross_sender,451				&from,452				&target_owner,453				nft_id,454				&src_nft_budget,455			)456			.map_err(Self::map_unique_err_to_proxy)?;457458			Ok(())459		}460461		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]462		#[transactional]463		pub fn accept_nft(464			origin: OriginFor<T>,465			rmrk_collection_id: RmrkCollectionId,466			rmrk_nft_id: RmrkNftId,467			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,468		) -> DispatchResult {469			let sender = ensure_signed(origin.clone())?;470			let cross_sender = T::CrossAccountId::from_sub(sender);471472			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;473			let nft_id = rmrk_nft_id.into();474475			let collection =476				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;477478			let new_owner = match new_owner {479				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {480					T::CrossAccountId::from_sub(account_id)481				}482				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(483					target_collection_id,484					target_nft_id,485				) => {486					let target_collection_id = Self::unique_collection_id(target_collection_id)?;487488					T::CrossTokenAddressMapping::token_to_address(489						target_collection_id,490						TokenId(target_nft_id),491					)492				}493			};494495			let budget = budget::Value::new(NESTING_BUDGET);496497			<PalletNft<T>>::transfer(&collection, &cross_sender, &new_owner, nft_id, &budget)498				.map_err(|err| {499					if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {500						<Error<T>>::CannotAcceptNonOwnedNft.into()501					} else {502						Self::map_unique_err_to_proxy(err)503					}504				})?;505506			<PalletNft<T>>::set_scoped_token_property(507				collection.id,508				nft_id,509				PropertyScope::Rmrk,510				Self::rmrk_property(PendingNftAccept, &false)?,511			)?;512513			Ok(())514		}515516		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]517		#[transactional]518		pub fn reject_nft(519			origin: OriginFor<T>,520			collection_id: RmrkCollectionId,521			nft_id: RmrkNftId,522		) -> DispatchResult {523			let sender = ensure_signed(origin)?;524			let cross_sender = T::CrossAccountId::from_sub(sender);525526			let collection_id = Self::unique_collection_id(collection_id)?;527			let nft_id = nft_id.into();528529			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {530				if err == <CommonError<T>>::NoPermission.into()531					|| err == <CommonError<T>>::ApprovedValueTooLow.into()532				{533					<Error<T>>::CannotRejectNonOwnedNft.into()534				} else {535					Self::map_unique_err_to_proxy(err)536				}537			})?;538539			Ok(())540		}541542		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]543		#[transactional]544		pub fn accept_resource(545			origin: OriginFor<T>,546			rmrk_collection_id: RmrkCollectionId,547			rmrk_nft_id: RmrkNftId,548			rmrk_resource_id: RmrkResourceId,549		) -> DispatchResult {550			let sender = ensure_signed(origin)?;551			let cross_sender = T::CrossAccountId::from_sub(sender);552553			let collection_id = Self::unique_collection_id(rmrk_collection_id)554				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;555556			let nft_id = rmrk_nft_id.into();557			let resource_id = rmrk_resource_id.into();558559			let budget = budget::Value::new(NESTING_BUDGET);560561			let nft_owner = <PalletStructure<T>>::find_topmost_owner(562				collection_id,563				nft_id,564				&budget,565			).map_err(|_| <Error<T>>::ResourceDoesntExist)?;566567			let resource_collection_id: CollectionId =568				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)569					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;570571			let is_pending: bool = Self::get_nft_property_decoded(resource_collection_id, resource_id, PendingResourceAccept)572				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;573574			ensure!(is_pending, <Error<T>>::ResourceNotPending);575576			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);577578			<PalletNft<T>>::set_scoped_token_property(579				resource_collection_id,580				resource_id,581				PropertyScope::Rmrk,582				Self::rmrk_property(PendingResourceAccept, &false)?,583			)?;584585			Ok(())586		}587588		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]589		#[transactional]590		pub fn set_property(591			origin: OriginFor<T>,592			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,593			maybe_nft_id: Option<RmrkNftId>,594			key: RmrkKeyString,595			value: RmrkValueString,596		) -> DispatchResult {597			let sender = ensure_signed(origin)?;598			let sender = T::CrossAccountId::from_sub(sender);599600			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;601			let budget = budget::Value::new(NESTING_BUDGET);602603			match maybe_nft_id {604				Some(nft_id) => {605					let token_id: TokenId = nft_id.into();606607					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;608					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;609610					<PalletNft<T>>::set_scoped_token_property(611						collection_id,612						token_id,613						PropertyScope::Rmrk,614						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,615					)?;616				}617				None => {618					let collection = Self::get_typed_nft_collection(619						collection_id,620						misc::CollectionType::Regular,621					)?;622623					Self::check_collection_owner(&collection, &sender)?;624625					<PalletCommon<T>>::set_scoped_collection_property(626						collection_id,627						PropertyScope::Rmrk,628						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,629					)?;630				}631			}632633			Self::deposit_event(Event::PropertySet {634				collection_id: rmrk_collection_id,635				maybe_nft_id,636				key,637				value,638			});639640			Ok(())641		}642643		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]644		#[transactional]645		pub fn add_basic_resource(646			origin: OriginFor<T>,647			collection_id: RmrkCollectionId,648			nft_id: RmrkNftId,649			resource: RmrkBasicResource,650		) -> DispatchResult {651			let sender = ensure_signed(origin.clone())?;652653			let resource_id = Self::resource_add(654				sender,655				Self::unique_collection_id(collection_id)?,656				nft_id.into(),657				[658					Self::rmrk_property(TokenType, &NftType::Resource)?,659					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,660					Self::rmrk_property(Src, &resource.src)?,661					Self::rmrk_property(Metadata, &resource.metadata)?,662					Self::rmrk_property(License, &resource.license)?,663					Self::rmrk_property(Thumb, &resource.thumb)?,664				]665				.into_iter(),666			)?;667668			Self::deposit_event(Event::ResourceAdded {669				nft_id,670				resource_id,671			});672			Ok(())673		}674675		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]676		#[transactional]677		pub fn add_composable_resource(678			origin: OriginFor<T>,679			collection_id: RmrkCollectionId,680			nft_id: RmrkNftId,681			_resource_id: RmrkBoundedResource,682			resource: RmrkComposableResource,683		) -> DispatchResult {684			let sender = ensure_signed(origin.clone())?;685686			let resource_id = Self::resource_add(687				sender,688				Self::unique_collection_id(collection_id)?,689				nft_id.into(),690				[691					Self::rmrk_property(TokenType, &NftType::Resource)?,692					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,693					Self::rmrk_property(Parts, &resource.parts)?,694					Self::rmrk_property(Base, &resource.base)?,695					Self::rmrk_property(Src, &resource.src)?,696					Self::rmrk_property(Metadata, &resource.metadata)?,697					Self::rmrk_property(License, &resource.license)?,698					Self::rmrk_property(Thumb, &resource.thumb)?,699				]700				.into_iter(),701			)?;702703			Self::deposit_event(Event::ResourceAdded {704				nft_id,705				resource_id,706			});707			Ok(())708		}709710		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]711		#[transactional]712		pub fn add_slot_resource(713			origin: OriginFor<T>,714			collection_id: RmrkCollectionId,715			nft_id: RmrkNftId,716			resource: RmrkSlotResource,717		) -> DispatchResult {718			let sender = ensure_signed(origin.clone())?;719720			let resource_id = Self::resource_add(721				sender,722				Self::unique_collection_id(collection_id)?,723				nft_id.into(),724				[725					Self::rmrk_property(TokenType, &NftType::Resource)?,726					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,727					Self::rmrk_property(Base, &resource.base)?,728					Self::rmrk_property(Src, &resource.src)?,729					Self::rmrk_property(Metadata, &resource.metadata)?,730					Self::rmrk_property(Slot, &resource.slot)?,731					Self::rmrk_property(License, &resource.license)?,732					Self::rmrk_property(Thumb, &resource.thumb)?,733				]734				.into_iter(),735			)?;736737			Self::deposit_event(Event::ResourceAdded {738				nft_id,739				resource_id,740			});741			Ok(())742		}743744		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]745		#[transactional]746		pub fn remove_resource(747			origin: OriginFor<T>,748			collection_id: RmrkCollectionId,749			nft_id: RmrkNftId,750			resource_id: RmrkResourceId,751		) -> DispatchResult {752			let sender = ensure_signed(origin.clone())?;753754			Self::resource_remove(755				sender,756				Self::unique_collection_id(collection_id)?,757				nft_id.into(),758				resource_id.into(),759			)?;760761			Self::deposit_event(Event::ResourceRemoval {762				nft_id,763				resource_id,764			});765			Ok(())766		}767	}768}769770impl<T: Config> Pallet<T> {771	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {772		let key = rmrk_key.to_key::<T>()?;773774		let scoped_key = PropertyScope::Rmrk775			.apply(key)776			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;777778		Ok(scoped_key)779	}780781	// todo think about renaming these782	pub fn rmrk_property<E: Encode>(783		rmrk_key: RmrkProperty,784		value: &E,785	) -> Result<Property, DispatchError> {786		let key = rmrk_key.to_key::<T>()?;787788		let value = value789			.encode()790			.try_into()791			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;792793		let property = Property { key, value };794795		Ok(property)796	}797798	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {799		vec.decode()800			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())801	}802803	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>804	where805		BoundedVec<u8, S>: TryFrom<Vec<u8>>,806	{807		vec.rebind()808			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())809	}810811	fn init_collection(812		sender: T::CrossAccountId,813		data: CreateCollectionData<T::AccountId>,814		properties: impl Iterator<Item = Property>,815	) -> Result<CollectionId, DispatchError> {816		let collection_id = <PalletNft<T>>::init_collection(sender, data);817818		if let Err(DispatchError::Arithmetic(_)) = &collection_id {819			return Err(<Error<T>>::NoAvailableCollectionId.into());820		}821822		<PalletCommon<T>>::set_scoped_collection_properties(823			collection_id?,824			PropertyScope::Rmrk,825			properties,826		)?;827828		collection_id829	}830831	pub fn create_nft(832		sender: &T::CrossAccountId,833		owner: &T::CrossAccountId,834		collection: &NonfungibleHandle<T>,835		properties: impl Iterator<Item = Property>,836	) -> Result<TokenId, DispatchError> {837		let data = CreateNftExData {838			properties: BoundedVec::default(),839			owner: owner.clone(),840		};841842		let budget = budget::Value::new(NESTING_BUDGET);843844		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;845846		let nft_id = <PalletNft<T>>::current_token_id(collection.id);847848		<PalletNft<T>>::set_scoped_token_properties(849			collection.id,850			nft_id,851			PropertyScope::Rmrk,852			properties,853		)?;854855		Ok(nft_id)856	}857858	fn destroy_nft(859		sender: T::CrossAccountId,860		collection_id: CollectionId,861		token_id: TokenId,862	) -> DispatchResult {863		let collection =864			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;865866		let token_data =867			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;868869		let from = token_data.owner;870871		let budget = budget::Value::new(NESTING_BUDGET);872873		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)874	}875876	fn resource_add(877		sender: T::AccountId,878		collection_id: CollectionId,879		token_id: TokenId,880		resource_properties: impl Iterator<Item = Property>,881	) -> Result<RmrkResourceId, DispatchError> {882		let collection =883			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;884		ensure!(collection.owner == sender, Error::<T>::NoPermission);885886		let sender = T::CrossAccountId::from_sub(sender);887		let budget = budget::Value::new(NESTING_BUDGET);888889		let nft_owner = <PalletStructure<T>>::find_topmost_owner(890			collection_id,891			token_id,892			&budget,893		).map_err(Self::map_unique_err_to_proxy)?;894895		let pending = sender != nft_owner;896897		let resource_collection_id: CollectionId =898			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;899		let resource_collection =900			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;901902		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them903904		let resource_id = Self::create_nft(905			&sender,906			&nft_owner,907			&resource_collection,908			resource_properties.chain(909				[910					Self::rmrk_property(PendingResourceAccept, &pending)?,911					Self::rmrk_property(PendingResourceRemoval, &false)?,912				]913				.into_iter(),914			),915		)916		.map_err(|err| match err {917			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),918			err => Self::map_unique_err_to_proxy(err),919		})?;920921		Ok(resource_id.0)922	}923924	fn resource_remove(925		sender: T::AccountId,926		collection_id: CollectionId,927		nft_id: TokenId,928		resource_id: TokenId,929	) -> DispatchResult {930		let collection =931			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;932		ensure!(collection.owner == sender, Error::<T>::NoPermission);933934		let resource_collection_id: CollectionId =935			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;936		let resource_collection =937			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;938		ensure!(939			<PalletNft<T>>::token_exists(&resource_collection, resource_id),940			Error::<T>::ResourceDoesntExist941		);942943		let budget = up_data_structs::budget::Value::new(10);944		let topmost_owner =945			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;946947		let sender = T::CrossAccountId::from_sub(sender);948		if topmost_owner == sender {949			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)950				.map_err(Self::map_unique_err_to_proxy)?;951		} else {952			<PalletNft<T>>::set_scoped_token_property(953				resource_collection_id,954				resource_id,955				PropertyScope::Rmrk,956				Self::rmrk_property(PendingResourceRemoval, &true)?,957			)?;958		}959960		Ok(())961	}962963	fn change_collection_owner(964		collection_id: CollectionId,965		collection_type: misc::CollectionType,966		sender: T::AccountId,967		new_owner: T::AccountId,968	) -> DispatchResult {969		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;970		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;971972		let mut collection = collection.into_inner();973974		collection.owner = new_owner;975		collection.save()976	}977978	fn check_collection_owner(979		collection: &NonfungibleHandle<T>,980		account: &T::CrossAccountId,981	) -> DispatchResult {982		collection983			.check_is_owner(account)984			.map_err(Self::map_unique_err_to_proxy)985	}986987	pub fn last_collection_idx() -> RmrkCollectionId {988		<CollectionIndex<T>>::get()989	}990991	pub fn unique_collection_id(992		rmrk_collection_id: RmrkCollectionId,993	) -> Result<CollectionId, DispatchError> {994		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)995			.map_err(|_| <Error<T>>::CollectionUnknown.into())996	}997998	pub fn rmrk_collection_id(999		unique_collection_id: CollectionId,1000	) -> Result<RmrkCollectionId, DispatchError> {1001		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1002			.map_err(|_| <Error<T>>::CollectionUnknown.into())1003	}10041005	pub fn get_nft_collection(1006		collection_id: CollectionId,1007	) -> Result<NonfungibleHandle<T>, DispatchError> {1008		let collection = <CollectionHandle<T>>::try_get(collection_id)1009			.map_err(|_| <Error<T>>::CollectionUnknown)?;10101011		match collection.mode {1012			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1013			_ => Err(<Error<T>>::CollectionUnknown.into()),1014		}1015	}10161017	pub fn collection_exists(collection_id: CollectionId) -> bool {1018		<CollectionHandle<T>>::try_get(collection_id).is_ok()1019	}10201021	pub fn get_collection_property(1022		collection_id: CollectionId,1023		key: RmrkProperty,1024	) -> Result<PropertyValue, DispatchError> {1025		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1026			.get(&Self::rmrk_property_key(key)?)1027			.ok_or(<Error<T>>::CollectionUnknown)?1028			.clone();10291030		Ok(collection_property)1031	}10321033	pub fn get_collection_property_decoded<V: Decode>(1034		collection_id: CollectionId,1035		key: RmrkProperty,1036	) -> Result<V, DispatchError> {1037		Self::decode_property(Self::get_collection_property(collection_id, key)?)1038	}10391040	pub fn get_collection_type(1041		collection_id: CollectionId,1042	) -> Result<misc::CollectionType, DispatchError> {1043		Self::get_collection_property_decoded(collection_id, CollectionType)1044			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1045	}10461047	pub fn ensure_collection_type(1048		collection_id: CollectionId,1049		collection_type: misc::CollectionType,1050	) -> DispatchResult {1051		let actual_type = Self::get_collection_type(collection_id)?;1052		ensure!(1053			actual_type == collection_type,1054			<CommonError<T>>::NoPermission1055		);10561057		Ok(())1058	}10591060	pub fn get_typed_nft_collection(1061		collection_id: CollectionId,1062		collection_type: misc::CollectionType,1063	) -> Result<NonfungibleHandle<T>, DispatchError> {1064		Self::ensure_collection_type(collection_id, collection_type)?;10651066		Self::get_nft_collection(collection_id)1067	}10681069	pub fn get_typed_nft_collection_mapped(1070		rmrk_collection_id: RmrkCollectionId,1071		collection_type: misc::CollectionType,1072	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1073		let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;10741075		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;10761077		Ok((collection, unique_collection_id))1078	}10791080	pub fn get_nft_property(1081		collection_id: CollectionId,1082		nft_id: TokenId,1083		key: RmrkProperty,1084	) -> Result<PropertyValue, DispatchError> {1085		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1086			.get(&Self::rmrk_property_key(key)?)1087			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1088			.clone();10891090		Ok(nft_property)1091	}10921093	pub fn get_nft_property_decoded<V: Decode>(1094		collection_id: CollectionId,1095		nft_id: TokenId,1096		key: RmrkProperty,1097	) -> Result<V, DispatchError> {1098		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1099	}11001101	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1102		<TokenData<T>>::contains_key((collection_id, nft_id))1103	}11041105	pub fn get_nft_type(1106		collection_id: CollectionId,1107		token_id: TokenId,1108	) -> Result<NftType, DispatchError> {1109		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1110			.map_err(|_| <Error<T>>::NftTypeEncodeError.into())1111	}11121113	pub fn ensure_nft_type(1114		collection_id: CollectionId,1115		token_id: TokenId,1116		nft_type: NftType,1117	) -> DispatchResult {1118		let actual_type = Self::get_nft_type(collection_id, token_id)?;1119		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);11201121		Ok(())1122	}11231124	pub fn ensure_nft_owner(1125		collection_id: CollectionId,1126		token_id: TokenId,1127		possible_owner: &T::CrossAccountId,1128		nesting_budget: &dyn budget::Budget,1129	) -> DispatchResult {1130		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1131			possible_owner.clone(),1132			collection_id,1133			token_id,1134			None,1135			nesting_budget,1136		)?;11371138		ensure!(is_owned, <Error<T>>::NoPermission);11391140		Ok(())1141	}11421143	pub fn filter_user_properties<Key, Value, R, Mapper>(1144		collection_id: CollectionId,1145		token_id: Option<TokenId>,1146		filter_keys: Option<Vec<RmrkPropertyKey>>,1147		mapper: Mapper,1148	) -> Result<Vec<R>, DispatchError>1149	where1150		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1151		Value: Decode + Default,1152		Mapper: Fn(Key, Value) -> R,1153	{1154		filter_keys1155			.map(|keys| {1156				let properties = keys1157					.into_iter()1158					.filter_map(|key| {1159						let key: Key = key.try_into().ok()?;11601161						let value = match token_id {1162							Some(token_id) => Self::get_nft_property_decoded(1163								collection_id,1164								token_id,1165								UserProperty(key.as_ref()),1166							),1167							None => Self::get_collection_property_decoded(1168								collection_id,1169								UserProperty(key.as_ref()),1170							),1171						}1172						.ok()?;11731174						Some(mapper(key, value))1175					})1176					.collect();11771178				Ok(properties)1179			})1180			.unwrap_or_else(|| {1181				let properties =1182					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();11831184				Ok(properties)1185			})1186	}11871188	pub fn iterate_user_properties<Key, Value, R, Mapper>(1189		collection_id: CollectionId,1190		token_id: Option<TokenId>,1191		mapper: Mapper,1192	) -> Result<impl Iterator<Item = R>, DispatchError>1193	where1194		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1195		Value: Decode + Default,1196		Mapper: Fn(Key, Value) -> R,1197	{1198		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;11991200		let properties = match token_id {1201			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1202			None => <PalletCommon<T>>::collection_properties(collection_id),1203		};12041205		let properties = properties.into_iter().filter_map(move |(key, value)| {1206			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;12071208			let key: Key = key.to_vec().try_into().ok()?;1209			let value: Value = value.decode().ok()?;12101211			Some(mapper(key, value))1212		});12131214		Ok(properties)1215	}12161217	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1218		map_unique_err_to_proxy! {1219			match err {1220				CommonError::NoPermission => NoPermission,1221				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1222				CommonError::PublicMintingNotAllowed => NoPermission,1223				CommonError::TokenNotFound => NoAvailableNftId,1224				CommonError::ApprovedValueTooLow => NoPermission,1225				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1226				StructureError::TokenNotFound => NoAvailableNftId,1227				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1228			}1229		}1230	}1231}
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		PropertySet {102			collection_id: RmrkCollectionId,103			maybe_nft_id: Option<RmrkNftId>,104			key: RmrkKeyString,105			value: RmrkValueString,106		},107		ResourceAdded {108			nft_id: RmrkNftId,109			resource_id: RmrkResourceId,110		},111		ResourceRemoval {112			nft_id: RmrkNftId,113			resource_id: RmrkResourceId,114		},115	}116117	#[pallet::error]118	pub enum Error<T> {119		/* Unique-specific events */120		CorruptedCollectionType,121		NftTypeEncodeError,122		RmrkPropertyKeyIsTooLong,123		RmrkPropertyValueIsTooLong,124125		/* RMRK compatible events */126		CollectionNotEmpty,127		NoAvailableCollectionId,128		NoAvailableNftId,129		CollectionUnknown,130		NoPermission,131		NonTransferable,132		CollectionFullOrLocked,133		ResourceDoesntExist,134		CannotSendToDescendentOrSelf,135		CannotAcceptNonOwnedNft,136		CannotRejectNonOwnedNft,137		ResourceNotPending,138	}139140	#[pallet::call]141	impl<T: Config> Pallet<T> {142		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]143		#[transactional]144		pub fn create_collection(145			origin: OriginFor<T>,146			metadata: RmrkString,147			max: Option<u32>,148			symbol: RmrkCollectionSymbol,149		) -> DispatchResult {150			let sender = ensure_signed(origin)?;151152			let limits = CollectionLimits {153				owner_can_transfer: Some(false),154				token_limit: max,155				..Default::default()156			};157158			let data = CreateCollectionData {159				limits: Some(limits),160				token_prefix: symbol161					.into_inner()162					.try_into()163					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,164				permissions: Some(CollectionPermissions {165					nesting: Some(NestingRule::Owner),166					..Default::default()167				}),168				..Default::default()169			};170171			let unique_collection_id = Self::init_collection(172				T::CrossAccountId::from_sub(sender.clone()),173				data,174				[175					Self::rmrk_property(Metadata, &metadata)?,176					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,177				]178				.into_iter(),179			)?;180			let rmrk_collection_id = <CollectionIndex<T>>::get();181182			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);183			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);184185			<CollectionIndex<T>>::mutate(|n| *n += 1);186187			Self::deposit_event(Event::CollectionCreated {188				issuer: sender,189				collection_id: rmrk_collection_id,190			});191192			Ok(())193		}194195		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]196		#[transactional]197		pub fn destroy_collection(198			origin: OriginFor<T>,199			collection_id: RmrkCollectionId,200		) -> DispatchResult {201			let sender = ensure_signed(origin)?;202			let cross_sender = T::CrossAccountId::from_sub(sender.clone());203204			let collection = Self::get_typed_nft_collection(205				Self::unique_collection_id(collection_id)?,206				misc::CollectionType::Regular,207			)?;208209			<PalletNft<T>>::destroy_collection(collection, &cross_sender)210				.map_err(Self::map_unique_err_to_proxy)?;211212			Self::deposit_event(Event::CollectionDestroyed {213				issuer: sender,214				collection_id,215			});216217			Ok(())218		}219220		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]221		#[transactional]222		pub fn change_collection_issuer(223			origin: OriginFor<T>,224			collection_id: RmrkCollectionId,225			new_issuer: <T::Lookup as StaticLookup>::Source,226		) -> DispatchResult {227			let sender = ensure_signed(origin)?;228229			let new_issuer = T::Lookup::lookup(new_issuer)?;230231			Self::change_collection_owner(232				Self::unique_collection_id(collection_id)?,233				misc::CollectionType::Regular,234				sender.clone(),235				new_issuer.clone(),236			)?;237238			Self::deposit_event(Event::IssuerChanged {239				old_issuer: sender,240				new_issuer,241				collection_id,242			});243244			Ok(())245		}246247		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]248		#[transactional]249		pub fn lock_collection(250			origin: OriginFor<T>,251			collection_id: RmrkCollectionId,252		) -> DispatchResult {253			let sender = ensure_signed(origin)?;254			let cross_sender = T::CrossAccountId::from_sub(sender.clone());255256			let collection = Self::get_typed_nft_collection(257				Self::unique_collection_id(collection_id)?,258				misc::CollectionType::Regular,259			)?;260261			Self::check_collection_owner(&collection, &cross_sender)?;262263			let token_count = collection.total_supply();264265			let mut collection = collection.into_inner();266			collection.limits.token_limit = Some(token_count);267			collection.save()?;268269			Self::deposit_event(Event::CollectionLocked {270				issuer: sender,271				collection_id,272			});273274			Ok(())275		}276277		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]278		#[transactional]279		pub fn mint_nft(280			origin: OriginFor<T>,281			owner: T::AccountId,282			collection_id: RmrkCollectionId,283			recipient: Option<T::AccountId>,284			royalty_amount: Option<Permill>,285			metadata: RmrkString,286			transferable: bool,287		) -> DispatchResult {288			let sender = ensure_signed(origin)?;289			let sender = T::CrossAccountId::from_sub(sender);290			let cross_owner = T::CrossAccountId::from_sub(owner.clone());291292			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {293				recipient: recipient.unwrap_or_else(|| owner.clone()),294				amount,295			});296297			let collection = Self::get_typed_nft_collection(298				Self::unique_collection_id(collection_id)?,299				misc::CollectionType::Regular,300			)?;301302			let nft_id = Self::create_nft(303				&sender,304				&cross_owner,305				&collection,306				[307					Self::rmrk_property(TokenType, &NftType::Regular)?,308					Self::rmrk_property(Transferable, &transferable)?,309					Self::rmrk_property(PendingNftAccept, &false)?,310					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,311					Self::rmrk_property(Metadata, &metadata)?,312					Self::rmrk_property(Equipped, &false)?,313					Self::rmrk_property(314						ResourceCollection,315						&Self::init_collection(316							sender.clone(),317							CreateCollectionData {318								..Default::default()319							},320							[Self::rmrk_property(321								CollectionType,322								&misc::CollectionType::Resource,323							)?]324							.into_iter(),325						)?,326					)?, // todo possibly add limits to the collection if rmrk warrants them327					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,328				]329				.into_iter(),330			)331			.map_err(|err| match err {332				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),333				err => Self::map_unique_err_to_proxy(err),334			})?;335336			Self::deposit_event(Event::NftMinted {337				owner,338				collection_id,339				nft_id: nft_id.0,340			});341342			Ok(())343		}344345		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]346		#[transactional]347		pub fn burn_nft(348			origin: OriginFor<T>,349			collection_id: RmrkCollectionId,350			nft_id: RmrkNftId,351		) -> DispatchResult {352			let sender = ensure_signed(origin)?;353			let cross_sender = T::CrossAccountId::from_sub(sender.clone());354355			Self::destroy_nft(356				cross_sender,357				Self::unique_collection_id(collection_id)?,358				nft_id.into(),359			)360			.map_err(Self::map_unique_err_to_proxy)?;361362			Self::deposit_event(Event::NFTBurned {363				owner: sender,364				nft_id,365			});366367			Ok(())368		}369370		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]371		#[transactional]372		pub fn send(373			origin: OriginFor<T>,374			rmrk_collection_id: RmrkCollectionId,375			rmrk_nft_id: RmrkNftId,376			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,377		) -> DispatchResult {378			let sender = ensure_signed(origin.clone())?;379			let cross_sender = T::CrossAccountId::from_sub(sender);380381			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;382			let nft_id = rmrk_nft_id.into();383384			let token_data =385				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;386387			let from = token_data.owner;388389			let collection =390				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;391392			if !Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)? {393				return Err(<Error<T>>::NonTransferable.into());394			}395396			if Self::get_nft_property_decoded(397				collection_id,398				nft_id,399				RmrkProperty::PendingNftAccept,400			)? {401				return Err(<Error<T>>::NoPermission.into());402			}403404			let target_owner;405406			match new_owner {407				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {408					target_owner = T::CrossAccountId::from_sub(account_id);409				}410				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(411					target_collection_id,412					target_nft_id,413				) => {414					let target_collection_id = Self::unique_collection_id(target_collection_id)?;415416					let target_nft_budget = budget::Value::new(NESTING_BUDGET);417418					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(419						target_collection_id,420						target_nft_id.into(),421						Some((collection_id, nft_id)),422						&target_nft_budget,423					)424					.map_err(Self::map_unique_err_to_proxy)?;425426					let is_approval_required = cross_sender != target_nft_owner;427428					if is_approval_required {429						target_owner = target_nft_owner;430431						<PalletNft<T>>::set_scoped_token_property(432							collection.id,433							nft_id,434							PropertyScope::Rmrk,435							Self::rmrk_property(PendingNftAccept, &is_approval_required)?,436						)?;437					} else {438						target_owner = T::CrossTokenAddressMapping::token_to_address(439							target_collection_id,440							target_nft_id.into(),441						);442					}443				}444			}445446			let src_nft_budget = budget::Value::new(NESTING_BUDGET);447448			<PalletNft<T>>::transfer_from(449				&collection,450				&cross_sender,451				&from,452				&target_owner,453				nft_id,454				&src_nft_budget,455			)456			.map_err(Self::map_unique_err_to_proxy)?;457458			Ok(())459		}460461		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]462		#[transactional]463		pub fn accept_nft(464			origin: OriginFor<T>,465			rmrk_collection_id: RmrkCollectionId,466			rmrk_nft_id: RmrkNftId,467			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,468		) -> DispatchResult {469			let sender = ensure_signed(origin.clone())?;470			let cross_sender = T::CrossAccountId::from_sub(sender);471472			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;473			let nft_id = rmrk_nft_id.into();474475			let collection =476				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;477478			let new_owner = match new_owner {479				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {480					T::CrossAccountId::from_sub(account_id)481				}482				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(483					target_collection_id,484					target_nft_id,485				) => {486					let target_collection_id = Self::unique_collection_id(target_collection_id)?;487488					T::CrossTokenAddressMapping::token_to_address(489						target_collection_id,490						TokenId(target_nft_id),491					)492				}493			};494495			let budget = budget::Value::new(NESTING_BUDGET);496497			<PalletNft<T>>::transfer(&collection, &cross_sender, &new_owner, nft_id, &budget)498				.map_err(|err| {499					if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {500						<Error<T>>::CannotAcceptNonOwnedNft.into()501					} else {502						Self::map_unique_err_to_proxy(err)503					}504				})?;505506			<PalletNft<T>>::set_scoped_token_property(507				collection.id,508				nft_id,509				PropertyScope::Rmrk,510				Self::rmrk_property(PendingNftAccept, &false)?,511			)?;512513			Ok(())514		}515516		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]517		#[transactional]518		pub fn reject_nft(519			origin: OriginFor<T>,520			collection_id: RmrkCollectionId,521			nft_id: RmrkNftId,522		) -> DispatchResult {523			let sender = ensure_signed(origin)?;524			let cross_sender = T::CrossAccountId::from_sub(sender);525526			let collection_id = Self::unique_collection_id(collection_id)?;527			let nft_id = nft_id.into();528529			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {530				if err == <CommonError<T>>::NoPermission.into()531					|| err == <CommonError<T>>::ApprovedValueTooLow.into()532				{533					<Error<T>>::CannotRejectNonOwnedNft.into()534				} else {535					Self::map_unique_err_to_proxy(err)536				}537			})?;538539			Ok(())540		}541542		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]543		#[transactional]544		pub fn accept_resource(545			origin: OriginFor<T>,546			rmrk_collection_id: RmrkCollectionId,547			rmrk_nft_id: RmrkNftId,548			rmrk_resource_id: RmrkResourceId,549		) -> DispatchResult {550			let sender = ensure_signed(origin)?;551			let cross_sender = T::CrossAccountId::from_sub(sender);552553			let collection_id = Self::unique_collection_id(rmrk_collection_id)554				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;555556			let nft_id = rmrk_nft_id.into();557			let resource_id = rmrk_resource_id.into();558559			let budget = budget::Value::new(NESTING_BUDGET);560561			let nft_owner =562				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)563					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;564565			let resource_collection_id: CollectionId =566				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)567					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;568569			let is_pending: bool = Self::get_nft_property_decoded(570				resource_collection_id,571				resource_id,572				PendingResourceAccept,573			)574			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;575576			ensure!(is_pending, <Error<T>>::ResourceNotPending);577578			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);579580			<PalletNft<T>>::set_scoped_token_property(581				resource_collection_id,582				rmrk_resource_id.into(),583				PropertyScope::Rmrk,584				Self::rmrk_property(PendingResourceAccept, &false)?,585			)?;586587			Ok(())588		}589590		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]591		#[transactional]592		pub fn accept_resource_removal(593			origin: OriginFor<T>,594			rmrk_collection_id: RmrkCollectionId,595			rmrk_nft_id: RmrkNftId,596			rmrk_resource_id: RmrkResourceId,597		) -> DispatchResult {598			let sender = ensure_signed(origin)?;599			let cross_sender = T::CrossAccountId::from_sub(sender);600601			let collection_id = Self::unique_collection_id(rmrk_collection_id)602				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;603604			let nft_id = rmrk_nft_id.into();605			let resource_id = rmrk_resource_id.into();606607			let budget = budget::Value::new(NESTING_BUDGET);608609			let nft_owner =610				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)611					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;612613			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);614615			let resource_collection_id: CollectionId =616				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)617					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;618619			let is_pending: bool = Self::get_nft_property_decoded(620				resource_collection_id,621				resource_id,622				PendingResourceRemoval,623			)624			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;625626			ensure!(is_pending, <Error<T>>::ResourceNotPending);627628			let resource_collection = Self::get_typed_nft_collection(629				resource_collection_id,630				misc::CollectionType::Resource,631			)?;632633			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())634				.map_err(Self::map_unique_err_to_proxy)?;635636			Ok(())637		}638639		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]640		#[transactional]641		pub fn set_property(642			origin: OriginFor<T>,643			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,644			maybe_nft_id: Option<RmrkNftId>,645			key: RmrkKeyString,646			value: RmrkValueString,647		) -> DispatchResult {648			let sender = ensure_signed(origin)?;649			let sender = T::CrossAccountId::from_sub(sender);650651			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;652			let budget = budget::Value::new(NESTING_BUDGET);653654			match maybe_nft_id {655				Some(nft_id) => {656					let token_id: TokenId = nft_id.into();657658					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;659					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;660661					<PalletNft<T>>::set_scoped_token_property(662						collection_id,663						token_id,664						PropertyScope::Rmrk,665						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,666					)?;667				}668				None => {669					let collection = Self::get_typed_nft_collection(670						collection_id,671						misc::CollectionType::Regular,672					)?;673674					Self::check_collection_owner(&collection, &sender)?;675676					<PalletCommon<T>>::set_scoped_collection_property(677						collection_id,678						PropertyScope::Rmrk,679						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,680					)?;681				}682			}683684			Self::deposit_event(Event::PropertySet {685				collection_id: rmrk_collection_id,686				maybe_nft_id,687				key,688				value,689			});690691			Ok(())692		}693694		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]695		#[transactional]696		pub fn add_basic_resource(697			origin: OriginFor<T>,698			collection_id: RmrkCollectionId,699			nft_id: RmrkNftId,700			resource: RmrkBasicResource,701		) -> DispatchResult {702			let sender = ensure_signed(origin.clone())?;703704			let resource_id = Self::resource_add(705				sender,706				Self::unique_collection_id(collection_id)?,707				nft_id.into(),708				[709					Self::rmrk_property(TokenType, &NftType::Resource)?,710					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,711					Self::rmrk_property(Src, &resource.src)?,712					Self::rmrk_property(Metadata, &resource.metadata)?,713					Self::rmrk_property(License, &resource.license)?,714					Self::rmrk_property(Thumb, &resource.thumb)?,715				]716				.into_iter(),717			)?;718719			Self::deposit_event(Event::ResourceAdded {720				nft_id,721				resource_id,722			});723			Ok(())724		}725726		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]727		#[transactional]728		pub fn add_composable_resource(729			origin: OriginFor<T>,730			collection_id: RmrkCollectionId,731			nft_id: RmrkNftId,732			_resource_id: RmrkBoundedResource,733			resource: RmrkComposableResource,734		) -> DispatchResult {735			let sender = ensure_signed(origin.clone())?;736737			let resource_id = Self::resource_add(738				sender,739				Self::unique_collection_id(collection_id)?,740				nft_id.into(),741				[742					Self::rmrk_property(TokenType, &NftType::Resource)?,743					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,744					Self::rmrk_property(Parts, &resource.parts)?,745					Self::rmrk_property(Base, &resource.base)?,746					Self::rmrk_property(Src, &resource.src)?,747					Self::rmrk_property(Metadata, &resource.metadata)?,748					Self::rmrk_property(License, &resource.license)?,749					Self::rmrk_property(Thumb, &resource.thumb)?,750				]751				.into_iter(),752			)?;753754			Self::deposit_event(Event::ResourceAdded {755				nft_id,756				resource_id,757			});758			Ok(())759		}760761		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]762		#[transactional]763		pub fn add_slot_resource(764			origin: OriginFor<T>,765			collection_id: RmrkCollectionId,766			nft_id: RmrkNftId,767			resource: RmrkSlotResource,768		) -> DispatchResult {769			let sender = ensure_signed(origin.clone())?;770771			let resource_id = Self::resource_add(772				sender,773				Self::unique_collection_id(collection_id)?,774				nft_id.into(),775				[776					Self::rmrk_property(TokenType, &NftType::Resource)?,777					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,778					Self::rmrk_property(Base, &resource.base)?,779					Self::rmrk_property(Src, &resource.src)?,780					Self::rmrk_property(Metadata, &resource.metadata)?,781					Self::rmrk_property(Slot, &resource.slot)?,782					Self::rmrk_property(License, &resource.license)?,783					Self::rmrk_property(Thumb, &resource.thumb)?,784				]785				.into_iter(),786			)?;787788			Self::deposit_event(Event::ResourceAdded {789				nft_id,790				resource_id,791			});792			Ok(())793		}794795		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]796		#[transactional]797		pub fn remove_resource(798			origin: OriginFor<T>,799			collection_id: RmrkCollectionId,800			nft_id: RmrkNftId,801			resource_id: RmrkResourceId,802		) -> DispatchResult {803			let sender = ensure_signed(origin.clone())?;804805			Self::resource_remove(806				sender,807				Self::unique_collection_id(collection_id)?,808				nft_id.into(),809				resource_id.into(),810			)?;811812			Self::deposit_event(Event::ResourceRemoval {813				nft_id,814				resource_id,815			});816			Ok(())817		}818	}819}820821impl<T: Config> Pallet<T> {822	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {823		let key = rmrk_key.to_key::<T>()?;824825		let scoped_key = PropertyScope::Rmrk826			.apply(key)827			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;828829		Ok(scoped_key)830	}831832	// todo think about renaming these833	pub fn rmrk_property<E: Encode>(834		rmrk_key: RmrkProperty,835		value: &E,836	) -> Result<Property, DispatchError> {837		let key = rmrk_key.to_key::<T>()?;838839		let value = value840			.encode()841			.try_into()842			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;843844		let property = Property { key, value };845846		Ok(property)847	}848849	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {850		vec.decode()851			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())852	}853854	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>855	where856		BoundedVec<u8, S>: TryFrom<Vec<u8>>,857	{858		vec.rebind()859			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())860	}861862	fn init_collection(863		sender: T::CrossAccountId,864		data: CreateCollectionData<T::AccountId>,865		properties: impl Iterator<Item = Property>,866	) -> Result<CollectionId, DispatchError> {867		let collection_id = <PalletNft<T>>::init_collection(sender, data);868869		if let Err(DispatchError::Arithmetic(_)) = &collection_id {870			return Err(<Error<T>>::NoAvailableCollectionId.into());871		}872873		<PalletCommon<T>>::set_scoped_collection_properties(874			collection_id?,875			PropertyScope::Rmrk,876			properties,877		)?;878879		collection_id880	}881882	pub fn create_nft(883		sender: &T::CrossAccountId,884		owner: &T::CrossAccountId,885		collection: &NonfungibleHandle<T>,886		properties: impl Iterator<Item = Property>,887	) -> Result<TokenId, DispatchError> {888		let data = CreateNftExData {889			properties: BoundedVec::default(),890			owner: owner.clone(),891		};892893		let budget = budget::Value::new(NESTING_BUDGET);894895		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;896897		let nft_id = <PalletNft<T>>::current_token_id(collection.id);898899		<PalletNft<T>>::set_scoped_token_properties(900			collection.id,901			nft_id,902			PropertyScope::Rmrk,903			properties,904		)?;905906		Ok(nft_id)907	}908909	fn destroy_nft(910		sender: T::CrossAccountId,911		collection_id: CollectionId,912		token_id: TokenId,913	) -> DispatchResult {914		let collection =915			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;916917		let token_data =918			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;919920		let from = token_data.owner;921922		let budget = budget::Value::new(NESTING_BUDGET);923924		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)925	}926927	fn resource_add(928		sender: T::AccountId,929		collection_id: CollectionId,930		token_id: TokenId,931		resource_properties: impl Iterator<Item = Property>,932	) -> Result<RmrkResourceId, DispatchError> {933		let collection =934			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;935		ensure!(collection.owner == sender, Error::<T>::NoPermission);936937		let sender = T::CrossAccountId::from_sub(sender);938		let budget = budget::Value::new(NESTING_BUDGET);939940		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)941			.map_err(Self::map_unique_err_to_proxy)?;942943		let pending = sender != nft_owner;944945		let resource_collection_id: CollectionId =946			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;947		let resource_collection =948			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;949950		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them951952		let resource_id = Self::create_nft(953			&sender,954			&nft_owner,955			&resource_collection,956			resource_properties.chain(957				[958					Self::rmrk_property(PendingResourceAccept, &pending)?,959					Self::rmrk_property(PendingResourceRemoval, &false)?,960				]961				.into_iter(),962			),963		)964		.map_err(|err| match err {965			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),966			err => Self::map_unique_err_to_proxy(err),967		})?;968969		Ok(resource_id.0)970	}971972	fn resource_remove(973		sender: T::AccountId,974		collection_id: CollectionId,975		nft_id: TokenId,976		resource_id: TokenId,977	) -> DispatchResult {978		let collection =979			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;980		ensure!(collection.owner == sender, Error::<T>::NoPermission);981982		let resource_collection_id: CollectionId =983			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;984		let resource_collection =985			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;986		ensure!(987			<PalletNft<T>>::token_exists(&resource_collection, resource_id),988			Error::<T>::ResourceDoesntExist989		);990991		let budget = up_data_structs::budget::Value::new(10);992		let topmost_owner =993			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;994995		let sender = T::CrossAccountId::from_sub(sender);996		if topmost_owner == sender {997			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)998				.map_err(Self::map_unique_err_to_proxy)?;999		} else {1000			<PalletNft<T>>::set_scoped_token_property(1001				resource_collection_id,1002				resource_id,1003				PropertyScope::Rmrk,1004				Self::rmrk_property(PendingResourceRemoval, &true)?,1005			)?;1006		}10071008		Ok(())1009	}10101011	fn change_collection_owner(1012		collection_id: CollectionId,1013		collection_type: misc::CollectionType,1014		sender: T::AccountId,1015		new_owner: T::AccountId,1016	) -> DispatchResult {1017		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1018		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;10191020		let mut collection = collection.into_inner();10211022		collection.owner = new_owner;1023		collection.save()1024	}10251026	fn check_collection_owner(1027		collection: &NonfungibleHandle<T>,1028		account: &T::CrossAccountId,1029	) -> DispatchResult {1030		collection1031			.check_is_owner(account)1032			.map_err(Self::map_unique_err_to_proxy)1033	}10341035	pub fn last_collection_idx() -> RmrkCollectionId {1036		<CollectionIndex<T>>::get()1037	}10381039	pub fn unique_collection_id(1040		rmrk_collection_id: RmrkCollectionId,1041	) -> Result<CollectionId, DispatchError> {1042		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1043			.map_err(|_| <Error<T>>::CollectionUnknown.into())1044	}10451046	pub fn rmrk_collection_id(1047		unique_collection_id: CollectionId,1048	) -> Result<RmrkCollectionId, DispatchError> {1049		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1050			.map_err(|_| <Error<T>>::CollectionUnknown.into())1051	}10521053	pub fn get_nft_collection(1054		collection_id: CollectionId,1055	) -> Result<NonfungibleHandle<T>, DispatchError> {1056		let collection = <CollectionHandle<T>>::try_get(collection_id)1057			.map_err(|_| <Error<T>>::CollectionUnknown)?;10581059		match collection.mode {1060			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1061			_ => Err(<Error<T>>::CollectionUnknown.into()),1062		}1063	}10641065	pub fn collection_exists(collection_id: CollectionId) -> bool {1066		<CollectionHandle<T>>::try_get(collection_id).is_ok()1067	}10681069	pub fn get_collection_property(1070		collection_id: CollectionId,1071		key: RmrkProperty,1072	) -> Result<PropertyValue, DispatchError> {1073		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1074			.get(&Self::rmrk_property_key(key)?)1075			.ok_or(<Error<T>>::CollectionUnknown)?1076			.clone();10771078		Ok(collection_property)1079	}10801081	pub fn get_collection_property_decoded<V: Decode>(1082		collection_id: CollectionId,1083		key: RmrkProperty,1084	) -> Result<V, DispatchError> {1085		Self::decode_property(Self::get_collection_property(collection_id, key)?)1086	}10871088	pub fn get_collection_type(1089		collection_id: CollectionId,1090	) -> Result<misc::CollectionType, DispatchError> {1091		Self::get_collection_property_decoded(collection_id, CollectionType)1092			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1093	}10941095	pub fn ensure_collection_type(1096		collection_id: CollectionId,1097		collection_type: misc::CollectionType,1098	) -> DispatchResult {1099		let actual_type = Self::get_collection_type(collection_id)?;1100		ensure!(1101			actual_type == collection_type,1102			<CommonError<T>>::NoPermission1103		);11041105		Ok(())1106	}11071108	pub fn get_typed_nft_collection(1109		collection_id: CollectionId,1110		collection_type: misc::CollectionType,1111	) -> Result<NonfungibleHandle<T>, DispatchError> {1112		Self::ensure_collection_type(collection_id, collection_type)?;11131114		Self::get_nft_collection(collection_id)1115	}11161117	pub fn get_typed_nft_collection_mapped(1118		rmrk_collection_id: RmrkCollectionId,1119		collection_type: misc::CollectionType,1120	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1121		let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;11221123		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;11241125		Ok((collection, unique_collection_id))1126	}11271128	pub fn get_nft_property(1129		collection_id: CollectionId,1130		nft_id: TokenId,1131		key: RmrkProperty,1132	) -> Result<PropertyValue, DispatchError> {1133		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1134			.get(&Self::rmrk_property_key(key)?)1135			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1136			.clone();11371138		Ok(nft_property)1139	}11401141	pub fn get_nft_property_decoded<V: Decode>(1142		collection_id: CollectionId,1143		nft_id: TokenId,1144		key: RmrkProperty,1145	) -> Result<V, DispatchError> {1146		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1147	}11481149	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1150		<TokenData<T>>::contains_key((collection_id, nft_id))1151	}11521153	pub fn get_nft_type(1154		collection_id: CollectionId,1155		token_id: TokenId,1156	) -> Result<NftType, DispatchError> {1157		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1158			.map_err(|_| <Error<T>>::NftTypeEncodeError.into())1159	}11601161	pub fn ensure_nft_type(1162		collection_id: CollectionId,1163		token_id: TokenId,1164		nft_type: NftType,1165	) -> DispatchResult {1166		let actual_type = Self::get_nft_type(collection_id, token_id)?;1167		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);11681169		Ok(())1170	}11711172	pub fn ensure_nft_owner(1173		collection_id: CollectionId,1174		token_id: TokenId,1175		possible_owner: &T::CrossAccountId,1176		nesting_budget: &dyn budget::Budget,1177	) -> DispatchResult {1178		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1179			possible_owner.clone(),1180			collection_id,1181			token_id,1182			None,1183			nesting_budget,1184		)?;11851186		ensure!(is_owned, <Error<T>>::NoPermission);11871188		Ok(())1189	}11901191	pub fn filter_user_properties<Key, Value, R, Mapper>(1192		collection_id: CollectionId,1193		token_id: Option<TokenId>,1194		filter_keys: Option<Vec<RmrkPropertyKey>>,1195		mapper: Mapper,1196	) -> Result<Vec<R>, DispatchError>1197	where1198		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1199		Value: Decode + Default,1200		Mapper: Fn(Key, Value) -> R,1201	{1202		filter_keys1203			.map(|keys| {1204				let properties = keys1205					.into_iter()1206					.filter_map(|key| {1207						let key: Key = key.try_into().ok()?;12081209						let value = match token_id {1210							Some(token_id) => Self::get_nft_property_decoded(1211								collection_id,1212								token_id,1213								UserProperty(key.as_ref()),1214							),1215							None => Self::get_collection_property_decoded(1216								collection_id,1217								UserProperty(key.as_ref()),1218							),1219						}1220						.ok()?;12211222						Some(mapper(key, value))1223					})1224					.collect();12251226				Ok(properties)1227			})1228			.unwrap_or_else(|| {1229				let properties =1230					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();12311232				Ok(properties)1233			})1234	}12351236	pub fn iterate_user_properties<Key, Value, R, Mapper>(1237		collection_id: CollectionId,1238		token_id: Option<TokenId>,1239		mapper: Mapper,1240	) -> Result<impl Iterator<Item = R>, DispatchError>1241	where1242		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1243		Value: Decode + Default,1244		Mapper: Fn(Key, Value) -> R,1245	{1246		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;12471248		let properties = match token_id {1249			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1250			None => <PalletCommon<T>>::collection_properties(collection_id),1251		};12521253		let properties = properties.into_iter().filter_map(move |(key, value)| {1254			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;12551256			let key: Key = key.to_vec().try_into().ok()?;1257			let value: Value = value.decode().ok()?;12581259			Some(mapper(key, value))1260		});12611262		Ok(properties)1263	}12641265	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1266		map_unique_err_to_proxy! {1267			match err {1268				CommonError::NoPermission => NoPermission,1269				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1270				CommonError::PublicMintingNotAllowed => NoPermission,1271				CommonError::TokenNotFound => NoAvailableNftId,1272				CommonError::ApprovedValueTooLow => NoPermission,1273				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1274				StructureError::TokenNotFound => NoAvailableNftId,1275				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1276			}1277		}1278	}1279}