git.delta.rocks / unique-network / refs/commits / 3ffbf1dba649

difftreelog

source

pallets/proxy-rmrk-core/src/lib.rs32.0 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			).map_err(Self::map_unique_err_to_proxy)?;359360			Self::deposit_event(Event::NFTBurned {361				owner: sender,362				nft_id,363			});364365			Ok(())366		}367368		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]369		#[transactional]370		pub fn send(371			origin: OriginFor<T>,372			rmrk_collection_id: RmrkCollectionId,373			rmrk_nft_id: RmrkNftId,374			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,375		) -> DispatchResult {376			let sender = ensure_signed(origin.clone())?;377			let cross_sender = T::CrossAccountId::from_sub(sender);378379			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;380			let nft_id = rmrk_nft_id.into();381382			let token_data = <TokenData<T>>::get((collection_id, nft_id))383				.ok_or(<Error<T>>::NoAvailableNftId)?;384385			let from = token_data.owner;386387			let collection = Self::get_typed_nft_collection(388				collection_id,389				misc::CollectionType::Regular,390			)?;391392			if !Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)? {393				return Err(<Error<T>>::NonTransferable.into());394			}395396			if Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)? {397				return Err(<Error<T>>::NoPermission.into());398			}399400			let target_owner;401402			match new_owner {403				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {404					target_owner = T::CrossAccountId::from_sub(account_id);405				},406				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {407					let target_collection_id = Self::unique_collection_id(target_collection_id)?;408409					let target_nft_budget = budget::Value::new(NESTING_BUDGET);410411					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(412						target_collection_id,413						target_nft_id.into(),414						Some((collection_id, nft_id)),415						&target_nft_budget,416					).map_err(Self::map_unique_err_to_proxy)?;417418					let is_approval_required = cross_sender != target_nft_owner;419420					if is_approval_required {421						target_owner = target_nft_owner;422423						<PalletNft<T>>::set_scoped_token_property(424							collection.id,425							nft_id,426							PropertyScope::Rmrk,427							Self::rmrk_property(PendingNftAccept, &is_approval_required)?,428						)?;429					} else {430						target_owner = T::CrossTokenAddressMapping::token_to_address(431							target_collection_id,432							target_nft_id.into(),433						);434					}435				}436			}437438			let src_nft_budget = budget::Value::new(NESTING_BUDGET);439440			<PalletNft<T>>::transfer_from(441				&collection,442				&cross_sender,443				&from,444				&target_owner,445				nft_id,446				&src_nft_budget447			).map_err(Self::map_unique_err_to_proxy)?;448449			Ok(())450		}451452		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]453		#[transactional]454		pub fn accept_nft(455			origin: OriginFor<T>,456			rmrk_collection_id: RmrkCollectionId,457			rmrk_nft_id: RmrkNftId,458			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,459		) -> DispatchResult {460			let sender = ensure_signed(origin.clone())?;461			let cross_sender = T::CrossAccountId::from_sub(sender);462463			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;464			let nft_id = rmrk_nft_id.into();465466			let collection = Self::get_typed_nft_collection(467				collection_id,468				misc::CollectionType::Regular,469			)?;470471			let new_owner = match new_owner {472				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {473					T::CrossAccountId::from_sub(account_id)474				},475				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {476					let target_collection_id = Self::unique_collection_id(target_collection_id)?;477478					T::CrossTokenAddressMapping::token_to_address(target_collection_id, TokenId(target_nft_id))479				}480			};481482			let budget = budget::Value::new(NESTING_BUDGET);483484			<PalletNft<T>>::transfer(485				&collection,486				&cross_sender,487				&new_owner,488				nft_id,489				&budget,490			).map_err(|err| {491				if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {492					<Error<T>>::CannotAcceptNonOwnedNft.into()493				} else {494					Self::map_unique_err_to_proxy(err)495				}496			})?;497498			<PalletNft<T>>::set_scoped_token_property(499				collection.id,500				nft_id,501				PropertyScope::Rmrk,502				Self::rmrk_property(PendingNftAccept, &false)?,503			)?;504505			Ok(())506		}507508		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]509		#[transactional]510		pub fn reject_nft(511			origin: OriginFor<T>,512			collection_id: RmrkCollectionId,513			nft_id: RmrkNftId,514		) -> DispatchResult {515			let sender = ensure_signed(origin)?;516			let cross_sender = T::CrossAccountId::from_sub(sender);517518			let collection_id = Self::unique_collection_id(collection_id)?;519			let nft_id = nft_id.into();520521			Self::destroy_nft(522				cross_sender,523				collection_id,524				nft_id525			).map_err(|err| {526				if err == <CommonError<T>>::NoPermission.into()527				|| err == <CommonError<T>>::ApprovedValueTooLow.into() {528					<Error<T>>::CannotRejectNonOwnedNft.into()529				} else {530					Self::map_unique_err_to_proxy(err)531				}532			})?;533534			Ok(())535		}536537		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]538		#[transactional]539		pub fn set_property(540			origin: OriginFor<T>,541			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,542			maybe_nft_id: Option<RmrkNftId>,543			key: RmrkKeyString,544			value: RmrkValueString,545		) -> DispatchResult {546			let sender = ensure_signed(origin)?;547			let sender = T::CrossAccountId::from_sub(sender);548549			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;550			let budget = budget::Value::new(NESTING_BUDGET);551552			match maybe_nft_id {553				Some(nft_id) => {554					let token_id: TokenId = nft_id.into();555556					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;557					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;558559					<PalletNft<T>>::set_scoped_token_property(560						collection_id,561						token_id,562						PropertyScope::Rmrk,563						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,564					)?;565				}566				None => {567					let collection = Self::get_typed_nft_collection(568						collection_id,569						misc::CollectionType::Regular,570					)?;571572					Self::check_collection_owner(&collection, &sender)?;573574					<PalletCommon<T>>::set_scoped_collection_property(575						collection_id,576						PropertyScope::Rmrk,577						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,578					)?;579				}580			}581582			Self::deposit_event(Event::PropertySet {583				collection_id: rmrk_collection_id,584				maybe_nft_id,585				key,586				value,587			});588589			Ok(())590		}591592		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]593		#[transactional]594		pub fn add_basic_resource(595			origin: OriginFor<T>,596			collection_id: RmrkCollectionId,597			nft_id: RmrkNftId,598			resource: RmrkBasicResource,599		) -> DispatchResult {600			let sender = ensure_signed(origin.clone())?;601602			let resource_id = Self::resource_add(603				sender,604				Self::unique_collection_id(collection_id)?,605				nft_id.into(),606				[607					Self::rmrk_property(TokenType, &NftType::Resource)?,608					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,609					Self::rmrk_property(Src, &resource.src)?,610					Self::rmrk_property(Metadata, &resource.metadata)?,611					Self::rmrk_property(License, &resource.license)?,612					Self::rmrk_property(Thumb, &resource.thumb)?,613				]614				.into_iter(),615			)?;616617			Self::deposit_event(Event::ResourceAdded {618				nft_id,619				resource_id,620			});621			Ok(())622		}623624		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]625		#[transactional]626		pub fn add_composable_resource(627			origin: OriginFor<T>,628			collection_id: RmrkCollectionId,629			nft_id: RmrkNftId,630			_resource_id: RmrkBoundedResource,631			resource: RmrkComposableResource,632		) -> DispatchResult {633			let sender = ensure_signed(origin.clone())?;634635			let resource_id = Self::resource_add(636				sender,637				Self::unique_collection_id(collection_id)?,638				nft_id.into(),639				[640					Self::rmrk_property(TokenType, &NftType::Resource)?,641					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,642					Self::rmrk_property(Parts, &resource.parts)?,643					Self::rmrk_property(Base, &resource.base)?,644					Self::rmrk_property(Src, &resource.src)?,645					Self::rmrk_property(Metadata, &resource.metadata)?,646					Self::rmrk_property(License, &resource.license)?,647					Self::rmrk_property(Thumb, &resource.thumb)?,648				]649				.into_iter(),650			)?;651652			Self::deposit_event(Event::ResourceAdded {653				nft_id,654				resource_id,655			});656			Ok(())657		}658659		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]660		#[transactional]661		pub fn add_slot_resource(662			origin: OriginFor<T>,663			collection_id: RmrkCollectionId,664			nft_id: RmrkNftId,665			resource: RmrkSlotResource,666		) -> DispatchResult {667			let sender = ensure_signed(origin.clone())?;668669			let resource_id = Self::resource_add(670				sender,671				Self::unique_collection_id(collection_id)?,672				nft_id.into(),673				[674					Self::rmrk_property(TokenType, &NftType::Resource)?,675					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,676					Self::rmrk_property(Base, &resource.base)?,677					Self::rmrk_property(Src, &resource.src)?,678					Self::rmrk_property(Metadata, &resource.metadata)?,679					Self::rmrk_property(Slot, &resource.slot)?,680					Self::rmrk_property(License, &resource.license)?,681					Self::rmrk_property(Thumb, &resource.thumb)?,682				]683				.into_iter(),684			)?;685686			Self::deposit_event(Event::ResourceAdded {687				nft_id,688				resource_id,689			});690			Ok(())691		}692693		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]694		#[transactional]695		pub fn remove_resource(696			origin: OriginFor<T>,697			collection_id: RmrkCollectionId,698			nft_id: RmrkNftId,699			resource_id: RmrkResourceId,700		) -> DispatchResult {701			let sender = ensure_signed(origin.clone())?;702703			Self::resource_remove(704				sender,705				Self::unique_collection_id(collection_id)?,706				nft_id.into(),707				resource_id.into(),708			)?;709710			Self::deposit_event(Event::ResourceRemoval {711				nft_id,712				resource_id,713			});714			Ok(())715		}716	}717}718719impl<T: Config> Pallet<T> {720	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {721		let key = rmrk_key.to_key::<T>()?;722723		let scoped_key = PropertyScope::Rmrk724			.apply(key)725			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;726727		Ok(scoped_key)728	}729730	// todo think about renaming these731	pub fn rmrk_property<E: Encode>(732		rmrk_key: RmrkProperty,733		value: &E,734	) -> Result<Property, DispatchError> {735		let key = rmrk_key.to_key::<T>()?;736737		let value = value738			.encode()739			.try_into()740			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;741742		let property = Property { key, value };743744		Ok(property)745	}746747	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {748		vec.decode()749			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())750	}751752	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>753	where754		BoundedVec<u8, S>: TryFrom<Vec<u8>>,755	{756		vec.rebind()757			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())758	}759760	fn init_collection(761		sender: T::CrossAccountId,762		data: CreateCollectionData<T::AccountId>,763		properties: impl Iterator<Item = Property>,764	) -> Result<CollectionId, DispatchError> {765		let collection_id = <PalletNft<T>>::init_collection(sender, data);766767		if let Err(DispatchError::Arithmetic(_)) = &collection_id {768			return Err(<Error<T>>::NoAvailableCollectionId.into());769		}770771		<PalletCommon<T>>::set_scoped_collection_properties(772			collection_id?,773			PropertyScope::Rmrk,774			properties,775		)?;776777		collection_id778	}779780	pub fn create_nft(781		sender: &T::CrossAccountId,782		owner: &T::CrossAccountId,783		collection: &NonfungibleHandle<T>,784		properties: impl Iterator<Item = Property>,785	) -> Result<TokenId, DispatchError> {786		let data = CreateNftExData {787			properties: BoundedVec::default(),788			owner: owner.clone(),789		};790791		let budget = budget::Value::new(NESTING_BUDGET);792793		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;794795		let nft_id = <PalletNft<T>>::current_token_id(collection.id);796797		<PalletNft<T>>::set_scoped_token_properties(798			collection.id,799			nft_id,800			PropertyScope::Rmrk,801			properties,802		)?;803804		Ok(nft_id)805	}806807	fn destroy_nft(808		sender: T::CrossAccountId,809		collection_id: CollectionId,810		token_id: TokenId,811	) -> DispatchResult {812		let collection = Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;813814		let token_data = <TokenData<T>>::get((collection_id, token_id))815			.ok_or(<Error<T>>::NoAvailableNftId)?;816817		let from = token_data.owner;818819		let budget = budget::Value::new(NESTING_BUDGET);820821		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)822	}823824	fn resource_add(825		sender: T::AccountId,826		collection_id: CollectionId,827		token_id: TokenId,828		resource_properties: impl Iterator<Item = Property>,829	) -> Result<RmrkResourceId, DispatchError> {830		let collection =831			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;832		ensure!(collection.owner == sender, Error::<T>::NoPermission);833834		// Check NFT lock status // todo depends on market, maybe later835		//ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);836837		let sender = T::CrossAccountId::from_sub(sender);838		let budget = budget::Value::new(NESTING_BUDGET);839		let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();840841		let resource_collection_id: CollectionId =842			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;843		let resource_collection =844			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;845846		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them847848		let resource_id = Self::create_nft(849			&sender, // todo owner of the nft?850			&sender,851			&resource_collection,852			resource_properties.chain(853				[854					Self::rmrk_property(PendingResourceAccept, &pending)?,855					Self::rmrk_property(PendingResourceRemoval, &false)?,856				]857				.into_iter(),858			),859		)860		.map_err(|err| match err {861			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),862			err => Self::map_unique_err_to_proxy(err),863		})?;864865		Ok(resource_id.0)866	}867868	fn resource_remove(869		sender: T::AccountId,870		collection_id: CollectionId,871		nft_id: TokenId,872		resource_id: TokenId,873	) -> DispatchResult {874		let collection =875			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;876		ensure!(collection.owner == sender, Error::<T>::NoPermission);877878		let resource_collection_id: CollectionId =879			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;880		let resource_collection =881			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;882		ensure!(883			<PalletNft<T>>::token_exists(&resource_collection, resource_id),884			Error::<T>::ResourceDoesntExist885		);886887		let budget = up_data_structs::budget::Value::new(10);888		let topmost_owner =889			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;890891		let sender = T::CrossAccountId::from_sub(sender);892		if topmost_owner == sender {893			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)894				.map_err(Self::map_unique_err_to_proxy)?;895		} else {896			<PalletNft<T>>::set_scoped_token_property(897				resource_collection_id,898				resource_id,899				PropertyScope::Rmrk,900				Self::rmrk_property(PendingResourceRemoval, &true)?,901			)?;902		}903904		Ok(())905	}906907	fn change_collection_owner(908		collection_id: CollectionId,909		collection_type: misc::CollectionType,910		sender: T::AccountId,911		new_owner: T::AccountId,912	) -> DispatchResult {913		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;914		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;915916		let mut collection = collection.into_inner();917918		collection.owner = new_owner;919		collection.save()920	}921922	fn check_collection_owner(923		collection: &NonfungibleHandle<T>,924		account: &T::CrossAccountId,925	) -> DispatchResult {926		collection927			.check_is_owner(account)928			.map_err(Self::map_unique_err_to_proxy)929	}930931	pub fn last_collection_idx() -> RmrkCollectionId {932		<CollectionIndex<T>>::get()933	}934935	pub fn unique_collection_id(936		rmrk_collection_id: RmrkCollectionId,937	) -> Result<CollectionId, DispatchError> {938		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)939			.map_err(|_| <Error<T>>::CollectionUnknown.into())940	}941942	pub fn rmrk_collection_id(943		unique_collection_id: CollectionId944	) -> Result<RmrkCollectionId, DispatchError> {945		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)946			.map_err(|_| <Error<T>>::CollectionUnknown.into())947	}948949	pub fn get_nft_collection(950		collection_id: CollectionId,951	) -> Result<NonfungibleHandle<T>, DispatchError> {952		let collection = <CollectionHandle<T>>::try_get(collection_id)953			.map_err(|_| <Error<T>>::CollectionUnknown)?;954955		match collection.mode {956			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),957			_ => Err(<Error<T>>::CollectionUnknown.into()),958		}959	}960961	pub fn collection_exists(collection_id: CollectionId) -> bool {962		<CollectionHandle<T>>::try_get(collection_id).is_ok()963	}964965	pub fn get_collection_property(966		collection_id: CollectionId,967		key: RmrkProperty,968	) -> Result<PropertyValue, DispatchError> {969		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)970			.get(&Self::rmrk_property_key(key)?)971			.ok_or(<Error<T>>::CollectionUnknown)?972			.clone();973974		Ok(collection_property)975	}976977	pub fn get_collection_property_decoded<V: Decode>(978		collection_id: CollectionId,979		key: RmrkProperty,980	) -> Result<V, DispatchError> {981		Self::decode_property(Self::get_collection_property(collection_id, key)?)982	}983984	pub fn get_collection_type(985		collection_id: CollectionId,986	) -> Result<misc::CollectionType, DispatchError> {987		Self::get_collection_property_decoded(collection_id, CollectionType)988			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())989	}990991	pub fn ensure_collection_type(992		collection_id: CollectionId,993		collection_type: misc::CollectionType,994	) -> DispatchResult {995		let actual_type = Self::get_collection_type(collection_id)?;996		ensure!(997			actual_type == collection_type,998			<CommonError<T>>::NoPermission999		);10001001		Ok(())1002	}10031004	pub fn get_typed_nft_collection(1005		collection_id: CollectionId,1006		collection_type: misc::CollectionType,1007	) -> Result<NonfungibleHandle<T>, DispatchError> {1008		Self::ensure_collection_type(collection_id, collection_type)?;10091010		Self::get_nft_collection(collection_id)1011	}10121013	pub fn get_typed_nft_collection_mapped(1014		rmrk_collection_id: RmrkCollectionId,1015		collection_type: misc::CollectionType,1016	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1017		let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;10181019		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;10201021		Ok((collection, unique_collection_id))1022	}10231024	pub fn get_nft_property(1025		collection_id: CollectionId,1026		nft_id: TokenId,1027		key: RmrkProperty,1028	) -> Result<PropertyValue, DispatchError> {1029		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1030			.get(&Self::rmrk_property_key(key)?)1031			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1032			.clone();10331034		Ok(nft_property)1035	}10361037	pub fn get_nft_property_decoded<V: Decode>(1038		collection_id: CollectionId,1039		nft_id: TokenId,1040		key: RmrkProperty,1041	) -> Result<V, DispatchError> {1042		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1043	}10441045	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1046		<TokenData<T>>::contains_key((collection_id, nft_id))1047	}10481049	pub fn get_nft_type(1050		collection_id: CollectionId,1051		token_id: TokenId,1052	) -> Result<NftType, DispatchError> {1053		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1054			.map_err(|_| <Error<T>>::NftTypeEncodeError.into())1055	}10561057	pub fn ensure_nft_type(1058		collection_id: CollectionId,1059		token_id: TokenId,1060		nft_type: NftType,1061	) -> DispatchResult {1062		let actual_type = Self::get_nft_type(collection_id, token_id)?;1063		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);10641065		Ok(())1066	}10671068	pub fn ensure_nft_owner(1069		collection_id: CollectionId,1070		token_id: TokenId,1071		possible_owner: &T::CrossAccountId,1072		nesting_budget: &dyn budget::Budget1073	) -> DispatchResult {1074		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1075			possible_owner.clone(),1076			collection_id,1077			token_id,1078			None,1079			nesting_budget,1080		)?;10811082		ensure!(1083			is_owned,1084			<Error<T>>::NoPermission1085		);10861087		Ok(())1088	}10891090	pub fn filter_user_properties<Key, Value, R, Mapper>(1091		collection_id: CollectionId,1092		token_id: Option<TokenId>,1093		filter_keys: Option<Vec<RmrkPropertyKey>>,1094		mapper: Mapper,1095	) -> Result<Vec<R>, DispatchError>1096	where1097		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1098		Value: Decode + Default,1099		Mapper: Fn(Key, Value) -> R,1100	{1101		filter_keys1102			.map(|keys| {1103				let properties = keys1104					.into_iter()1105					.filter_map(|key| {1106						let key: Key = key.try_into().ok()?;11071108						let value = match token_id {1109							Some(token_id) => Self::get_nft_property_decoded(1110								collection_id,1111								token_id,1112								UserProperty(key.as_ref()),1113							),1114							None => Self::get_collection_property_decoded(1115								collection_id,1116								UserProperty(key.as_ref()),1117							),1118						}1119						.ok()?;11201121						Some(mapper(key, value))1122					})1123					.collect();11241125				Ok(properties)1126			})1127			.unwrap_or_else(|| {1128				let properties =1129					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();11301131				Ok(properties)1132			})1133	}11341135	pub fn iterate_user_properties<Key, Value, R, Mapper>(1136		collection_id: CollectionId,1137		token_id: Option<TokenId>,1138		mapper: Mapper,1139	) -> Result<impl Iterator<Item = R>, DispatchError>1140	where1141		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1142		Value: Decode + Default,1143		Mapper: Fn(Key, Value) -> R,1144	{1145		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;11461147		let properties = match token_id {1148			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1149			None => <PalletCommon<T>>::collection_properties(collection_id),1150		};11511152		let properties = properties.into_iter().filter_map(move |(key, value)| {1153			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;11541155			let key: Key = key.to_vec().try_into().ok()?;1156			let value: Value = value.decode().ok()?;11571158			Some(mapper(key, value))1159		});11601161		Ok(properties)1162	}11631164	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1165		map_unique_err_to_proxy! {1166			match err {1167				CommonError::NoPermission => NoPermission,1168				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1169				CommonError::PublicMintingNotAllowed => NoPermission,1170				CommonError::TokenNotFound => NoAvailableNftId,1171				CommonError::ApprovedValueTooLow => NoPermission,1172				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1173				StructureError::TokenNotFound => NoAvailableNftId,1174				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1175			}1176		}1177	}1178}