git.delta.rocks / unique-network / refs/commits / 416d78a0ff7b

difftreelog

source

pallets/proxy-rmrk-core/src/lib.rs32.1 KiBsourcehistory
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	}138139	#[pallet::call]140	impl<T: Config> Pallet<T> {141		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]142		#[transactional]143		pub fn create_collection(144			origin: OriginFor<T>,145			metadata: RmrkString,146			max: Option<u32>,147			symbol: RmrkCollectionSymbol,148		) -> DispatchResult {149			let sender = ensure_signed(origin)?;150151			let limits = CollectionLimits {152				owner_can_transfer: Some(false),153				token_limit: max,154				..Default::default()155			};156157			let data = CreateCollectionData {158				limits: Some(limits),159				token_prefix: symbol160					.into_inner()161					.try_into()162					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,163				permissions: Some(CollectionPermissions {164					nesting: Some(NestingRule::Owner),165					..Default::default()166				}),167				..Default::default()168			};169170			let unique_collection_id = Self::init_collection(171				T::CrossAccountId::from_sub(sender.clone()),172				data,173				[174					Self::rmrk_property(Metadata, &metadata)?,175					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,176				]177				.into_iter(),178			)?;179			let rmrk_collection_id = <CollectionIndex<T>>::get();180181			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);182			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);183184			<CollectionIndex<T>>::mutate(|n| *n += 1);185186			Self::deposit_event(Event::CollectionCreated {187				issuer: sender,188				collection_id: rmrk_collection_id,189			});190191			Ok(())192		}193194		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]195		#[transactional]196		pub fn destroy_collection(197			origin: OriginFor<T>,198			collection_id: RmrkCollectionId,199		) -> DispatchResult {200			let sender = ensure_signed(origin)?;201			let cross_sender = T::CrossAccountId::from_sub(sender.clone());202203			let collection = Self::get_typed_nft_collection(204				Self::unique_collection_id(collection_id)?,205				misc::CollectionType::Regular,206			)?;207208			<PalletNft<T>>::destroy_collection(collection, &cross_sender)209				.map_err(Self::map_unique_err_to_proxy)?;210211			Self::deposit_event(Event::CollectionDestroyed {212				issuer: sender,213				collection_id,214			});215216			Ok(())217		}218219		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]220		#[transactional]221		pub fn change_collection_issuer(222			origin: OriginFor<T>,223			collection_id: RmrkCollectionId,224			new_issuer: <T::Lookup as StaticLookup>::Source,225		) -> DispatchResult {226			let sender = ensure_signed(origin)?;227228			let new_issuer = T::Lookup::lookup(new_issuer)?;229230			Self::change_collection_owner(231				Self::unique_collection_id(collection_id)?,232				misc::CollectionType::Regular,233				sender.clone(),234				new_issuer.clone(),235			)?;236237			Self::deposit_event(Event::IssuerChanged {238				old_issuer: sender,239				new_issuer,240				collection_id,241			});242243			Ok(())244		}245246		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]247		#[transactional]248		pub fn lock_collection(249			origin: OriginFor<T>,250			collection_id: RmrkCollectionId,251		) -> DispatchResult {252			let sender = ensure_signed(origin)?;253			let cross_sender = T::CrossAccountId::from_sub(sender.clone());254255			let collection = Self::get_typed_nft_collection(256				Self::unique_collection_id(collection_id)?,257				misc::CollectionType::Regular,258			)?;259260			Self::check_collection_owner(&collection, &cross_sender)?;261262			let token_count = collection.total_supply();263264			let mut collection = collection.into_inner();265			collection.limits.token_limit = Some(token_count);266			collection.save()?;267268			Self::deposit_event(Event::CollectionLocked {269				issuer: sender,270				collection_id,271			});272273			Ok(())274		}275276		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]277		#[transactional]278		pub fn mint_nft(279			origin: OriginFor<T>,280			owner: T::AccountId,281			collection_id: RmrkCollectionId,282			recipient: Option<T::AccountId>,283			royalty_amount: Option<Permill>,284			metadata: RmrkString,285			transferable: bool,286		) -> DispatchResult {287			let sender = ensure_signed(origin)?;288			let sender = T::CrossAccountId::from_sub(sender);289			let cross_owner = T::CrossAccountId::from_sub(owner.clone());290291			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {292				recipient: recipient.unwrap_or_else(|| owner.clone()),293				amount,294			});295296			let collection = Self::get_typed_nft_collection(297				Self::unique_collection_id(collection_id)?,298				misc::CollectionType::Regular,299			)?;300301			let nft_id = Self::create_nft(302				&sender,303				&cross_owner,304				&collection,305				[306					Self::rmrk_property(TokenType, &NftType::Regular)?,307					Self::rmrk_property(Transferable, &transferable)?,308					Self::rmrk_property(PendingNftAccept, &false)?,309					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,310					Self::rmrk_property(Metadata, &metadata)?,311					Self::rmrk_property(Equipped, &false)?,312					Self::rmrk_property(313						ResourceCollection,314						&Self::init_collection(315							sender.clone(),316							CreateCollectionData {317								..Default::default()318							},319							[Self::rmrk_property(320								CollectionType,321								&misc::CollectionType::Resource,322							)?]323							.into_iter(),324						)?,325					)?, // todo possibly add limits to the collection if rmrk warrants them326					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,327				]328				.into_iter(),329			)330			.map_err(|err| match err {331				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),332				err => Self::map_unique_err_to_proxy(err),333			})?;334335			Self::deposit_event(Event::NftMinted {336				owner,337				collection_id,338				nft_id: nft_id.0,339			});340341			Ok(())342		}343344		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]345		#[transactional]346		pub fn burn_nft(347			origin: OriginFor<T>,348			collection_id: RmrkCollectionId,349			nft_id: RmrkNftId,350		) -> DispatchResult {351			let sender = ensure_signed(origin)?;352			let cross_sender = T::CrossAccountId::from_sub(sender.clone());353354			Self::destroy_nft(355				cross_sender,356				Self::unique_collection_id(collection_id)?,357				nft_id.into(),358			)359			.map_err(Self::map_unique_err_to_proxy)?;360361			Self::deposit_event(Event::NFTBurned {362				owner: sender,363				nft_id,364			});365366			Ok(())367		}368369		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]370		#[transactional]371		pub fn send(372			origin: OriginFor<T>,373			rmrk_collection_id: RmrkCollectionId,374			rmrk_nft_id: RmrkNftId,375			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,376		) -> DispatchResult {377			let sender = ensure_signed(origin.clone())?;378			let cross_sender = T::CrossAccountId::from_sub(sender);379380			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;381			let nft_id = rmrk_nft_id.into();382383			let token_data =384				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;385386			let from = token_data.owner;387388			let collection =389				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;390391			if !Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)? {392				return Err(<Error<T>>::NonTransferable.into());393			}394395			if Self::get_nft_property_decoded(396				collection_id,397				nft_id,398				RmrkProperty::PendingNftAccept,399			)? {400				return Err(<Error<T>>::NoPermission.into());401			}402403			let target_owner;404405			match new_owner {406				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {407					target_owner = T::CrossAccountId::from_sub(account_id);408				}409				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(410					target_collection_id,411					target_nft_id,412				) => {413					let target_collection_id = Self::unique_collection_id(target_collection_id)?;414415					let target_nft_budget = budget::Value::new(NESTING_BUDGET);416417					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(418						target_collection_id,419						target_nft_id.into(),420						Some((collection_id, nft_id)),421						&target_nft_budget,422					)423					.map_err(Self::map_unique_err_to_proxy)?;424425					let is_approval_required = cross_sender != target_nft_owner;426427					if is_approval_required {428						target_owner = target_nft_owner;429430						<PalletNft<T>>::set_scoped_token_property(431							collection.id,432							nft_id,433							PropertyScope::Rmrk,434							Self::rmrk_property(PendingNftAccept, &is_approval_required)?,435						)?;436					} else {437						target_owner = T::CrossTokenAddressMapping::token_to_address(438							target_collection_id,439							target_nft_id.into(),440						);441					}442				}443			}444445			let src_nft_budget = budget::Value::new(NESTING_BUDGET);446447			<PalletNft<T>>::transfer_from(448				&collection,449				&cross_sender,450				&from,451				&target_owner,452				nft_id,453				&src_nft_budget,454			)455			.map_err(Self::map_unique_err_to_proxy)?;456457			Ok(())458		}459460		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]461		#[transactional]462		pub fn accept_nft(463			origin: OriginFor<T>,464			rmrk_collection_id: RmrkCollectionId,465			rmrk_nft_id: RmrkNftId,466			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,467		) -> DispatchResult {468			let sender = ensure_signed(origin.clone())?;469			let cross_sender = T::CrossAccountId::from_sub(sender);470471			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;472			let nft_id = rmrk_nft_id.into();473474			let collection =475				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;476477			let new_owner = match new_owner {478				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {479					T::CrossAccountId::from_sub(account_id)480				}481				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(482					target_collection_id,483					target_nft_id,484				) => {485					let target_collection_id = Self::unique_collection_id(target_collection_id)?;486487					T::CrossTokenAddressMapping::token_to_address(488						target_collection_id,489						TokenId(target_nft_id),490					)491				}492			};493494			let budget = budget::Value::new(NESTING_BUDGET);495496			<PalletNft<T>>::transfer(&collection, &cross_sender, &new_owner, nft_id, &budget)497				.map_err(|err| {498					if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {499						<Error<T>>::CannotAcceptNonOwnedNft.into()500					} else {501						Self::map_unique_err_to_proxy(err)502					}503				})?;504505			<PalletNft<T>>::set_scoped_token_property(506				collection.id,507				nft_id,508				PropertyScope::Rmrk,509				Self::rmrk_property(PendingNftAccept, &false)?,510			)?;511512			Ok(())513		}514515		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]516		#[transactional]517		pub fn reject_nft(518			origin: OriginFor<T>,519			collection_id: RmrkCollectionId,520			nft_id: RmrkNftId,521		) -> DispatchResult {522			let sender = ensure_signed(origin)?;523			let cross_sender = T::CrossAccountId::from_sub(sender);524525			let collection_id = Self::unique_collection_id(collection_id)?;526			let nft_id = nft_id.into();527528			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {529				if err == <CommonError<T>>::NoPermission.into()530					|| err == <CommonError<T>>::ApprovedValueTooLow.into()531				{532					<Error<T>>::CannotRejectNonOwnedNft.into()533				} else {534					Self::map_unique_err_to_proxy(err)535				}536			})?;537538			Ok(())539		}540541		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]542		#[transactional]543		pub fn set_property(544			origin: OriginFor<T>,545			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,546			maybe_nft_id: Option<RmrkNftId>,547			key: RmrkKeyString,548			value: RmrkValueString,549		) -> DispatchResult {550			let sender = ensure_signed(origin)?;551			let sender = T::CrossAccountId::from_sub(sender);552553			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;554			let budget = budget::Value::new(NESTING_BUDGET);555556			match maybe_nft_id {557				Some(nft_id) => {558					let token_id: TokenId = nft_id.into();559560					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;561					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;562563					<PalletNft<T>>::set_scoped_token_property(564						collection_id,565						token_id,566						PropertyScope::Rmrk,567						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,568					)?;569				}570				None => {571					let collection = Self::get_typed_nft_collection(572						collection_id,573						misc::CollectionType::Regular,574					)?;575576					Self::check_collection_owner(&collection, &sender)?;577578					<PalletCommon<T>>::set_scoped_collection_property(579						collection_id,580						PropertyScope::Rmrk,581						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,582					)?;583				}584			}585586			Self::deposit_event(Event::PropertySet {587				collection_id: rmrk_collection_id,588				maybe_nft_id,589				key,590				value,591			});592593			Ok(())594		}595596		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]597		#[transactional]598		pub fn add_basic_resource(599			origin: OriginFor<T>,600			collection_id: RmrkCollectionId,601			nft_id: RmrkNftId,602			resource: RmrkBasicResource,603		) -> DispatchResult {604			let sender = ensure_signed(origin.clone())?;605606			let resource_id = Self::resource_add(607				sender,608				Self::unique_collection_id(collection_id)?,609				nft_id.into(),610				[611					Self::rmrk_property(TokenType, &NftType::Resource)?,612					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,613					Self::rmrk_property(Src, &resource.src)?,614					Self::rmrk_property(Metadata, &resource.metadata)?,615					Self::rmrk_property(License, &resource.license)?,616					Self::rmrk_property(Thumb, &resource.thumb)?,617				]618				.into_iter(),619			)?;620621			Self::deposit_event(Event::ResourceAdded {622				nft_id,623				resource_id,624			});625			Ok(())626		}627628		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]629		#[transactional]630		pub fn add_composable_resource(631			origin: OriginFor<T>,632			collection_id: RmrkCollectionId,633			nft_id: RmrkNftId,634			_resource_id: RmrkBoundedResource,635			resource: RmrkComposableResource,636		) -> DispatchResult {637			let sender = ensure_signed(origin.clone())?;638639			let resource_id = Self::resource_add(640				sender,641				Self::unique_collection_id(collection_id)?,642				nft_id.into(),643				[644					Self::rmrk_property(TokenType, &NftType::Resource)?,645					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,646					Self::rmrk_property(Parts, &resource.parts)?,647					Self::rmrk_property(Base, &resource.base)?,648					Self::rmrk_property(Src, &resource.src)?,649					Self::rmrk_property(Metadata, &resource.metadata)?,650					Self::rmrk_property(License, &resource.license)?,651					Self::rmrk_property(Thumb, &resource.thumb)?,652				]653				.into_iter(),654			)?;655656			Self::deposit_event(Event::ResourceAdded {657				nft_id,658				resource_id,659			});660			Ok(())661		}662663		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]664		#[transactional]665		pub fn add_slot_resource(666			origin: OriginFor<T>,667			collection_id: RmrkCollectionId,668			nft_id: RmrkNftId,669			resource: RmrkSlotResource,670		) -> DispatchResult {671			let sender = ensure_signed(origin.clone())?;672673			let resource_id = Self::resource_add(674				sender,675				Self::unique_collection_id(collection_id)?,676				nft_id.into(),677				[678					Self::rmrk_property(TokenType, &NftType::Resource)?,679					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,680					Self::rmrk_property(Base, &resource.base)?,681					Self::rmrk_property(Src, &resource.src)?,682					Self::rmrk_property(Metadata, &resource.metadata)?,683					Self::rmrk_property(Slot, &resource.slot)?,684					Self::rmrk_property(License, &resource.license)?,685					Self::rmrk_property(Thumb, &resource.thumb)?,686				]687				.into_iter(),688			)?;689690			Self::deposit_event(Event::ResourceAdded {691				nft_id,692				resource_id,693			});694			Ok(())695		}696697		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]698		#[transactional]699		pub fn remove_resource(700			origin: OriginFor<T>,701			collection_id: RmrkCollectionId,702			nft_id: RmrkNftId,703			resource_id: RmrkResourceId,704		) -> DispatchResult {705			let sender = ensure_signed(origin.clone())?;706707			Self::resource_remove(708				sender,709				Self::unique_collection_id(collection_id)?,710				nft_id.into(),711				resource_id.into(),712			)?;713714			Self::deposit_event(Event::ResourceRemoval {715				nft_id,716				resource_id,717			});718			Ok(())719		}720	}721}722723impl<T: Config> Pallet<T> {724	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {725		let key = rmrk_key.to_key::<T>()?;726727		let scoped_key = PropertyScope::Rmrk728			.apply(key)729			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;730731		Ok(scoped_key)732	}733734	// todo think about renaming these735	pub fn rmrk_property<E: Encode>(736		rmrk_key: RmrkProperty,737		value: &E,738	) -> Result<Property, DispatchError> {739		let key = rmrk_key.to_key::<T>()?;740741		let value = value742			.encode()743			.try_into()744			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;745746		let property = Property { key, value };747748		Ok(property)749	}750751	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {752		vec.decode()753			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())754	}755756	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>757	where758		BoundedVec<u8, S>: TryFrom<Vec<u8>>,759	{760		vec.rebind()761			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())762	}763764	fn init_collection(765		sender: T::CrossAccountId,766		data: CreateCollectionData<T::AccountId>,767		properties: impl Iterator<Item = Property>,768	) -> Result<CollectionId, DispatchError> {769		let collection_id = <PalletNft<T>>::init_collection(sender, data);770771		if let Err(DispatchError::Arithmetic(_)) = &collection_id {772			return Err(<Error<T>>::NoAvailableCollectionId.into());773		}774775		<PalletCommon<T>>::set_scoped_collection_properties(776			collection_id?,777			PropertyScope::Rmrk,778			properties,779		)?;780781		collection_id782	}783784	pub fn create_nft(785		sender: &T::CrossAccountId,786		owner: &T::CrossAccountId,787		collection: &NonfungibleHandle<T>,788		properties: impl Iterator<Item = Property>,789	) -> Result<TokenId, DispatchError> {790		let data = CreateNftExData {791			properties: BoundedVec::default(),792			owner: owner.clone(),793		};794795		let budget = budget::Value::new(NESTING_BUDGET);796797		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;798799		let nft_id = <PalletNft<T>>::current_token_id(collection.id);800801		<PalletNft<T>>::set_scoped_token_properties(802			collection.id,803			nft_id,804			PropertyScope::Rmrk,805			properties,806		)?;807808		Ok(nft_id)809	}810811	fn destroy_nft(812		sender: T::CrossAccountId,813		collection_id: CollectionId,814		token_id: TokenId,815	) -> DispatchResult {816		let collection =817			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;818819		let token_data =820			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;821822		let from = token_data.owner;823824		let budget = budget::Value::new(NESTING_BUDGET);825826		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)827	}828829	fn resource_add(830		sender: T::AccountId,831		collection_id: CollectionId,832		token_id: TokenId,833		resource_properties: impl Iterator<Item = Property>,834	) -> Result<RmrkResourceId, DispatchError> {835		let collection =836			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;837		ensure!(collection.owner == sender, Error::<T>::NoPermission);838839		// Check NFT lock status // todo depends on market, maybe later840		//ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);841842		let sender = T::CrossAccountId::from_sub(sender);843		let budget = budget::Value::new(NESTING_BUDGET);844		let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();845846		let resource_collection_id: CollectionId =847			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;848		let resource_collection =849			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;850851		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them852853		let resource_id = Self::create_nft(854			&sender, // todo owner of the nft?855			&sender,856			&resource_collection,857			resource_properties.chain(858				[859					Self::rmrk_property(PendingResourceAccept, &pending)?,860					Self::rmrk_property(PendingResourceRemoval, &false)?,861				]862				.into_iter(),863			),864		)865		.map_err(|err| match err {866			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),867			err => Self::map_unique_err_to_proxy(err),868		})?;869870		Ok(resource_id.0)871	}872873	fn resource_remove(874		sender: T::AccountId,875		collection_id: CollectionId,876		nft_id: TokenId,877		resource_id: TokenId,878	) -> DispatchResult {879		let collection =880			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;881		ensure!(collection.owner == sender, Error::<T>::NoPermission);882883		let resource_collection_id: CollectionId =884			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;885		let resource_collection =886			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;887		ensure!(888			<PalletNft<T>>::token_exists(&resource_collection, resource_id),889			Error::<T>::ResourceDoesntExist890		);891892		let budget = up_data_structs::budget::Value::new(10);893		let topmost_owner =894			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;895896		let sender = T::CrossAccountId::from_sub(sender);897		if topmost_owner == sender {898			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)899				.map_err(Self::map_unique_err_to_proxy)?;900		} else {901			<PalletNft<T>>::set_scoped_token_property(902				resource_collection_id,903				resource_id,904				PropertyScope::Rmrk,905				Self::rmrk_property(PendingResourceRemoval, &true)?,906			)?;907		}908909		Ok(())910	}911912	fn change_collection_owner(913		collection_id: CollectionId,914		collection_type: misc::CollectionType,915		sender: T::AccountId,916		new_owner: T::AccountId,917	) -> DispatchResult {918		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;919		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;920921		let mut collection = collection.into_inner();922923		collection.owner = new_owner;924		collection.save()925	}926927	fn check_collection_owner(928		collection: &NonfungibleHandle<T>,929		account: &T::CrossAccountId,930	) -> DispatchResult {931		collection932			.check_is_owner(account)933			.map_err(Self::map_unique_err_to_proxy)934	}935936	pub fn last_collection_idx() -> RmrkCollectionId {937		<CollectionIndex<T>>::get()938	}939940	pub fn unique_collection_id(941		rmrk_collection_id: RmrkCollectionId,942	) -> Result<CollectionId, DispatchError> {943		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)944			.map_err(|_| <Error<T>>::CollectionUnknown.into())945	}946947	pub fn rmrk_collection_id(948		unique_collection_id: CollectionId,949	) -> Result<RmrkCollectionId, DispatchError> {950		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)951			.map_err(|_| <Error<T>>::CollectionUnknown.into())952	}953954	pub fn get_nft_collection(955		collection_id: CollectionId,956	) -> Result<NonfungibleHandle<T>, DispatchError> {957		let collection = <CollectionHandle<T>>::try_get(collection_id)958			.map_err(|_| <Error<T>>::CollectionUnknown)?;959960		match collection.mode {961			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),962			_ => Err(<Error<T>>::CollectionUnknown.into()),963		}964	}965966	pub fn collection_exists(collection_id: CollectionId) -> bool {967		<CollectionHandle<T>>::try_get(collection_id).is_ok()968	}969970	pub fn get_collection_property(971		collection_id: CollectionId,972		key: RmrkProperty,973	) -> Result<PropertyValue, DispatchError> {974		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)975			.get(&Self::rmrk_property_key(key)?)976			.ok_or(<Error<T>>::CollectionUnknown)?977			.clone();978979		Ok(collection_property)980	}981982	pub fn get_collection_property_decoded<V: Decode>(983		collection_id: CollectionId,984		key: RmrkProperty,985	) -> Result<V, DispatchError> {986		Self::decode_property(Self::get_collection_property(collection_id, key)?)987	}988989	pub fn get_collection_type(990		collection_id: CollectionId,991	) -> Result<misc::CollectionType, DispatchError> {992		Self::get_collection_property_decoded(collection_id, CollectionType)993			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())994	}995996	pub fn ensure_collection_type(997		collection_id: CollectionId,998		collection_type: misc::CollectionType,999	) -> DispatchResult {1000		let actual_type = Self::get_collection_type(collection_id)?;1001		ensure!(1002			actual_type == collection_type,1003			<CommonError<T>>::NoPermission1004		);10051006		Ok(())1007	}10081009	pub fn get_typed_nft_collection(1010		collection_id: CollectionId,1011		collection_type: misc::CollectionType,1012	) -> Result<NonfungibleHandle<T>, DispatchError> {1013		Self::ensure_collection_type(collection_id, collection_type)?;10141015		Self::get_nft_collection(collection_id)1016	}10171018	pub fn get_typed_nft_collection_mapped(1019		rmrk_collection_id: RmrkCollectionId,1020		collection_type: misc::CollectionType,1021	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1022		let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;10231024		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;10251026		Ok((collection, unique_collection_id))1027	}10281029	pub fn get_nft_property(1030		collection_id: CollectionId,1031		nft_id: TokenId,1032		key: RmrkProperty,1033	) -> Result<PropertyValue, DispatchError> {1034		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1035			.get(&Self::rmrk_property_key(key)?)1036			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1037			.clone();10381039		Ok(nft_property)1040	}10411042	pub fn get_nft_property_decoded<V: Decode>(1043		collection_id: CollectionId,1044		nft_id: TokenId,1045		key: RmrkProperty,1046	) -> Result<V, DispatchError> {1047		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1048	}10491050	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1051		<TokenData<T>>::contains_key((collection_id, nft_id))1052	}10531054	pub fn get_nft_type(1055		collection_id: CollectionId,1056		token_id: TokenId,1057	) -> Result<NftType, DispatchError> {1058		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1059			.map_err(|_| <Error<T>>::NftTypeEncodeError.into())1060	}10611062	pub fn ensure_nft_type(1063		collection_id: CollectionId,1064		token_id: TokenId,1065		nft_type: NftType,1066	) -> DispatchResult {1067		let actual_type = Self::get_nft_type(collection_id, token_id)?;1068		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);10691070		Ok(())1071	}10721073	pub fn ensure_nft_owner(1074		collection_id: CollectionId,1075		token_id: TokenId,1076		possible_owner: &T::CrossAccountId,1077		nesting_budget: &dyn budget::Budget,1078	) -> DispatchResult {1079		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1080			possible_owner.clone(),1081			collection_id,1082			token_id,1083			None,1084			nesting_budget,1085		)?;10861087		ensure!(is_owned, <Error<T>>::NoPermission);10881089		Ok(())1090	}10911092	pub fn filter_user_properties<Key, Value, R, Mapper>(1093		collection_id: CollectionId,1094		token_id: Option<TokenId>,1095		filter_keys: Option<Vec<RmrkPropertyKey>>,1096		mapper: Mapper,1097	) -> Result<Vec<R>, DispatchError>1098	where1099		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1100		Value: Decode + Default,1101		Mapper: Fn(Key, Value) -> R,1102	{1103		filter_keys1104			.map(|keys| {1105				let properties = keys1106					.into_iter()1107					.filter_map(|key| {1108						let key: Key = key.try_into().ok()?;11091110						let value = match token_id {1111							Some(token_id) => Self::get_nft_property_decoded(1112								collection_id,1113								token_id,1114								UserProperty(key.as_ref()),1115							),1116							None => Self::get_collection_property_decoded(1117								collection_id,1118								UserProperty(key.as_ref()),1119							),1120						}1121						.ok()?;11221123						Some(mapper(key, value))1124					})1125					.collect();11261127				Ok(properties)1128			})1129			.unwrap_or_else(|| {1130				let properties =1131					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();11321133				Ok(properties)1134			})1135	}11361137	pub fn iterate_user_properties<Key, Value, R, Mapper>(1138		collection_id: CollectionId,1139		token_id: Option<TokenId>,1140		mapper: Mapper,1141	) -> Result<impl Iterator<Item = R>, DispatchError>1142	where1143		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1144		Value: Decode + Default,1145		Mapper: Fn(Key, Value) -> R,1146	{1147		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;11481149		let properties = match token_id {1150			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1151			None => <PalletCommon<T>>::collection_properties(collection_id),1152		};11531154		let properties = properties.into_iter().filter_map(move |(key, value)| {1155			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;11561157			let key: Key = key.to_vec().try_into().ok()?;1158			let value: Value = value.decode().ok()?;11591160			Some(mapper(key, value))1161		});11621163		Ok(properties)1164	}11651166	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1167		map_unique_err_to_proxy! {1168			match err {1169				CommonError::NoPermission => NoPermission,1170				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1171				CommonError::PublicMintingNotAllowed => NoPermission,1172				CommonError::TokenNotFound => NoAvailableNftId,1173				CommonError::ApprovedValueTooLow => NoPermission,1174				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1175				StructureError::TokenNotFound => NoAvailableNftId,1176				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1177			}1178		}1179	}1180}