git.delta.rocks / unique-network / refs/commits / 5771b3fcf428

difftreelog

cargo fmt

Daniel Shiposha2022-06-06parent: #22128f8.patch.diff
in: master

3 files changed

modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
before · pallets/proxy-rmrk-core/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;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_traits::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			ensure!(393				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,394				<Error<T>>::NonTransferable395			);396397			ensure!(398				!Self::get_nft_property_decoded(399					collection_id,400					nft_id,401					RmrkProperty::PendingNftAccept402				)?,403				<Error<T>>::NoPermission404			);405406			let target_owner;407408			match new_owner {409				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {410					target_owner = T::CrossAccountId::from_sub(account_id);411				}412				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(413					target_collection_id,414					target_nft_id,415				) => {416					let target_collection_id = Self::unique_collection_id(target_collection_id)?;417418					let target_nft_budget = budget::Value::new(NESTING_BUDGET);419420					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(421						target_collection_id,422						target_nft_id.into(),423						Some((collection_id, nft_id)),424						&target_nft_budget,425					)426					.map_err(Self::map_unique_err_to_proxy)?;427428					let is_approval_required = cross_sender != target_nft_owner;429430					if is_approval_required {431						target_owner = target_nft_owner;432433						<PalletNft<T>>::set_scoped_token_property(434							collection.id,435							nft_id,436							PropertyScope::Rmrk,437							Self::rmrk_property(PendingNftAccept, &is_approval_required)?,438						)?;439					} else {440						target_owner = T::CrossTokenAddressMapping::token_to_address(441							target_collection_id,442							target_nft_id.into(),443						);444					}445				}446			}447448			let src_nft_budget = budget::Value::new(NESTING_BUDGET);449450			<PalletNft<T>>::transfer_from(451				&collection,452				&cross_sender,453				&from,454				&target_owner,455				nft_id,456				&src_nft_budget,457			)458			.map_err(Self::map_unique_err_to_proxy)?;459460			Ok(())461		}462463		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]464		#[transactional]465		pub fn accept_nft(466			origin: OriginFor<T>,467			rmrk_collection_id: RmrkCollectionId,468			rmrk_nft_id: RmrkNftId,469			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,470		) -> DispatchResult {471			let sender = ensure_signed(origin.clone())?;472			let cross_sender = T::CrossAccountId::from_sub(sender);473474			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;475			let nft_id = rmrk_nft_id.into();476477			let collection =478				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;479480			let new_owner = match new_owner {481				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {482					T::CrossAccountId::from_sub(account_id)483				}484				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(485					target_collection_id,486					target_nft_id,487				) => {488					let target_collection_id = Self::unique_collection_id(target_collection_id)?;489490					T::CrossTokenAddressMapping::token_to_address(491						target_collection_id,492						TokenId(target_nft_id),493					)494				}495			};496497			let budget = budget::Value::new(NESTING_BUDGET);498499			<PalletNft<T>>::transfer(&collection, &cross_sender, &new_owner, nft_id, &budget)500				.map_err(|err| {501					if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {502						<Error<T>>::CannotAcceptNonOwnedNft.into()503					} else {504						Self::map_unique_err_to_proxy(err)505					}506				})?;507508			<PalletNft<T>>::set_scoped_token_property(509				collection.id,510				nft_id,511				PropertyScope::Rmrk,512				Self::rmrk_property(PendingNftAccept, &false)?,513			)?;514515			Ok(())516		}517518		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]519		#[transactional]520		pub fn reject_nft(521			origin: OriginFor<T>,522			collection_id: RmrkCollectionId,523			nft_id: RmrkNftId,524		) -> DispatchResult {525			let sender = ensure_signed(origin)?;526			let cross_sender = T::CrossAccountId::from_sub(sender);527528			let collection_id = Self::unique_collection_id(collection_id)?;529			let nft_id = nft_id.into();530531			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {532				if err == <CommonError<T>>::NoPermission.into()533					|| err == <CommonError<T>>::ApprovedValueTooLow.into()534				{535					<Error<T>>::CannotRejectNonOwnedNft.into()536				} else {537					Self::map_unique_err_to_proxy(err)538				}539			})?;540541			Ok(())542		}543544		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]545		#[transactional]546		pub fn accept_resource(547			origin: OriginFor<T>,548			rmrk_collection_id: RmrkCollectionId,549			rmrk_nft_id: RmrkNftId,550			rmrk_resource_id: RmrkResourceId,551		) -> DispatchResult {552			let sender = ensure_signed(origin)?;553			let cross_sender = T::CrossAccountId::from_sub(sender);554555			let collection_id = Self::unique_collection_id(rmrk_collection_id)556				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;557558			let nft_id = rmrk_nft_id.into();559			let resource_id = rmrk_resource_id.into();560561			let budget = budget::Value::new(NESTING_BUDGET);562563			let nft_owner =564				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &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(572				resource_collection_id,573				resource_id,574				PendingResourceAccept,575			)576			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;577578			ensure!(is_pending, <Error<T>>::ResourceNotPending);579580			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);581582			<PalletNft<T>>::set_scoped_token_property(583				resource_collection_id,584				rmrk_resource_id.into(),585				PropertyScope::Rmrk,586				Self::rmrk_property(PendingResourceAccept, &false)?,587			)?;588589			Ok(())590		}591592		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]593		#[transactional]594		pub fn accept_resource_removal(595			origin: OriginFor<T>,596			rmrk_collection_id: RmrkCollectionId,597			rmrk_nft_id: RmrkNftId,598			rmrk_resource_id: RmrkResourceId,599		) -> DispatchResult {600			let sender = ensure_signed(origin)?;601			let cross_sender = T::CrossAccountId::from_sub(sender);602603			let collection_id = Self::unique_collection_id(rmrk_collection_id)604				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;605606			let nft_id = rmrk_nft_id.into();607			let resource_id = rmrk_resource_id.into();608609			let budget = budget::Value::new(NESTING_BUDGET);610611			let nft_owner =612				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)613					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;614615			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);616617			let resource_collection_id: CollectionId =618				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)619					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;620621			let is_pending: bool = Self::get_nft_property_decoded(622				resource_collection_id,623				resource_id,624				PendingResourceRemoval,625			)626			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;627628			ensure!(is_pending, <Error<T>>::ResourceNotPending);629630			let resource_collection = Self::get_typed_nft_collection(631				resource_collection_id,632				misc::CollectionType::Resource,633			)?;634635			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())636				.map_err(Self::map_unique_err_to_proxy)?;637638			Ok(())639		}640641		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]642		#[transactional]643		pub fn set_property(644			origin: OriginFor<T>,645			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,646			maybe_nft_id: Option<RmrkNftId>,647			key: RmrkKeyString,648			value: RmrkValueString,649		) -> DispatchResult {650			let sender = ensure_signed(origin)?;651			let sender = T::CrossAccountId::from_sub(sender);652653			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;654			let budget = budget::Value::new(NESTING_BUDGET);655656			match maybe_nft_id {657				Some(nft_id) => {658					let token_id: TokenId = nft_id.into();659660					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;661					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;662663					<PalletNft<T>>::set_scoped_token_property(664						collection_id,665						token_id,666						PropertyScope::Rmrk,667						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,668					)?;669				}670				None => {671					let collection = Self::get_typed_nft_collection(672						collection_id,673						misc::CollectionType::Regular,674					)?;675676					Self::check_collection_owner(&collection, &sender)?;677678					<PalletCommon<T>>::set_scoped_collection_property(679						collection_id,680						PropertyScope::Rmrk,681						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,682					)?;683				}684			}685686			Self::deposit_event(Event::PropertySet {687				collection_id: rmrk_collection_id,688				maybe_nft_id,689				key,690				value,691			});692693			Ok(())694		}695696		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]697		#[transactional]698		pub fn set_priority(699			origin: OriginFor<T>,700			rmrk_collection_id: RmrkCollectionId,701			rmrk_nft_id: RmrkNftId,702			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,703		) -> DispatchResult {704			let sender = ensure_signed(origin)?;705			let sender = T::CrossAccountId::from_sub(sender);706707			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;708			let nft_id = rmrk_nft_id.into();709			let budget = budget::Value::new(NESTING_BUDGET);710711			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;712			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;713714			<PalletNft<T>>::set_scoped_token_property(715				collection_id,716				nft_id,717				PropertyScope::Rmrk,718				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?719			)?;720721			Ok(())722		}723724		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]725		#[transactional]726		pub fn add_basic_resource(727			origin: OriginFor<T>,728			collection_id: RmrkCollectionId,729			nft_id: RmrkNftId,730			resource: RmrkBasicResource,731		) -> DispatchResult {732			let sender = ensure_signed(origin.clone())?;733734			let resource_id = Self::resource_add(735				sender,736				Self::unique_collection_id(collection_id)?,737				nft_id.into(),738				[739					Self::rmrk_property(TokenType, &NftType::Resource)?,740					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,741					Self::rmrk_property(Src, &resource.src)?,742					Self::rmrk_property(Metadata, &resource.metadata)?,743					Self::rmrk_property(License, &resource.license)?,744					Self::rmrk_property(Thumb, &resource.thumb)?,745				]746				.into_iter(),747			)?;748749			Self::deposit_event(Event::ResourceAdded {750				nft_id,751				resource_id,752			});753			Ok(())754		}755756		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]757		#[transactional]758		pub fn add_composable_resource(759			origin: OriginFor<T>,760			collection_id: RmrkCollectionId,761			nft_id: RmrkNftId,762			_resource_id: RmrkBoundedResource,763			resource: RmrkComposableResource,764		) -> DispatchResult {765			let sender = ensure_signed(origin.clone())?;766767			let resource_id = Self::resource_add(768				sender,769				Self::unique_collection_id(collection_id)?,770				nft_id.into(),771				[772					Self::rmrk_property(TokenType, &NftType::Resource)?,773					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,774					Self::rmrk_property(Parts, &resource.parts)?,775					Self::rmrk_property(Base, &resource.base)?,776					Self::rmrk_property(Src, &resource.src)?,777					Self::rmrk_property(Metadata, &resource.metadata)?,778					Self::rmrk_property(License, &resource.license)?,779					Self::rmrk_property(Thumb, &resource.thumb)?,780				]781				.into_iter(),782			)?;783784			Self::deposit_event(Event::ResourceAdded {785				nft_id,786				resource_id,787			});788			Ok(())789		}790791		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]792		#[transactional]793		pub fn add_slot_resource(794			origin: OriginFor<T>,795			collection_id: RmrkCollectionId,796			nft_id: RmrkNftId,797			resource: RmrkSlotResource,798		) -> DispatchResult {799			let sender = ensure_signed(origin.clone())?;800801			let resource_id = Self::resource_add(802				sender,803				Self::unique_collection_id(collection_id)?,804				nft_id.into(),805				[806					Self::rmrk_property(TokenType, &NftType::Resource)?,807					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,808					Self::rmrk_property(Base, &resource.base)?,809					Self::rmrk_property(Src, &resource.src)?,810					Self::rmrk_property(Metadata, &resource.metadata)?,811					Self::rmrk_property(Slot, &resource.slot)?,812					Self::rmrk_property(License, &resource.license)?,813					Self::rmrk_property(Thumb, &resource.thumb)?,814				]815				.into_iter(),816			)?;817818			Self::deposit_event(Event::ResourceAdded {819				nft_id,820				resource_id,821			});822			Ok(())823		}824825		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]826		#[transactional]827		pub fn remove_resource(828			origin: OriginFor<T>,829			collection_id: RmrkCollectionId,830			nft_id: RmrkNftId,831			resource_id: RmrkResourceId,832		) -> DispatchResult {833			let sender = ensure_signed(origin.clone())?;834835			Self::resource_remove(836				sender,837				Self::unique_collection_id(collection_id)?,838				nft_id.into(),839				resource_id.into(),840			)?;841842			Self::deposit_event(Event::ResourceRemoval {843				nft_id,844				resource_id,845			});846			Ok(())847		}848	}849}850851impl<T: Config> Pallet<T> {852	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {853		let key = rmrk_key.to_key::<T>()?;854855		let scoped_key = PropertyScope::Rmrk856			.apply(key)857			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;858859		Ok(scoped_key)860	}861862	// todo think about renaming these863	pub fn rmrk_property<E: Encode>(864		rmrk_key: RmrkProperty,865		value: &E,866	) -> Result<Property, DispatchError> {867		let key = rmrk_key.to_key::<T>()?;868869		let value = value870			.encode()871			.try_into()872			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;873874		let property = Property { key, value };875876		Ok(property)877	}878879	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {880		vec.decode()881			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())882	}883884	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>885	where886		BoundedVec<u8, S>: TryFrom<Vec<u8>>,887	{888		vec.rebind()889			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())890	}891892	fn init_collection(893		sender: T::CrossAccountId,894		data: CreateCollectionData<T::AccountId>,895		properties: impl Iterator<Item = Property>,896	) -> Result<CollectionId, DispatchError> {897		let collection_id = <PalletNft<T>>::init_collection(sender, data);898899		if let Err(DispatchError::Arithmetic(_)) = &collection_id {900			return Err(<Error<T>>::NoAvailableCollectionId.into());901		}902903		<PalletCommon<T>>::set_scoped_collection_properties(904			collection_id?,905			PropertyScope::Rmrk,906			properties,907		)?;908909		collection_id910	}911912	pub fn create_nft(913		sender: &T::CrossAccountId,914		owner: &T::CrossAccountId,915		collection: &NonfungibleHandle<T>,916		properties: impl Iterator<Item = Property>,917	) -> Result<TokenId, DispatchError> {918		let data = CreateNftExData {919			properties: BoundedVec::default(),920			owner: owner.clone(),921		};922923		let budget = budget::Value::new(NESTING_BUDGET);924925		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;926927		let nft_id = <PalletNft<T>>::current_token_id(collection.id);928929		<PalletNft<T>>::set_scoped_token_properties(930			collection.id,931			nft_id,932			PropertyScope::Rmrk,933			properties,934		)?;935936		Ok(nft_id)937	}938939	fn destroy_nft(940		sender: T::CrossAccountId,941		collection_id: CollectionId,942		token_id: TokenId,943	) -> DispatchResult {944		let collection =945			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;946947		let token_data =948			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;949950		let from = token_data.owner;951952		let budget = budget::Value::new(NESTING_BUDGET);953954		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)955	}956957	fn resource_add(958		sender: T::AccountId,959		collection_id: CollectionId,960		token_id: TokenId,961		resource_properties: impl Iterator<Item = Property>,962	) -> Result<RmrkResourceId, DispatchError> {963		let collection =964			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;965		ensure!(collection.owner == sender, Error::<T>::NoPermission);966967		let sender = T::CrossAccountId::from_sub(sender);968		let budget = budget::Value::new(NESTING_BUDGET);969970		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)971			.map_err(Self::map_unique_err_to_proxy)?;972973		let pending = sender != nft_owner;974975		let resource_collection_id: CollectionId =976			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;977		let resource_collection =978			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;979980		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them981982		let resource_id = Self::create_nft(983			&sender,984			&nft_owner,985			&resource_collection,986			resource_properties.chain(987				[988					Self::rmrk_property(PendingResourceAccept, &pending)?,989					Self::rmrk_property(PendingResourceRemoval, &false)?,990				]991				.into_iter(),992			),993		)994		.map_err(|err| match err {995			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),996			err => Self::map_unique_err_to_proxy(err),997		})?;998999		Ok(resource_id.0)1000	}10011002	fn resource_remove(1003		sender: T::AccountId,1004		collection_id: CollectionId,1005		nft_id: TokenId,1006		resource_id: TokenId,1007	) -> DispatchResult {1008		let collection =1009			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1010		ensure!(collection.owner == sender, Error::<T>::NoPermission);10111012		let resource_collection_id: CollectionId =1013			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1014		let resource_collection =1015			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1016		ensure!(1017			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1018			Error::<T>::ResourceDoesntExist1019		);10201021		let budget = up_data_structs::budget::Value::new(10);1022		let topmost_owner =1023			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;10241025		let sender = T::CrossAccountId::from_sub(sender);1026		if topmost_owner == sender {1027			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1028				.map_err(Self::map_unique_err_to_proxy)?;1029		} else {1030			<PalletNft<T>>::set_scoped_token_property(1031				resource_collection_id,1032				resource_id,1033				PropertyScope::Rmrk,1034				Self::rmrk_property(PendingResourceRemoval, &true)?,1035			)?;1036		}10371038		Ok(())1039	}10401041	fn change_collection_owner(1042		collection_id: CollectionId,1043		collection_type: misc::CollectionType,1044		sender: T::AccountId,1045		new_owner: T::AccountId,1046	) -> DispatchResult {1047		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1048		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;10491050		let mut collection = collection.into_inner();10511052		collection.owner = new_owner;1053		collection.save()1054	}10551056	fn check_collection_owner(1057		collection: &NonfungibleHandle<T>,1058		account: &T::CrossAccountId,1059	) -> DispatchResult {1060		collection1061			.check_is_owner(account)1062			.map_err(Self::map_unique_err_to_proxy)1063	}10641065	pub fn last_collection_idx() -> RmrkCollectionId {1066		<CollectionIndex<T>>::get()1067	}10681069	pub fn unique_collection_id(1070		rmrk_collection_id: RmrkCollectionId,1071	) -> Result<CollectionId, DispatchError> {1072		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1073			.map_err(|_| <Error<T>>::CollectionUnknown.into())1074	}10751076	pub fn rmrk_collection_id(1077		unique_collection_id: CollectionId,1078	) -> Result<RmrkCollectionId, DispatchError> {1079		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1080			.map_err(|_| <Error<T>>::CollectionUnknown.into())1081	}10821083	pub fn get_nft_collection(1084		collection_id: CollectionId,1085	) -> Result<NonfungibleHandle<T>, DispatchError> {1086		let collection = <CollectionHandle<T>>::try_get(collection_id)1087			.map_err(|_| <Error<T>>::CollectionUnknown)?;10881089		match collection.mode {1090			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1091			_ => Err(<Error<T>>::CollectionUnknown.into()),1092		}1093	}10941095	pub fn collection_exists(collection_id: CollectionId) -> bool {1096		<CollectionHandle<T>>::try_get(collection_id).is_ok()1097	}10981099	pub fn get_collection_property(1100		collection_id: CollectionId,1101		key: RmrkProperty,1102	) -> Result<PropertyValue, DispatchError> {1103		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1104			.get(&Self::rmrk_property_key(key)?)1105			.ok_or(<Error<T>>::CollectionUnknown)?1106			.clone();11071108		Ok(collection_property)1109	}11101111	pub fn get_collection_property_decoded<V: Decode>(1112		collection_id: CollectionId,1113		key: RmrkProperty,1114	) -> Result<V, DispatchError> {1115		Self::decode_property(Self::get_collection_property(collection_id, key)?)1116	}11171118	pub fn get_collection_type(1119		collection_id: CollectionId,1120	) -> Result<misc::CollectionType, DispatchError> {1121		Self::get_collection_property_decoded(collection_id, CollectionType)1122			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1123	}11241125	pub fn ensure_collection_type(1126		collection_id: CollectionId,1127		collection_type: misc::CollectionType,1128	) -> DispatchResult {1129		let actual_type = Self::get_collection_type(collection_id)?;1130		ensure!(1131			actual_type == collection_type,1132			<CommonError<T>>::NoPermission1133		);11341135		Ok(())1136	}11371138	pub fn get_typed_nft_collection(1139		collection_id: CollectionId,1140		collection_type: misc::CollectionType,1141	) -> Result<NonfungibleHandle<T>, DispatchError> {1142		Self::ensure_collection_type(collection_id, collection_type)?;11431144		Self::get_nft_collection(collection_id)1145	}11461147	pub fn get_typed_nft_collection_mapped(1148		rmrk_collection_id: RmrkCollectionId,1149		collection_type: misc::CollectionType,1150	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1151		let unique_collection_id = match collection_type {1152			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1153			_ => rmrk_collection_id.into()1154		};11551156		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;11571158		Ok((collection, unique_collection_id))1159	}11601161	pub fn get_nft_property(1162		collection_id: CollectionId,1163		nft_id: TokenId,1164		key: RmrkProperty,1165	) -> Result<PropertyValue, DispatchError> {1166		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1167			.get(&Self::rmrk_property_key(key)?)1168			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1169			.clone();11701171		Ok(nft_property)1172	}11731174	pub fn get_nft_property_decoded<V: Decode>(1175		collection_id: CollectionId,1176		nft_id: TokenId,1177		key: RmrkProperty,1178	) -> Result<V, DispatchError> {1179		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1180	}11811182	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1183		<TokenData<T>>::contains_key((collection_id, nft_id))1184	}11851186	pub fn get_nft_type(1187		collection_id: CollectionId,1188		token_id: TokenId,1189	) -> Result<NftType, DispatchError> {1190		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1191			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1192	}11931194	pub fn ensure_nft_type(1195		collection_id: CollectionId,1196		token_id: TokenId,1197		nft_type: NftType,1198	) -> DispatchResult {1199		let actual_type = Self::get_nft_type(collection_id, token_id)?;1200		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);12011202		Ok(())1203	}12041205	pub fn ensure_nft_owner(1206		collection_id: CollectionId,1207		token_id: TokenId,1208		possible_owner: &T::CrossAccountId,1209		nesting_budget: &dyn budget::Budget,1210	) -> DispatchResult {1211		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1212			possible_owner.clone(),1213			collection_id,1214			token_id,1215			None,1216			nesting_budget,1217		).map_err(Self::map_unique_err_to_proxy)?;12181219		ensure!(is_owned, <Error<T>>::NoPermission);12201221		Ok(())1222	}12231224	pub fn filter_user_properties<Key, Value, R, Mapper>(1225		collection_id: CollectionId,1226		token_id: Option<TokenId>,1227		filter_keys: Option<Vec<RmrkPropertyKey>>,1228		mapper: Mapper,1229	) -> Result<Vec<R>, DispatchError>1230	where1231		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1232		Value: Decode + Default,1233		Mapper: Fn(Key, Value) -> R,1234	{1235		filter_keys1236			.map(|keys| {1237				let properties = keys1238					.into_iter()1239					.filter_map(|key| {1240						let key: Key = key.try_into().ok()?;12411242						let value = match token_id {1243							Some(token_id) => Self::get_nft_property_decoded(1244								collection_id,1245								token_id,1246								UserProperty(key.as_ref()),1247							),1248							None => Self::get_collection_property_decoded(1249								collection_id,1250								UserProperty(key.as_ref()),1251							),1252						}1253						.ok()?;12541255						Some(mapper(key, value))1256					})1257					.collect();12581259				Ok(properties)1260			})1261			.unwrap_or_else(|| {1262				let properties =1263					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();12641265				Ok(properties)1266			})1267	}12681269	pub fn iterate_user_properties<Key, Value, R, Mapper>(1270		collection_id: CollectionId,1271		token_id: Option<TokenId>,1272		mapper: Mapper,1273	) -> Result<impl Iterator<Item = R>, DispatchError>1274	where1275		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1276		Value: Decode + Default,1277		Mapper: Fn(Key, Value) -> R,1278	{1279		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;12801281		let properties = match token_id {1282			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1283			None => <PalletCommon<T>>::collection_properties(collection_id),1284		};12851286		let properties = properties.into_iter().filter_map(move |(key, value)| {1287			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;12881289			let key: Key = key.to_vec().try_into().ok()?;1290			let value: Value = value.decode().ok()?;12911292			Some(mapper(key, value))1293		});12941295		Ok(properties)1296	}12971298	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1299		map_unique_err_to_proxy! {1300			match err {1301				CommonError::NoPermission => NoPermission,1302				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1303				CommonError::PublicMintingNotAllowed => NoPermission,1304				CommonError::TokenNotFound => NoAvailableNftId,1305				CommonError::ApprovedValueTooLow => NoPermission,1306				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1307				StructureError::TokenNotFound => NoAvailableNftId,1308				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1309			}1310		}1311	}1312}
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_traits::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			ensure!(393				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,394				<Error<T>>::NonTransferable395			);396397			ensure!(398				!Self::get_nft_property_decoded(399					collection_id,400					nft_id,401					RmrkProperty::PendingNftAccept402				)?,403				<Error<T>>::NoPermission404			);405406			let target_owner;407408			match new_owner {409				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {410					target_owner = T::CrossAccountId::from_sub(account_id);411				}412				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(413					target_collection_id,414					target_nft_id,415				) => {416					let target_collection_id = Self::unique_collection_id(target_collection_id)?;417418					let target_nft_budget = budget::Value::new(NESTING_BUDGET);419420					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(421						target_collection_id,422						target_nft_id.into(),423						Some((collection_id, nft_id)),424						&target_nft_budget,425					)426					.map_err(Self::map_unique_err_to_proxy)?;427428					let is_approval_required = cross_sender != target_nft_owner;429430					if is_approval_required {431						target_owner = target_nft_owner;432433						<PalletNft<T>>::set_scoped_token_property(434							collection.id,435							nft_id,436							PropertyScope::Rmrk,437							Self::rmrk_property(PendingNftAccept, &is_approval_required)?,438						)?;439					} else {440						target_owner = T::CrossTokenAddressMapping::token_to_address(441							target_collection_id,442							target_nft_id.into(),443						);444					}445				}446			}447448			let src_nft_budget = budget::Value::new(NESTING_BUDGET);449450			<PalletNft<T>>::transfer_from(451				&collection,452				&cross_sender,453				&from,454				&target_owner,455				nft_id,456				&src_nft_budget,457			)458			.map_err(Self::map_unique_err_to_proxy)?;459460			Ok(())461		}462463		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]464		#[transactional]465		pub fn accept_nft(466			origin: OriginFor<T>,467			rmrk_collection_id: RmrkCollectionId,468			rmrk_nft_id: RmrkNftId,469			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,470		) -> DispatchResult {471			let sender = ensure_signed(origin.clone())?;472			let cross_sender = T::CrossAccountId::from_sub(sender);473474			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;475			let nft_id = rmrk_nft_id.into();476477			let collection =478				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;479480			let new_owner = match new_owner {481				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {482					T::CrossAccountId::from_sub(account_id)483				}484				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(485					target_collection_id,486					target_nft_id,487				) => {488					let target_collection_id = Self::unique_collection_id(target_collection_id)?;489490					T::CrossTokenAddressMapping::token_to_address(491						target_collection_id,492						TokenId(target_nft_id),493					)494				}495			};496497			let budget = budget::Value::new(NESTING_BUDGET);498499			<PalletNft<T>>::transfer(&collection, &cross_sender, &new_owner, nft_id, &budget)500				.map_err(|err| {501					if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {502						<Error<T>>::CannotAcceptNonOwnedNft.into()503					} else {504						Self::map_unique_err_to_proxy(err)505					}506				})?;507508			<PalletNft<T>>::set_scoped_token_property(509				collection.id,510				nft_id,511				PropertyScope::Rmrk,512				Self::rmrk_property(PendingNftAccept, &false)?,513			)?;514515			Ok(())516		}517518		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]519		#[transactional]520		pub fn reject_nft(521			origin: OriginFor<T>,522			collection_id: RmrkCollectionId,523			nft_id: RmrkNftId,524		) -> DispatchResult {525			let sender = ensure_signed(origin)?;526			let cross_sender = T::CrossAccountId::from_sub(sender);527528			let collection_id = Self::unique_collection_id(collection_id)?;529			let nft_id = nft_id.into();530531			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {532				if err == <CommonError<T>>::NoPermission.into()533					|| err == <CommonError<T>>::ApprovedValueTooLow.into()534				{535					<Error<T>>::CannotRejectNonOwnedNft.into()536				} else {537					Self::map_unique_err_to_proxy(err)538				}539			})?;540541			Ok(())542		}543544		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]545		#[transactional]546		pub fn accept_resource(547			origin: OriginFor<T>,548			rmrk_collection_id: RmrkCollectionId,549			rmrk_nft_id: RmrkNftId,550			rmrk_resource_id: RmrkResourceId,551		) -> DispatchResult {552			let sender = ensure_signed(origin)?;553			let cross_sender = T::CrossAccountId::from_sub(sender);554555			let collection_id = Self::unique_collection_id(rmrk_collection_id)556				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;557558			let nft_id = rmrk_nft_id.into();559			let resource_id = rmrk_resource_id.into();560561			let budget = budget::Value::new(NESTING_BUDGET);562563			let nft_owner =564				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &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(572				resource_collection_id,573				resource_id,574				PendingResourceAccept,575			)576			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;577578			ensure!(is_pending, <Error<T>>::ResourceNotPending);579580			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);581582			<PalletNft<T>>::set_scoped_token_property(583				resource_collection_id,584				rmrk_resource_id.into(),585				PropertyScope::Rmrk,586				Self::rmrk_property(PendingResourceAccept, &false)?,587			)?;588589			Ok(())590		}591592		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]593		#[transactional]594		pub fn accept_resource_removal(595			origin: OriginFor<T>,596			rmrk_collection_id: RmrkCollectionId,597			rmrk_nft_id: RmrkNftId,598			rmrk_resource_id: RmrkResourceId,599		) -> DispatchResult {600			let sender = ensure_signed(origin)?;601			let cross_sender = T::CrossAccountId::from_sub(sender);602603			let collection_id = Self::unique_collection_id(rmrk_collection_id)604				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;605606			let nft_id = rmrk_nft_id.into();607			let resource_id = rmrk_resource_id.into();608609			let budget = budget::Value::new(NESTING_BUDGET);610611			let nft_owner =612				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)613					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;614615			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);616617			let resource_collection_id: CollectionId =618				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)619					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;620621			let is_pending: bool = Self::get_nft_property_decoded(622				resource_collection_id,623				resource_id,624				PendingResourceRemoval,625			)626			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;627628			ensure!(is_pending, <Error<T>>::ResourceNotPending);629630			let resource_collection = Self::get_typed_nft_collection(631				resource_collection_id,632				misc::CollectionType::Resource,633			)?;634635			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())636				.map_err(Self::map_unique_err_to_proxy)?;637638			Ok(())639		}640641		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]642		#[transactional]643		pub fn set_property(644			origin: OriginFor<T>,645			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,646			maybe_nft_id: Option<RmrkNftId>,647			key: RmrkKeyString,648			value: RmrkValueString,649		) -> DispatchResult {650			let sender = ensure_signed(origin)?;651			let sender = T::CrossAccountId::from_sub(sender);652653			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;654			let budget = budget::Value::new(NESTING_BUDGET);655656			match maybe_nft_id {657				Some(nft_id) => {658					let token_id: TokenId = nft_id.into();659660					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;661					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;662663					<PalletNft<T>>::set_scoped_token_property(664						collection_id,665						token_id,666						PropertyScope::Rmrk,667						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,668					)?;669				}670				None => {671					let collection = Self::get_typed_nft_collection(672						collection_id,673						misc::CollectionType::Regular,674					)?;675676					Self::check_collection_owner(&collection, &sender)?;677678					<PalletCommon<T>>::set_scoped_collection_property(679						collection_id,680						PropertyScope::Rmrk,681						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,682					)?;683				}684			}685686			Self::deposit_event(Event::PropertySet {687				collection_id: rmrk_collection_id,688				maybe_nft_id,689				key,690				value,691			});692693			Ok(())694		}695696		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]697		#[transactional]698		pub fn set_priority(699			origin: OriginFor<T>,700			rmrk_collection_id: RmrkCollectionId,701			rmrk_nft_id: RmrkNftId,702			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,703		) -> DispatchResult {704			let sender = ensure_signed(origin)?;705			let sender = T::CrossAccountId::from_sub(sender);706707			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;708			let nft_id = rmrk_nft_id.into();709			let budget = budget::Value::new(NESTING_BUDGET);710711			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;712			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;713714			<PalletNft<T>>::set_scoped_token_property(715				collection_id,716				nft_id,717				PropertyScope::Rmrk,718				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,719			)?;720721			Ok(())722		}723724		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]725		#[transactional]726		pub fn add_basic_resource(727			origin: OriginFor<T>,728			collection_id: RmrkCollectionId,729			nft_id: RmrkNftId,730			resource: RmrkBasicResource,731		) -> DispatchResult {732			let sender = ensure_signed(origin.clone())?;733734			let resource_id = Self::resource_add(735				sender,736				Self::unique_collection_id(collection_id)?,737				nft_id.into(),738				[739					Self::rmrk_property(TokenType, &NftType::Resource)?,740					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,741					Self::rmrk_property(Src, &resource.src)?,742					Self::rmrk_property(Metadata, &resource.metadata)?,743					Self::rmrk_property(License, &resource.license)?,744					Self::rmrk_property(Thumb, &resource.thumb)?,745				]746				.into_iter(),747			)?;748749			Self::deposit_event(Event::ResourceAdded {750				nft_id,751				resource_id,752			});753			Ok(())754		}755756		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]757		#[transactional]758		pub fn add_composable_resource(759			origin: OriginFor<T>,760			collection_id: RmrkCollectionId,761			nft_id: RmrkNftId,762			_resource_id: RmrkBoundedResource,763			resource: RmrkComposableResource,764		) -> DispatchResult {765			let sender = ensure_signed(origin.clone())?;766767			let resource_id = Self::resource_add(768				sender,769				Self::unique_collection_id(collection_id)?,770				nft_id.into(),771				[772					Self::rmrk_property(TokenType, &NftType::Resource)?,773					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,774					Self::rmrk_property(Parts, &resource.parts)?,775					Self::rmrk_property(Base, &resource.base)?,776					Self::rmrk_property(Src, &resource.src)?,777					Self::rmrk_property(Metadata, &resource.metadata)?,778					Self::rmrk_property(License, &resource.license)?,779					Self::rmrk_property(Thumb, &resource.thumb)?,780				]781				.into_iter(),782			)?;783784			Self::deposit_event(Event::ResourceAdded {785				nft_id,786				resource_id,787			});788			Ok(())789		}790791		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]792		#[transactional]793		pub fn add_slot_resource(794			origin: OriginFor<T>,795			collection_id: RmrkCollectionId,796			nft_id: RmrkNftId,797			resource: RmrkSlotResource,798		) -> DispatchResult {799			let sender = ensure_signed(origin.clone())?;800801			let resource_id = Self::resource_add(802				sender,803				Self::unique_collection_id(collection_id)?,804				nft_id.into(),805				[806					Self::rmrk_property(TokenType, &NftType::Resource)?,807					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,808					Self::rmrk_property(Base, &resource.base)?,809					Self::rmrk_property(Src, &resource.src)?,810					Self::rmrk_property(Metadata, &resource.metadata)?,811					Self::rmrk_property(Slot, &resource.slot)?,812					Self::rmrk_property(License, &resource.license)?,813					Self::rmrk_property(Thumb, &resource.thumb)?,814				]815				.into_iter(),816			)?;817818			Self::deposit_event(Event::ResourceAdded {819				nft_id,820				resource_id,821			});822			Ok(())823		}824825		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]826		#[transactional]827		pub fn remove_resource(828			origin: OriginFor<T>,829			collection_id: RmrkCollectionId,830			nft_id: RmrkNftId,831			resource_id: RmrkResourceId,832		) -> DispatchResult {833			let sender = ensure_signed(origin.clone())?;834835			Self::resource_remove(836				sender,837				Self::unique_collection_id(collection_id)?,838				nft_id.into(),839				resource_id.into(),840			)?;841842			Self::deposit_event(Event::ResourceRemoval {843				nft_id,844				resource_id,845			});846			Ok(())847		}848	}849}850851impl<T: Config> Pallet<T> {852	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {853		let key = rmrk_key.to_key::<T>()?;854855		let scoped_key = PropertyScope::Rmrk856			.apply(key)857			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;858859		Ok(scoped_key)860	}861862	// todo think about renaming these863	pub fn rmrk_property<E: Encode>(864		rmrk_key: RmrkProperty,865		value: &E,866	) -> Result<Property, DispatchError> {867		let key = rmrk_key.to_key::<T>()?;868869		let value = value870			.encode()871			.try_into()872			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;873874		let property = Property { key, value };875876		Ok(property)877	}878879	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {880		vec.decode()881			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())882	}883884	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>885	where886		BoundedVec<u8, S>: TryFrom<Vec<u8>>,887	{888		vec.rebind()889			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())890	}891892	fn init_collection(893		sender: T::CrossAccountId,894		data: CreateCollectionData<T::AccountId>,895		properties: impl Iterator<Item = Property>,896	) -> Result<CollectionId, DispatchError> {897		let collection_id = <PalletNft<T>>::init_collection(sender, data);898899		if let Err(DispatchError::Arithmetic(_)) = &collection_id {900			return Err(<Error<T>>::NoAvailableCollectionId.into());901		}902903		<PalletCommon<T>>::set_scoped_collection_properties(904			collection_id?,905			PropertyScope::Rmrk,906			properties,907		)?;908909		collection_id910	}911912	pub fn create_nft(913		sender: &T::CrossAccountId,914		owner: &T::CrossAccountId,915		collection: &NonfungibleHandle<T>,916		properties: impl Iterator<Item = Property>,917	) -> Result<TokenId, DispatchError> {918		let data = CreateNftExData {919			properties: BoundedVec::default(),920			owner: owner.clone(),921		};922923		let budget = budget::Value::new(NESTING_BUDGET);924925		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;926927		let nft_id = <PalletNft<T>>::current_token_id(collection.id);928929		<PalletNft<T>>::set_scoped_token_properties(930			collection.id,931			nft_id,932			PropertyScope::Rmrk,933			properties,934		)?;935936		Ok(nft_id)937	}938939	fn destroy_nft(940		sender: T::CrossAccountId,941		collection_id: CollectionId,942		token_id: TokenId,943	) -> DispatchResult {944		let collection =945			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;946947		let token_data =948			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;949950		let from = token_data.owner;951952		let budget = budget::Value::new(NESTING_BUDGET);953954		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)955	}956957	fn resource_add(958		sender: T::AccountId,959		collection_id: CollectionId,960		token_id: TokenId,961		resource_properties: impl Iterator<Item = Property>,962	) -> Result<RmrkResourceId, DispatchError> {963		let collection =964			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;965		ensure!(collection.owner == sender, Error::<T>::NoPermission);966967		let sender = T::CrossAccountId::from_sub(sender);968		let budget = budget::Value::new(NESTING_BUDGET);969970		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)971			.map_err(Self::map_unique_err_to_proxy)?;972973		let pending = sender != nft_owner;974975		let resource_collection_id: CollectionId =976			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;977		let resource_collection =978			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;979980		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them981982		let resource_id = Self::create_nft(983			&sender,984			&nft_owner,985			&resource_collection,986			resource_properties.chain(987				[988					Self::rmrk_property(PendingResourceAccept, &pending)?,989					Self::rmrk_property(PendingResourceRemoval, &false)?,990				]991				.into_iter(),992			),993		)994		.map_err(|err| match err {995			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),996			err => Self::map_unique_err_to_proxy(err),997		})?;998999		Ok(resource_id.0)1000	}10011002	fn resource_remove(1003		sender: T::AccountId,1004		collection_id: CollectionId,1005		nft_id: TokenId,1006		resource_id: TokenId,1007	) -> DispatchResult {1008		let collection =1009			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1010		ensure!(collection.owner == sender, Error::<T>::NoPermission);10111012		let resource_collection_id: CollectionId =1013			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1014		let resource_collection =1015			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1016		ensure!(1017			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1018			Error::<T>::ResourceDoesntExist1019		);10201021		let budget = up_data_structs::budget::Value::new(10);1022		let topmost_owner =1023			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;10241025		let sender = T::CrossAccountId::from_sub(sender);1026		if topmost_owner == sender {1027			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1028				.map_err(Self::map_unique_err_to_proxy)?;1029		} else {1030			<PalletNft<T>>::set_scoped_token_property(1031				resource_collection_id,1032				resource_id,1033				PropertyScope::Rmrk,1034				Self::rmrk_property(PendingResourceRemoval, &true)?,1035			)?;1036		}10371038		Ok(())1039	}10401041	fn change_collection_owner(1042		collection_id: CollectionId,1043		collection_type: misc::CollectionType,1044		sender: T::AccountId,1045		new_owner: T::AccountId,1046	) -> DispatchResult {1047		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1048		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;10491050		let mut collection = collection.into_inner();10511052		collection.owner = new_owner;1053		collection.save()1054	}10551056	fn check_collection_owner(1057		collection: &NonfungibleHandle<T>,1058		account: &T::CrossAccountId,1059	) -> DispatchResult {1060		collection1061			.check_is_owner(account)1062			.map_err(Self::map_unique_err_to_proxy)1063	}10641065	pub fn last_collection_idx() -> RmrkCollectionId {1066		<CollectionIndex<T>>::get()1067	}10681069	pub fn unique_collection_id(1070		rmrk_collection_id: RmrkCollectionId,1071	) -> Result<CollectionId, DispatchError> {1072		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1073			.map_err(|_| <Error<T>>::CollectionUnknown.into())1074	}10751076	pub fn rmrk_collection_id(1077		unique_collection_id: CollectionId,1078	) -> Result<RmrkCollectionId, DispatchError> {1079		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1080			.map_err(|_| <Error<T>>::CollectionUnknown.into())1081	}10821083	pub fn get_nft_collection(1084		collection_id: CollectionId,1085	) -> Result<NonfungibleHandle<T>, DispatchError> {1086		let collection = <CollectionHandle<T>>::try_get(collection_id)1087			.map_err(|_| <Error<T>>::CollectionUnknown)?;10881089		match collection.mode {1090			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1091			_ => Err(<Error<T>>::CollectionUnknown.into()),1092		}1093	}10941095	pub fn collection_exists(collection_id: CollectionId) -> bool {1096		<CollectionHandle<T>>::try_get(collection_id).is_ok()1097	}10981099	pub fn get_collection_property(1100		collection_id: CollectionId,1101		key: RmrkProperty,1102	) -> Result<PropertyValue, DispatchError> {1103		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1104			.get(&Self::rmrk_property_key(key)?)1105			.ok_or(<Error<T>>::CollectionUnknown)?1106			.clone();11071108		Ok(collection_property)1109	}11101111	pub fn get_collection_property_decoded<V: Decode>(1112		collection_id: CollectionId,1113		key: RmrkProperty,1114	) -> Result<V, DispatchError> {1115		Self::decode_property(Self::get_collection_property(collection_id, key)?)1116	}11171118	pub fn get_collection_type(1119		collection_id: CollectionId,1120	) -> Result<misc::CollectionType, DispatchError> {1121		Self::get_collection_property_decoded(collection_id, CollectionType)1122			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1123	}11241125	pub fn ensure_collection_type(1126		collection_id: CollectionId,1127		collection_type: misc::CollectionType,1128	) -> DispatchResult {1129		let actual_type = Self::get_collection_type(collection_id)?;1130		ensure!(1131			actual_type == collection_type,1132			<CommonError<T>>::NoPermission1133		);11341135		Ok(())1136	}11371138	pub fn get_typed_nft_collection(1139		collection_id: CollectionId,1140		collection_type: misc::CollectionType,1141	) -> Result<NonfungibleHandle<T>, DispatchError> {1142		Self::ensure_collection_type(collection_id, collection_type)?;11431144		Self::get_nft_collection(collection_id)1145	}11461147	pub fn get_typed_nft_collection_mapped(1148		rmrk_collection_id: RmrkCollectionId,1149		collection_type: misc::CollectionType,1150	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1151		let unique_collection_id = match collection_type {1152			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1153			_ => rmrk_collection_id.into(),1154		};11551156		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;11571158		Ok((collection, unique_collection_id))1159	}11601161	pub fn get_nft_property(1162		collection_id: CollectionId,1163		nft_id: TokenId,1164		key: RmrkProperty,1165	) -> Result<PropertyValue, DispatchError> {1166		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1167			.get(&Self::rmrk_property_key(key)?)1168			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1169			.clone();11701171		Ok(nft_property)1172	}11731174	pub fn get_nft_property_decoded<V: Decode>(1175		collection_id: CollectionId,1176		nft_id: TokenId,1177		key: RmrkProperty,1178	) -> Result<V, DispatchError> {1179		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1180	}11811182	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1183		<TokenData<T>>::contains_key((collection_id, nft_id))1184	}11851186	pub fn get_nft_type(1187		collection_id: CollectionId,1188		token_id: TokenId,1189	) -> Result<NftType, DispatchError> {1190		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1191			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1192	}11931194	pub fn ensure_nft_type(1195		collection_id: CollectionId,1196		token_id: TokenId,1197		nft_type: NftType,1198	) -> DispatchResult {1199		let actual_type = Self::get_nft_type(collection_id, token_id)?;1200		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);12011202		Ok(())1203	}12041205	pub fn ensure_nft_owner(1206		collection_id: CollectionId,1207		token_id: TokenId,1208		possible_owner: &T::CrossAccountId,1209		nesting_budget: &dyn budget::Budget,1210	) -> DispatchResult {1211		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1212			possible_owner.clone(),1213			collection_id,1214			token_id,1215			None,1216			nesting_budget,1217		)1218		.map_err(Self::map_unique_err_to_proxy)?;12191220		ensure!(is_owned, <Error<T>>::NoPermission);12211222		Ok(())1223	}12241225	pub fn filter_user_properties<Key, Value, R, Mapper>(1226		collection_id: CollectionId,1227		token_id: Option<TokenId>,1228		filter_keys: Option<Vec<RmrkPropertyKey>>,1229		mapper: Mapper,1230	) -> Result<Vec<R>, DispatchError>1231	where1232		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1233		Value: Decode + Default,1234		Mapper: Fn(Key, Value) -> R,1235	{1236		filter_keys1237			.map(|keys| {1238				let properties = keys1239					.into_iter()1240					.filter_map(|key| {1241						let key: Key = key.try_into().ok()?;12421243						let value = match token_id {1244							Some(token_id) => Self::get_nft_property_decoded(1245								collection_id,1246								token_id,1247								UserProperty(key.as_ref()),1248							),1249							None => Self::get_collection_property_decoded(1250								collection_id,1251								UserProperty(key.as_ref()),1252							),1253						}1254						.ok()?;12551256						Some(mapper(key, value))1257					})1258					.collect();12591260				Ok(properties)1261			})1262			.unwrap_or_else(|| {1263				let properties =1264					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();12651266				Ok(properties)1267			})1268	}12691270	pub fn iterate_user_properties<Key, Value, R, Mapper>(1271		collection_id: CollectionId,1272		token_id: Option<TokenId>,1273		mapper: Mapper,1274	) -> Result<impl Iterator<Item = R>, DispatchError>1275	where1276		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1277		Value: Decode + Default,1278		Mapper: Fn(Key, Value) -> R,1279	{1280		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;12811282		let properties = match token_id {1283			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1284			None => <PalletCommon<T>>::collection_properties(collection_id),1285		};12861287		let properties = properties.into_iter().filter_map(move |(key, value)| {1288			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;12891290			let key: Key = key.to_vec().try_into().ok()?;1291			let value: Value = value.decode().ok()?;12921293			Some(mapper(key, value))1294		});12951296		Ok(properties)1297	}12981299	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1300		map_unique_err_to_proxy! {1301			match err {1302				CommonError::NoPermission => NoPermission,1303				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1304				CommonError::PublicMintingNotAllowed => NoPermission,1305				CommonError::TokenNotFound => NoAvailableNftId,1306				CommonError::ApprovedValueTooLow => NoPermission,1307				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1308				StructureError::TokenNotFound => NoAvailableNftId,1309				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1310			}1311		}1312	}1313}
modifiedprimitives/rmrk-traits/src/lib.rsdiffbeforeafterboth
--- a/primitives/rmrk-traits/src/lib.rs
+++ b/primitives/rmrk-traits/src/lib.rs
@@ -17,9 +17,7 @@
 pub use collection::CollectionInfo;
 pub use nft::{AccountIdOrCollectionNftTuple, NftInfo, RoyaltyInfo, NftChild};
 pub use property::PropertyInfo;
-pub use resource::{
-	BasicResource, ComposableResource, ResourceInfo, ResourceTypes, SlotResource,
-};
+pub use resource::{BasicResource, ComposableResource, ResourceInfo, ResourceTypes, SlotResource};
 pub mod primitives {
 	pub type CollectionId = u32;
 	pub type ResourceId = u32;
modifiedprimitives/rmrk-traits/src/serialize.rsdiffbeforeafterboth
--- a/primitives/rmrk-traits/src/serialize.rs
+++ b/primitives/rmrk-traits/src/serialize.rs
@@ -2,30 +2,30 @@
 use serde::ser::{self, Serialize};
 
 pub mod vec {
-    use super::*;
+	use super::*;
 
-    pub fn serialize<D, V, C>(value: &C, serializer: D) -> Result<D::Ok, D::Error>
-    where
-        D: ser::Serializer,
-        V: Serialize,
-        C: AsRef<[V]>,
-    {
-        value.as_ref().serialize(serializer)
-    }
+	pub fn serialize<D, V, C>(value: &C, serializer: D) -> Result<D::Ok, D::Error>
+	where
+		D: ser::Serializer,
+		V: Serialize,
+		C: AsRef<[V]>,
+	{
+		value.as_ref().serialize(serializer)
+	}
 }
 
 pub mod opt_vec {
-    use super::*;
+	use super::*;
 
-    pub fn serialize<D, V, C>(value: &Option<C>, serializer: D) -> Result<D::Ok, D::Error>
-    where
-        D: ser::Serializer,
-        V: Serialize,
-        C: AsRef<[V]>,
-    {
-        match value {
-            Some(value) => super::vec::serialize(value, serializer),
-            None => serializer.serialize_none(),
-        }
-    }
+	pub fn serialize<D, V, C>(value: &Option<C>, serializer: D) -> Result<D::Ok, D::Error>
+	where
+		D: ser::Serializer,
+		V: Serialize,
+		C: AsRef<[V]>,
+	{
+		match value {
+			Some(value) => super::vec::serialize(value, serializer),
+			None => serializer.serialize_none(),
+		}
+	}
 }