git.delta.rocks / unique-network / refs/commits / 70623ccb06f0

difftreelog

feat(rmrk-proxy) delete-resource

Fahrrader2022-06-02parent: #e617e73.patch.diff
in: master

1 file changed

modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
before · pallets/proxy-rmrk-core/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::*;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;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::*;4142#[frame_support::pallet]43pub mod pallet {44	use super::*;45	use pallet_evm::account;4647	#[pallet::config]48	pub trait Config:49		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config50	{51		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;52	}5354	#[pallet::storage]55	#[pallet::getter(fn collection_index)]56	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5758	#[pallet::storage]59	#[pallet::getter(fn collection_index_map)]60	pub type CollectionIndexMap<T: Config> =61		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6263	#[pallet::pallet]64	#[pallet::generate_store(pub(super) trait Store)]65	pub struct Pallet<T>(_);6667	#[pallet::event]68	#[pallet::generate_deposit(pub(super) fn deposit_event)]69	pub enum Event<T: Config> {70		CollectionCreated {71			issuer: T::AccountId,72			collection_id: RmrkCollectionId,73		},74		CollectionDestroyed {75			issuer: T::AccountId,76			collection_id: RmrkCollectionId,77		},78		IssuerChanged {79			old_issuer: T::AccountId,80			new_issuer: T::AccountId,81			collection_id: RmrkCollectionId,82		},83		CollectionLocked {84			issuer: T::AccountId,85			collection_id: RmrkCollectionId,86		},87		NftMinted {88			owner: T::AccountId,89			collection_id: RmrkCollectionId,90			nft_id: RmrkNftId,91		},92		NFTBurned {93			owner: T::AccountId,94			nft_id: RmrkNftId,95		},96		PropertySet {97			collection_id: RmrkCollectionId,98			maybe_nft_id: Option<RmrkNftId>,99			key: RmrkKeyString,100			value: RmrkValueString,101		},102		ResourceAdded {103			nft_id: RmrkNftId,104			resource_id: RmrkResourceId,105		},106	}107108	#[pallet::error]109	pub enum Error<T> {110		/* Unique-specific events */111		CorruptedCollectionType,112		NftTypeEncodeError,113		RmrkPropertyKeyIsTooLong,114		RmrkPropertyValueIsTooLong,115116		/* RMRK compatible events */117		CollectionNotEmpty,118		NoAvailableCollectionId,119		NoAvailableNftId,120		CollectionUnknown,121		NoPermission,122		CollectionFullOrLocked,123		// todo add resource errors?124	}125126	#[pallet::call]127	impl<T: Config> Pallet<T> {128		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]129		#[transactional]130		pub fn create_collection(131			origin: OriginFor<T>,132			metadata: RmrkString,133			max: Option<u32>,134			symbol: RmrkCollectionSymbol,135		) -> DispatchResult {136			let sender = ensure_signed(origin)?;137138			let limits = CollectionLimits {139				owner_can_transfer: Some(false),140				token_limit: max,141				..Default::default()142			};143144			let data = CreateCollectionData {145				limits: Some(limits),146				token_prefix: symbol147					.into_inner()148					.try_into()149					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,150				..Default::default()151			};152153			<CollectionIndex<T>>::mutate(|n| *n += 1);154155			let unique_collection_id = Self::init_collection(156				T::CrossAccountId::from_sub(sender.clone()),157				data,158				[159					Self::rmrk_property(Metadata, &metadata)?,160					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,161				]162				.into_iter(),163			)?; //collection_id_res?;164			let rmrk_collection_id = <CollectionIndex<T>>::get();165166			<CollectionIndexMap<T>>::insert(rmrk_collection_id, unique_collection_id);167168			Self::deposit_event(Event::CollectionCreated {169				issuer: sender,170				collection_id: rmrk_collection_id,171			});172173			Ok(())174		}175176		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]177		#[transactional]178		pub fn destroy_collection(179			origin: OriginFor<T>,180			collection_id: RmrkCollectionId,181		) -> DispatchResult {182			let sender = ensure_signed(origin)?;183			let cross_sender = T::CrossAccountId::from_sub(sender.clone());184185			let collection = Self::get_typed_nft_collection(186				Self::unique_collection_id(collection_id)?,187				misc::CollectionType::Regular,188			)?;189190			ensure!(191				collection.total_supply() == 0,192				<Error<T>>::CollectionNotEmpty193			);194195			<PalletNft<T>>::destroy_collection(collection, &cross_sender)196				.map_err(Self::map_common_err_to_proxy)?;197198			Self::deposit_event(Event::CollectionDestroyed {199				issuer: sender,200				collection_id,201			});202203			Ok(())204		}205206		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]207		#[transactional]208		pub fn change_collection_issuer(209			origin: OriginFor<T>,210			collection_id: RmrkCollectionId,211			new_issuer: <T::Lookup as StaticLookup>::Source,212		) -> DispatchResult {213			let sender = ensure_signed(origin)?;214215			let new_issuer = T::Lookup::lookup(new_issuer)?;216217			Self::change_collection_owner(218				Self::unique_collection_id(collection_id)?,219				misc::CollectionType::Regular,220				sender.clone(),221				new_issuer.clone(),222			)?;223224			Self::deposit_event(Event::IssuerChanged {225				old_issuer: sender,226				new_issuer,227				collection_id,228			});229230			Ok(())231		}232233		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]234		#[transactional]235		pub fn lock_collection(236			origin: OriginFor<T>,237			collection_id: RmrkCollectionId,238		) -> DispatchResult {239			let sender = ensure_signed(origin)?;240			let cross_sender = T::CrossAccountId::from_sub(sender.clone());241242			let collection = Self::get_typed_nft_collection(243				Self::unique_collection_id(collection_id)?,244				misc::CollectionType::Regular,245			)?;246247			Self::check_collection_owner(&collection, &cross_sender)?;248249			let token_count = collection.total_supply();250251			let mut collection = collection.into_inner();252			collection.limits.token_limit = Some(token_count);253			collection.save()?;254255			Self::deposit_event(Event::CollectionLocked {256				issuer: sender,257				collection_id,258			});259260			Ok(())261		}262263		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]264		#[transactional]265		pub fn mint_nft(266			origin: OriginFor<T>,267			owner: T::AccountId,268			collection_id: RmrkCollectionId,269			recipient: Option<T::AccountId>,270			royalty_amount: Option<Permill>,271			metadata: RmrkString,272		) -> DispatchResult {273			let sender = ensure_signed(origin)?;274			let sender = T::CrossAccountId::from_sub(sender);275			let cross_owner = T::CrossAccountId::from_sub(owner.clone());276277			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {278				recipient: recipient.unwrap_or_else(|| owner.clone()),279				amount,280			});281282			let collection = Self::get_typed_nft_collection(283				Self::unique_collection_id(collection_id)?,284				misc::CollectionType::Regular,285			)?;286287			let nft_id = Self::create_nft(288				&sender,289				&cross_owner,290				&collection,291				[292					Self::rmrk_property(TokenType, &NftType::Regular)?,293					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,294					Self::rmrk_property(Metadata, &metadata)?,295					Self::rmrk_property(Equipped, &false)?,296					Self::rmrk_property(297						ResourceCollection,298						&Self::init_collection(299							sender.clone(),300							CreateCollectionData {301								..Default::default()302							},303							[Self::rmrk_property(304								CollectionType,305								&misc::CollectionType::Resource,306							)?]307							.into_iter(),308						)?,309					)?, // todo possibly add limits to the collection if rmrk warrants them310					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?, // todo create resource priorities?311				]312				.into_iter(),313			)314			.map_err(|err| match err {315				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),316				err => Self::map_common_err_to_proxy(err),317			})?;318319			Self::deposit_event(Event::NftMinted {320				owner,321				collection_id,322				nft_id: nft_id.0,323			});324325			Ok(())326		}327328		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]329		#[transactional]330		pub fn burn_nft(331			origin: OriginFor<T>,332			collection_id: RmrkCollectionId,333			nft_id: RmrkNftId,334		) -> DispatchResult {335			let sender = ensure_signed(origin)?;336			let cross_sender = T::CrossAccountId::from_sub(sender.clone());337338			Self::destroy_nft(339				cross_sender,340				Self::unique_collection_id(collection_id)?,341				misc::CollectionType::Regular,342				nft_id.into(),343			)?;344345			Self::deposit_event(Event::NFTBurned {346				owner: sender,347				nft_id,348			});349350			Ok(())351		}352353		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]354		#[transactional]355		pub fn set_property(356			origin: OriginFor<T>,357			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,358			maybe_nft_id: Option<RmrkNftId>,359			key: RmrkKeyString,360			value: RmrkValueString,361		) -> DispatchResult {362			let sender = ensure_signed(origin)?;363			let sender = T::CrossAccountId::from_sub(sender);364365			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;366367			match maybe_nft_id {368				Some(nft_id) => {369					let token_id: TokenId = nft_id.into();370371					Self::ensure_nft_owner(collection_id, token_id, &sender)?;372					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;373374					<PalletNft<T>>::set_scoped_token_property(375						collection_id,376						token_id,377						PropertyScope::Rmrk,378						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,379					)?;380				}381				None => {382					let collection = Self::get_typed_nft_collection(383						collection_id,384						misc::CollectionType::Regular,385					)?;386387					Self::check_collection_owner(&collection, &sender)?;388389					<PalletCommon<T>>::set_scoped_collection_property(390						collection_id,391						PropertyScope::Rmrk,392						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,393					)?;394				}395			}396397			Self::deposit_event(Event::PropertySet {398				collection_id: rmrk_collection_id,399				maybe_nft_id,400				key,401				value,402			});403404			Ok(())405		}406407		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]408		#[transactional]409		pub fn add_basic_resource(410			origin: OriginFor<T>,411			collection_id: RmrkCollectionId,412			nft_id: RmrkNftId,413			resource: RmrkBasicResource,414		) -> DispatchResult {415			let sender = ensure_signed(origin.clone())?;416417			let resource_id = Self::resource_add(418				sender,419				Self::unique_collection_id(collection_id)?,420				nft_id.into(),421				[422					Self::rmrk_property(TokenType, &NftType::Resource)?,423					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,424					Self::rmrk_property(Src, &resource.src)?,425					Self::rmrk_property(Metadata, &resource.metadata)?,426					Self::rmrk_property(License, &resource.license)?,427					Self::rmrk_property(Thumb, &resource.thumb)?,428				]429				.into_iter(),430			)?;431432			Self::deposit_event(Event::ResourceAdded {433				nft_id,434				resource_id,435			});436			Ok(())437		}438439		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]440		#[transactional]441		pub fn add_composable_resource(442			origin: OriginFor<T>,443			collection_id: RmrkCollectionId,444			nft_id: RmrkNftId,445			_resource_id: RmrkBoundedResource,446			resource: RmrkComposableResource,447		) -> DispatchResult {448			let sender = ensure_signed(origin.clone())?;449450			let resource_id = Self::resource_add(451				sender,452				Self::unique_collection_id(collection_id)?,453				nft_id.into(),454				[455					Self::rmrk_property(TokenType, &NftType::Resource)?,456					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,457					Self::rmrk_property(Parts, &resource.parts)?,458					Self::rmrk_property(Base, &resource.base)?,459					Self::rmrk_property(Src, &resource.src)?,460					Self::rmrk_property(Metadata, &resource.metadata)?,461					Self::rmrk_property(License, &resource.license)?,462					Self::rmrk_property(Thumb, &resource.thumb)?,463				]464				.into_iter(),465			)?;466467			Self::deposit_event(Event::ResourceAdded {468				nft_id,469				resource_id,470			});471			Ok(())472		}473474		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]475		#[transactional]476		pub fn add_slot_resource(477			origin: OriginFor<T>,478			collection_id: RmrkCollectionId,479			nft_id: RmrkNftId,480			resource: RmrkSlotResource,481		) -> DispatchResult {482			let sender = ensure_signed(origin.clone())?;483484			let resource_id = Self::resource_add(485				sender,486				Self::unique_collection_id(collection_id)?,487				nft_id.into(),488				[489					Self::rmrk_property(TokenType, &NftType::Resource)?,490					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,491					Self::rmrk_property(Base, &resource.base)?,492					Self::rmrk_property(Src, &resource.src)?,493					Self::rmrk_property(Metadata, &resource.metadata)?,494					Self::rmrk_property(Slot, &resource.slot)?,495					Self::rmrk_property(License, &resource.license)?,496					Self::rmrk_property(Thumb, &resource.thumb)?,497				]498				.into_iter(),499			)?;500501			Self::deposit_event(Event::ResourceAdded {502				nft_id,503				resource_id,504			});505			Ok(())506		}507	}508}509510impl<T: Config> Pallet<T> {511	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {512		let key = rmrk_key.to_key::<T>()?;513514		let scoped_key = PropertyScope::Rmrk515			.apply(key)516			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;517518		Ok(scoped_key)519	}520521	pub fn rmrk_property<E: Encode>(522		// todo think about renaming this523		rmrk_key: RmrkProperty,524		value: &E,525	) -> Result<Property, DispatchError> {526		let key = rmrk_key.to_key::<T>()?;527528		let value = value529			.encode()530			.try_into()531			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;532533		let property = Property { key, value };534535		Ok(property)536	}537538	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {539		vec.decode()540			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())541	}542543	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>544	where545		BoundedVec<u8, S>: TryFrom<Vec<u8>>,546	{547		vec.rebind()548			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())549	}550551	fn init_collection(552		sender: T::CrossAccountId,553		data: CreateCollectionData<T::AccountId>,554		properties: impl Iterator<Item = Property>,555	) -> Result<CollectionId, DispatchError> {556		let collection_id = <PalletNft<T>>::init_collection(sender, data);557558		if let Err(DispatchError::Arithmetic(_)) = &collection_id {559			return Err(<Error<T>>::NoAvailableCollectionId.into());560		}561562		<PalletCommon<T>>::set_scoped_collection_properties(563			collection_id?,564			PropertyScope::Rmrk,565			properties,566		)?;567568		collection_id569	}570571	pub fn create_nft(572		sender: &T::CrossAccountId,573		owner: &T::CrossAccountId,574		collection: &NonfungibleHandle<T>,575		properties: impl Iterator<Item = Property>,576	) -> Result<TokenId, DispatchError> {577		let data = CreateNftExData {578			properties: BoundedVec::default(),579			owner: owner.clone(),580		};581582		let budget = budget::Value::new(2);583584		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;585586		let nft_id = <PalletNft<T>>::current_token_id(collection.id);587588		<PalletNft<T>>::set_scoped_token_properties(589			collection.id,590			nft_id,591			PropertyScope::Rmrk,592			properties,593		)?;594595		Ok(nft_id)596	}597598	fn destroy_nft(599		sender: T::CrossAccountId,600		collection_id: CollectionId,601		collection_type: misc::CollectionType,602		token_id: TokenId,603	) -> DispatchResult {604		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;605606		<PalletNft<T>>::burn(&collection, &sender, token_id)607			.map_err(Self::map_common_err_to_proxy)?;608609		Ok(())610	}611612	fn resource_add(613		sender: T::AccountId,614		collection_id: CollectionId,615		token_id: TokenId,616		resource_properties: impl Iterator<Item = Property>,617	) -> Result<RmrkResourceId, DispatchError> {618		let collection =619			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;620		ensure!(collection.owner == sender, Error::<T>::NoPermission);621622		// Check NFT lock status // todo depends on market, maybe later623		//ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);624625		let sender = T::CrossAccountId::from_sub(sender);626		let budget = budget::Value::new(10);627		let pending = <PalletStructure<T>>::check_indirectly_owned(628			sender.clone(),629			collection_id,630			token_id,631			None,632			&budget,633		)?;634635		let resource_collection_id: CollectionId =636			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;637		let resource_collection =638			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;639640		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them641642		let resource_id = Self::create_nft(643			&sender, // todo owner of the nft?644			&sender,645			&resource_collection,646			resource_properties.chain(647				[648					Self::rmrk_property(PendingResourceAccept, &pending)?,649					Self::rmrk_property(PendingResourceRemoval, &false)?,650				]651				.into_iter(),652			),653		)654		.map_err(|err| match err {655			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),656			err => Self::map_common_err_to_proxy(err),657		})?;658659		Ok(resource_id.0)660	}661662	fn change_collection_owner(663		collection_id: CollectionId,664		collection_type: misc::CollectionType,665		sender: T::AccountId,666		new_owner: T::AccountId,667	) -> DispatchResult {668		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;669		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;670671		let mut collection = collection.into_inner();672673		collection.owner = new_owner;674		collection.save()675	}676677	fn check_collection_owner(678		collection: &NonfungibleHandle<T>,679		account: &T::CrossAccountId,680	) -> DispatchResult {681		collection682			.check_is_owner(account)683			.map_err(Self::map_common_err_to_proxy)684	}685686	pub fn last_collection_idx() -> RmrkCollectionId {687		<CollectionIndex<T>>::get()688	}689690	pub fn unique_collection_id(691		rmrk_collection_id: RmrkCollectionId,692	) -> Result<CollectionId, DispatchError> {693		<CollectionIndexMap<T>>::try_get(rmrk_collection_id)694			.map_err(|_| <Error<T>>::CollectionUnknown.into())695	}696697	pub fn get_nft_collection(698		collection_id: CollectionId,699	) -> Result<NonfungibleHandle<T>, DispatchError> {700		let collection = <CollectionHandle<T>>::try_get(collection_id)701			.map_err(|_| <Error<T>>::CollectionUnknown)?;702703		match collection.mode {704			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),705			_ => Err(<Error<T>>::CollectionUnknown.into()),706		}707	}708709	pub fn collection_exists(collection_id: CollectionId) -> bool {710		<CollectionHandle<T>>::try_get(collection_id).is_ok()711	}712713	pub fn get_collection_property(714		collection_id: CollectionId,715		key: RmrkProperty,716	) -> Result<PropertyValue, DispatchError> {717		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)718			.get(&Self::rmrk_property_key(key)?)719			.ok_or(<Error<T>>::CollectionUnknown)?720			.clone();721722		Ok(collection_property)723	}724725	pub fn get_collection_property_decoded<V: Decode>(726		collection_id: CollectionId,727		key: RmrkProperty,728	) -> Result<V, DispatchError> {729		Self::decode_property(Self::get_collection_property(collection_id, key)?)730	}731732	pub fn get_collection_type(733		collection_id: CollectionId,734	) -> Result<misc::CollectionType, DispatchError> {735		Self::get_collection_property_decoded(collection_id, CollectionType)736			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())737	}738739	pub fn ensure_collection_type(740		collection_id: CollectionId,741		collection_type: misc::CollectionType,742	) -> DispatchResult {743		let actual_type = Self::get_collection_type(collection_id)?;744		ensure!(745			actual_type == collection_type,746			<CommonError<T>>::NoPermission747		);748749		Ok(())750	}751752	pub fn get_typed_nft_collection(753		collection_id: CollectionId,754		collection_type: misc::CollectionType,755	) -> Result<NonfungibleHandle<T>, DispatchError> {756		Self::ensure_collection_type(collection_id, collection_type)?;757758		Self::get_nft_collection(collection_id)759	}760761	pub fn get_nft_property(762		collection_id: CollectionId,763		nft_id: TokenId,764		key: RmrkProperty,765	) -> Result<PropertyValue, DispatchError> {766		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))767			.get(&Self::rmrk_property_key(key)?)768			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?769			.clone();770771		Ok(nft_property)772	}773774	pub fn get_nft_property_decoded<V: Decode>(775		collection_id: CollectionId,776		nft_id: TokenId,777		key: RmrkProperty,778	) -> Result<V, DispatchError> {779		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)780	}781782	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {783		<TokenData<T>>::contains_key((collection_id, nft_id))784	}785786	pub fn get_nft_type(787		collection_id: CollectionId,788		token_id: TokenId,789	) -> Result<NftType, DispatchError> {790		Self::get_nft_property_decoded(collection_id, token_id, TokenType)791			.map_err(|_| <Error<T>>::NftTypeEncodeError.into())792	}793794	pub fn ensure_nft_type(795		collection_id: CollectionId,796		token_id: TokenId,797		nft_type: NftType,798	) -> DispatchResult {799		let actual_type = Self::get_nft_type(collection_id, token_id)?;800		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);801802		Ok(())803	}804805	pub fn ensure_nft_owner(806		collection_id: CollectionId,807		token_id: TokenId,808		possible_owner: &T::CrossAccountId,809	) -> DispatchResult {810		let token_data =811			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;812813		ensure!(814			token_data.owner == *possible_owner,815			<Error<T>>::NoPermission816		);817818		Ok(())819	}820821	pub fn filter_user_properties<Key, Value, R, Mapper>(822		collection_id: CollectionId,823		token_id: Option<TokenId>,824		filter_keys: Option<Vec<RmrkPropertyKey>>,825		mapper: Mapper,826	) -> Result<Vec<R>, DispatchError>827	where828		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,829		Value: Decode + Default,830		Mapper: Fn(Key, Value) -> R,831	{832		filter_keys833			.map(|keys| {834				let properties = keys835					.into_iter()836					.filter_map(|key| {837						let key: Key = key.try_into().ok()?;838839						let value = match token_id {840							Some(token_id) => Self::get_nft_property_decoded(841								collection_id,842								token_id,843								UserProperty(key.as_ref()),844							),845							None => Self::get_collection_property_decoded(846								collection_id,847								UserProperty(key.as_ref()),848							),849						}850						.ok()?;851852						Some(mapper(key, value))853					})854					.collect();855856				Ok(properties)857			})858			.unwrap_or_else(|| {859				let properties =860					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();861862				Ok(properties)863			})864	}865866	pub fn iterate_user_properties<Key, Value, R, Mapper>(867		collection_id: CollectionId,868		token_id: Option<TokenId>,869		mapper: Mapper,870	) -> Result<impl Iterator<Item = R>, DispatchError>871	where872		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,873		Value: Decode + Default,874		Mapper: Fn(Key, Value) -> R,875	{876		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;877878		let properties = match token_id {879			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),880			None => <PalletCommon<T>>::collection_properties(collection_id),881		};882883		let properties = properties.into_iter().filter_map(move |(key, value)| {884			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;885886			let key: Key = key.to_vec().try_into().ok()?;887			let value: Value = value.decode().ok()?;888889			Some(mapper(key, value))890		});891892		Ok(properties)893	}894895	fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {896		map_common_err_to_proxy! {897			match err {898				NoPermission => NoPermission,899				CollectionTokenLimitExceeded => CollectionFullOrLocked,900				PublicMintingNotAllowed => NoPermission,901				TokenNotFound => NoAvailableNftId902			}903		}904	}905}
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::*;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;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::*;4142#[frame_support::pallet]43pub mod pallet {44	use super::*;45	use pallet_evm::account;4647	#[pallet::config]48	pub trait Config:49		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config50	{51		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;52	}5354	#[pallet::storage]55	#[pallet::getter(fn collection_index)]56	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5758	#[pallet::storage]59	#[pallet::getter(fn collection_index_map)]60	pub type CollectionIndexMap<T: Config> =61		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6263	#[pallet::pallet]64	#[pallet::generate_store(pub(super) trait Store)]65	pub struct Pallet<T>(_);6667	#[pallet::event]68	#[pallet::generate_deposit(pub(super) fn deposit_event)]69	pub enum Event<T: Config> {70		CollectionCreated {71			issuer: T::AccountId,72			collection_id: RmrkCollectionId,73		},74		CollectionDestroyed {75			issuer: T::AccountId,76			collection_id: RmrkCollectionId,77		},78		IssuerChanged {79			old_issuer: T::AccountId,80			new_issuer: T::AccountId,81			collection_id: RmrkCollectionId,82		},83		CollectionLocked {84			issuer: T::AccountId,85			collection_id: RmrkCollectionId,86		},87		NftMinted {88			owner: T::AccountId,89			collection_id: RmrkCollectionId,90			nft_id: RmrkNftId,91		},92		NFTBurned {93			owner: T::AccountId,94			nft_id: RmrkNftId,95		},96		PropertySet {97			collection_id: RmrkCollectionId,98			maybe_nft_id: Option<RmrkNftId>,99			key: RmrkKeyString,100			value: RmrkValueString,101		},102		ResourceAdded {103			nft_id: RmrkNftId,104			resource_id: RmrkResourceId,105		},106		ResourceRemoval {107			nft_id: RmrkNftId,108			resource_id: RmrkResourceId,109		},110	}111112	#[pallet::error]113	pub enum Error<T> {114		/* Unique-specific events */115		CorruptedCollectionType,116		NftTypeEncodeError,117		RmrkPropertyKeyIsTooLong,118		RmrkPropertyValueIsTooLong,119120		/* RMRK compatible events */121		CollectionNotEmpty,122		NoAvailableCollectionId,123		NoAvailableNftId,124		CollectionUnknown,125		NoPermission,126		CollectionFullOrLocked,127		ResourceDoesntExist,128	}129130	#[pallet::call]131	impl<T: Config> Pallet<T> {132		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]133		#[transactional]134		pub fn create_collection(135			origin: OriginFor<T>,136			metadata: RmrkString,137			max: Option<u32>,138			symbol: RmrkCollectionSymbol,139		) -> DispatchResult {140			let sender = ensure_signed(origin)?;141142			let limits = CollectionLimits {143				owner_can_transfer: Some(false),144				token_limit: max,145				..Default::default()146			};147148			let data = CreateCollectionData {149				limits: Some(limits),150				token_prefix: symbol151					.into_inner()152					.try_into()153					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,154				..Default::default()155			};156157			<CollectionIndex<T>>::mutate(|n| *n += 1);158159			let unique_collection_id = Self::init_collection(160				T::CrossAccountId::from_sub(sender.clone()),161				data,162				[163					Self::rmrk_property(Metadata, &metadata)?,164					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,165				]166				.into_iter(),167			)?;168			let rmrk_collection_id = <CollectionIndex<T>>::get();169170			<CollectionIndexMap<T>>::insert(rmrk_collection_id, unique_collection_id);171172			Self::deposit_event(Event::CollectionCreated {173				issuer: sender,174				collection_id: rmrk_collection_id,175			});176177			Ok(())178		}179180		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]181		#[transactional]182		pub fn destroy_collection(183			origin: OriginFor<T>,184			collection_id: RmrkCollectionId,185		) -> DispatchResult {186			let sender = ensure_signed(origin)?;187			let cross_sender = T::CrossAccountId::from_sub(sender.clone());188189			let collection = Self::get_typed_nft_collection(190				Self::unique_collection_id(collection_id)?,191				misc::CollectionType::Regular,192			)?;193194			ensure!(195				collection.total_supply() == 0,196				<Error<T>>::CollectionNotEmpty197			);198199			<PalletNft<T>>::destroy_collection(collection, &cross_sender)200				.map_err(Self::map_common_err_to_proxy)?;201202			Self::deposit_event(Event::CollectionDestroyed {203				issuer: sender,204				collection_id,205			});206207			Ok(())208		}209210		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]211		#[transactional]212		pub fn change_collection_issuer(213			origin: OriginFor<T>,214			collection_id: RmrkCollectionId,215			new_issuer: <T::Lookup as StaticLookup>::Source,216		) -> DispatchResult {217			let sender = ensure_signed(origin)?;218219			let new_issuer = T::Lookup::lookup(new_issuer)?;220221			Self::change_collection_owner(222				Self::unique_collection_id(collection_id)?,223				misc::CollectionType::Regular,224				sender.clone(),225				new_issuer.clone(),226			)?;227228			Self::deposit_event(Event::IssuerChanged {229				old_issuer: sender,230				new_issuer,231				collection_id,232			});233234			Ok(())235		}236237		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]238		#[transactional]239		pub fn lock_collection(240			origin: OriginFor<T>,241			collection_id: RmrkCollectionId,242		) -> DispatchResult {243			let sender = ensure_signed(origin)?;244			let cross_sender = T::CrossAccountId::from_sub(sender.clone());245246			let collection = Self::get_typed_nft_collection(247				Self::unique_collection_id(collection_id)?,248				misc::CollectionType::Regular,249			)?;250251			Self::check_collection_owner(&collection, &cross_sender)?;252253			let token_count = collection.total_supply();254255			let mut collection = collection.into_inner();256			collection.limits.token_limit = Some(token_count);257			collection.save()?;258259			Self::deposit_event(Event::CollectionLocked {260				issuer: sender,261				collection_id,262			});263264			Ok(())265		}266267		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]268		#[transactional]269		pub fn mint_nft(270			origin: OriginFor<T>,271			owner: T::AccountId,272			collection_id: RmrkCollectionId,273			recipient: Option<T::AccountId>,274			royalty_amount: Option<Permill>,275			metadata: RmrkString,276		) -> DispatchResult {277			let sender = ensure_signed(origin)?;278			let sender = T::CrossAccountId::from_sub(sender);279			let cross_owner = T::CrossAccountId::from_sub(owner.clone());280281			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {282				recipient: recipient.unwrap_or_else(|| owner.clone()),283				amount,284			});285286			let collection = Self::get_typed_nft_collection(287				Self::unique_collection_id(collection_id)?,288				misc::CollectionType::Regular,289			)?;290291			let nft_id = Self::create_nft(292				&sender,293				&cross_owner,294				&collection,295				[296					Self::rmrk_property(TokenType, &NftType::Regular)?,297					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,298					Self::rmrk_property(Metadata, &metadata)?,299					Self::rmrk_property(Equipped, &false)?,300					Self::rmrk_property(301						ResourceCollection,302						&Self::init_collection(303							sender.clone(),304							CreateCollectionData {305								..Default::default()306							},307							[Self::rmrk_property(308								CollectionType,309								&misc::CollectionType::Resource,310							)?]311							.into_iter(),312						)?,313					)?, // todo possibly add limits to the collection if rmrk warrants them314					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,315				]316				.into_iter(),317			)318			.map_err(|err| match err {319				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),320				err => Self::map_common_err_to_proxy(err),321			})?;322323			Self::deposit_event(Event::NftMinted {324				owner,325				collection_id,326				nft_id: nft_id.0,327			});328329			Ok(())330		}331332		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]333		#[transactional]334		pub fn burn_nft(335			origin: OriginFor<T>,336			collection_id: RmrkCollectionId,337			nft_id: RmrkNftId,338		) -> DispatchResult {339			let sender = ensure_signed(origin)?;340			let cross_sender = T::CrossAccountId::from_sub(sender.clone());341342			Self::destroy_nft(343				cross_sender,344				Self::unique_collection_id(collection_id)?,345				misc::CollectionType::Regular,346				nft_id.into(),347			)?;348349			Self::deposit_event(Event::NFTBurned {350				owner: sender,351				nft_id,352			});353354			Ok(())355		}356357		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]358		#[transactional]359		pub fn set_property(360			origin: OriginFor<T>,361			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,362			maybe_nft_id: Option<RmrkNftId>,363			key: RmrkKeyString,364			value: RmrkValueString,365		) -> DispatchResult {366			let sender = ensure_signed(origin)?;367			let sender = T::CrossAccountId::from_sub(sender);368369			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;370371			match maybe_nft_id {372				Some(nft_id) => {373					let token_id: TokenId = nft_id.into();374375					Self::ensure_nft_owner(collection_id, token_id, &sender)?;376					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;377378					<PalletNft<T>>::set_scoped_token_property(379						collection_id,380						token_id,381						PropertyScope::Rmrk,382						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,383					)?;384				}385				None => {386					let collection = Self::get_typed_nft_collection(387						collection_id,388						misc::CollectionType::Regular,389					)?;390391					Self::check_collection_owner(&collection, &sender)?;392393					<PalletCommon<T>>::set_scoped_collection_property(394						collection_id,395						PropertyScope::Rmrk,396						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,397					)?;398				}399			}400401			Self::deposit_event(Event::PropertySet {402				collection_id: rmrk_collection_id,403				maybe_nft_id,404				key,405				value,406			});407408			Ok(())409		}410411		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]412		#[transactional]413		pub fn add_basic_resource(414			origin: OriginFor<T>,415			collection_id: RmrkCollectionId,416			nft_id: RmrkNftId,417			resource: RmrkBasicResource,418		) -> DispatchResult {419			let sender = ensure_signed(origin.clone())?;420421			let resource_id = Self::resource_add(422				sender,423				Self::unique_collection_id(collection_id)?,424				nft_id.into(),425				[426					Self::rmrk_property(TokenType, &NftType::Resource)?,427					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,428					Self::rmrk_property(Src, &resource.src)?,429					Self::rmrk_property(Metadata, &resource.metadata)?,430					Self::rmrk_property(License, &resource.license)?,431					Self::rmrk_property(Thumb, &resource.thumb)?,432				]433				.into_iter(),434			)?;435436			Self::deposit_event(Event::ResourceAdded {437				nft_id,438				resource_id,439			});440			Ok(())441		}442443		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]444		#[transactional]445		pub fn add_composable_resource(446			origin: OriginFor<T>,447			collection_id: RmrkCollectionId,448			nft_id: RmrkNftId,449			_resource_id: RmrkBoundedResource,450			resource: RmrkComposableResource,451		) -> DispatchResult {452			let sender = ensure_signed(origin.clone())?;453454			let resource_id = Self::resource_add(455				sender,456				Self::unique_collection_id(collection_id)?,457				nft_id.into(),458				[459					Self::rmrk_property(TokenType, &NftType::Resource)?,460					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,461					Self::rmrk_property(Parts, &resource.parts)?,462					Self::rmrk_property(Base, &resource.base)?,463					Self::rmrk_property(Src, &resource.src)?,464					Self::rmrk_property(Metadata, &resource.metadata)?,465					Self::rmrk_property(License, &resource.license)?,466					Self::rmrk_property(Thumb, &resource.thumb)?,467				]468				.into_iter(),469			)?;470471			Self::deposit_event(Event::ResourceAdded {472				nft_id,473				resource_id,474			});475			Ok(())476		}477478		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]479		#[transactional]480		pub fn add_slot_resource(481			origin: OriginFor<T>,482			collection_id: RmrkCollectionId,483			nft_id: RmrkNftId,484			resource: RmrkSlotResource,485		) -> DispatchResult {486			let sender = ensure_signed(origin.clone())?;487488			let resource_id = Self::resource_add(489				sender,490				Self::unique_collection_id(collection_id)?,491				nft_id.into(),492				[493					Self::rmrk_property(TokenType, &NftType::Resource)?,494					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,495					Self::rmrk_property(Base, &resource.base)?,496					Self::rmrk_property(Src, &resource.src)?,497					Self::rmrk_property(Metadata, &resource.metadata)?,498					Self::rmrk_property(Slot, &resource.slot)?,499					Self::rmrk_property(License, &resource.license)?,500					Self::rmrk_property(Thumb, &resource.thumb)?,501				]502				.into_iter(),503			)?;504505			Self::deposit_event(Event::ResourceAdded {506				nft_id,507				resource_id,508			});509			Ok(())510		}511512		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]513		#[transactional]514		pub fn remove_resource(515			origin: OriginFor<T>,516			collection_id: RmrkCollectionId,517			nft_id: RmrkNftId,518			resource_id: RmrkResourceId,519		) -> DispatchResult {520			let sender = ensure_signed(origin.clone())?;521522			Self::resource_remove(523				sender,524				Self::unique_collection_id(collection_id)?,525				nft_id.into(),526				resource_id.into(),527			)?;528529			Self::deposit_event(Event::ResourceRemoval {530				nft_id,531				resource_id,532			});533			Ok(())534		}535	}536}537538impl<T: Config> Pallet<T> {539	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {540		let key = rmrk_key.to_key::<T>()?;541542		let scoped_key = PropertyScope::Rmrk543			.apply(key)544			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;545546		Ok(scoped_key)547	}548549	// todo think about renaming these550	pub fn rmrk_property<E: Encode>(551		rmrk_key: RmrkProperty,552		value: &E,553	) -> Result<Property, DispatchError> {554		let key = rmrk_key.to_key::<T>()?;555556		let value = value557			.encode()558			.try_into()559			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;560561		let property = Property { key, value };562563		Ok(property)564	}565566	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {567		vec.decode()568			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())569	}570571	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>572	where573		BoundedVec<u8, S>: TryFrom<Vec<u8>>,574	{575		vec.rebind()576			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())577	}578579	fn init_collection(580		sender: T::CrossAccountId,581		data: CreateCollectionData<T::AccountId>,582		properties: impl Iterator<Item = Property>,583	) -> Result<CollectionId, DispatchError> {584		let collection_id = <PalletNft<T>>::init_collection(sender, data);585586		if let Err(DispatchError::Arithmetic(_)) = &collection_id {587			return Err(<Error<T>>::NoAvailableCollectionId.into());588		}589590		<PalletCommon<T>>::set_scoped_collection_properties(591			collection_id?,592			PropertyScope::Rmrk,593			properties,594		)?;595596		collection_id597	}598599	pub fn create_nft(600		sender: &T::CrossAccountId,601		owner: &T::CrossAccountId,602		collection: &NonfungibleHandle<T>,603		properties: impl Iterator<Item = Property>,604	) -> Result<TokenId, DispatchError> {605		let data = CreateNftExData {606			properties: BoundedVec::default(),607			owner: owner.clone(),608		};609610		let budget = budget::Value::new(2);611612		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;613614		let nft_id = <PalletNft<T>>::current_token_id(collection.id);615616		<PalletNft<T>>::set_scoped_token_properties(617			collection.id,618			nft_id,619			PropertyScope::Rmrk,620			properties,621		)?;622623		Ok(nft_id)624	}625626	fn destroy_nft(627		sender: T::CrossAccountId,628		collection_id: CollectionId,629		collection_type: misc::CollectionType,630		token_id: TokenId,631	) -> DispatchResult {632		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;633634		<PalletNft<T>>::burn(&collection, &sender, token_id)635			.map_err(Self::map_common_err_to_proxy)?;636637		Ok(())638	}639640	fn resource_add(641		sender: T::AccountId,642		collection_id: CollectionId,643		token_id: TokenId,644		resource_properties: impl Iterator<Item = Property>,645	) -> Result<RmrkResourceId, DispatchError> {646		let collection =647			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;648		ensure!(collection.owner == sender, Error::<T>::NoPermission);649650		// Check NFT lock status // todo depends on market, maybe later651		//ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);652653		let sender = T::CrossAccountId::from_sub(sender);654		let budget = budget::Value::new(10);655		let pending = !<PalletStructure<T>>::check_indirectly_owned(656			sender.clone(),657			collection_id,658			token_id,659			None,660			&budget,661		)?;662663		let resource_collection_id: CollectionId =664			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;665		let resource_collection =666			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;667668		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them669670		let resource_id = Self::create_nft(671			&sender, // todo owner of the nft?672			&sender,673			&resource_collection,674			resource_properties.chain(675				[676					Self::rmrk_property(PendingResourceAccept, &pending)?,677					Self::rmrk_property(PendingResourceRemoval, &false)?,678				]679				.into_iter(),680			),681		)682		.map_err(|err| match err {683			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),684			err => Self::map_common_err_to_proxy(err),685		})?;686687		Ok(resource_id.0)688	}689690	fn resource_remove(691		sender: T::AccountId,692		collection_id: CollectionId,693		nft_id: TokenId,694		resource_id: TokenId,695	) -> DispatchResult {696		let collection =697			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;698		ensure!(collection.owner == sender, Error::<T>::NoPermission);699700		let resource_collection_id: CollectionId =701			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;702		let resource_collection =703			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;704		ensure!(705			<PalletNft<T>>::token_exists(&resource_collection, resource_id),706			Error::<T>::ResourceDoesntExist707		);708709		let budget = up_data_structs::budget::Value::new(10);710		let topmost_owner =711			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;712713		let sender = T::CrossAccountId::from_sub(sender);714		if topmost_owner == sender {715			<PalletNft<T>>::burn(&collection, &sender, nft_id)716				.map_err(Self::map_common_err_to_proxy)?;717		} else {718			<PalletNft<T>>::set_scoped_token_property(719				collection_id,720				nft_id,721				PropertyScope::Rmrk,722				Self::rmrk_property(PendingResourceRemoval, &true)?,723			)?;724		}725726		Ok(())727	}728729	fn change_collection_owner(730		collection_id: CollectionId,731		collection_type: misc::CollectionType,732		sender: T::AccountId,733		new_owner: T::AccountId,734	) -> DispatchResult {735		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;736		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;737738		let mut collection = collection.into_inner();739740		collection.owner = new_owner;741		collection.save()742	}743744	fn check_collection_owner(745		collection: &NonfungibleHandle<T>,746		account: &T::CrossAccountId,747	) -> DispatchResult {748		collection749			.check_is_owner(account)750			.map_err(Self::map_common_err_to_proxy)751	}752753	pub fn last_collection_idx() -> RmrkCollectionId {754		<CollectionIndex<T>>::get()755	}756757	pub fn unique_collection_id(758		rmrk_collection_id: RmrkCollectionId,759	) -> Result<CollectionId, DispatchError> {760		<CollectionIndexMap<T>>::try_get(rmrk_collection_id)761			.map_err(|_| <Error<T>>::CollectionUnknown.into())762	}763764	pub fn get_nft_collection(765		collection_id: CollectionId,766	) -> Result<NonfungibleHandle<T>, DispatchError> {767		let collection = <CollectionHandle<T>>::try_get(collection_id)768			.map_err(|_| <Error<T>>::CollectionUnknown)?;769770		match collection.mode {771			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),772			_ => Err(<Error<T>>::CollectionUnknown.into()),773		}774	}775776	pub fn collection_exists(collection_id: CollectionId) -> bool {777		<CollectionHandle<T>>::try_get(collection_id).is_ok()778	}779780	pub fn get_collection_property(781		collection_id: CollectionId,782		key: RmrkProperty,783	) -> Result<PropertyValue, DispatchError> {784		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)785			.get(&Self::rmrk_property_key(key)?)786			.ok_or(<Error<T>>::CollectionUnknown)?787			.clone();788789		Ok(collection_property)790	}791792	pub fn get_collection_property_decoded<V: Decode>(793		collection_id: CollectionId,794		key: RmrkProperty,795	) -> Result<V, DispatchError> {796		Self::decode_property(Self::get_collection_property(collection_id, key)?)797	}798799	pub fn get_collection_type(800		collection_id: CollectionId,801	) -> Result<misc::CollectionType, DispatchError> {802		Self::get_collection_property_decoded(collection_id, CollectionType)803			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())804	}805806	pub fn ensure_collection_type(807		collection_id: CollectionId,808		collection_type: misc::CollectionType,809	) -> DispatchResult {810		let actual_type = Self::get_collection_type(collection_id)?;811		ensure!(812			actual_type == collection_type,813			<CommonError<T>>::NoPermission814		);815816		Ok(())817	}818819	pub fn get_typed_nft_collection(820		collection_id: CollectionId,821		collection_type: misc::CollectionType,822	) -> Result<NonfungibleHandle<T>, DispatchError> {823		Self::ensure_collection_type(collection_id, collection_type)?;824825		Self::get_nft_collection(collection_id)826	}827828	pub fn get_nft_property(829		collection_id: CollectionId,830		nft_id: TokenId,831		key: RmrkProperty,832	) -> Result<PropertyValue, DispatchError> {833		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))834			.get(&Self::rmrk_property_key(key)?)835			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?836			.clone();837838		Ok(nft_property)839	}840841	pub fn get_nft_property_decoded<V: Decode>(842		collection_id: CollectionId,843		nft_id: TokenId,844		key: RmrkProperty,845	) -> Result<V, DispatchError> {846		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)847	}848849	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {850		<TokenData<T>>::contains_key((collection_id, nft_id))851	}852853	pub fn get_nft_type(854		collection_id: CollectionId,855		token_id: TokenId,856	) -> Result<NftType, DispatchError> {857		Self::get_nft_property_decoded(collection_id, token_id, TokenType)858			.map_err(|_| <Error<T>>::NftTypeEncodeError.into())859	}860861	pub fn ensure_nft_type(862		collection_id: CollectionId,863		token_id: TokenId,864		nft_type: NftType,865	) -> DispatchResult {866		let actual_type = Self::get_nft_type(collection_id, token_id)?;867		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);868869		Ok(())870	}871872	pub fn ensure_nft_owner(873		collection_id: CollectionId,874		token_id: TokenId,875		possible_owner: &T::CrossAccountId,876	) -> DispatchResult {877		let token_data =878			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;879880		ensure!(881			token_data.owner == *possible_owner,882			<Error<T>>::NoPermission883		);884885		Ok(())886	}887888	pub fn filter_user_properties<Key, Value, R, Mapper>(889		collection_id: CollectionId,890		token_id: Option<TokenId>,891		filter_keys: Option<Vec<RmrkPropertyKey>>,892		mapper: Mapper,893	) -> Result<Vec<R>, DispatchError>894	where895		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,896		Value: Decode + Default,897		Mapper: Fn(Key, Value) -> R,898	{899		filter_keys900			.map(|keys| {901				let properties = keys902					.into_iter()903					.filter_map(|key| {904						let key: Key = key.try_into().ok()?;905906						let value = match token_id {907							Some(token_id) => Self::get_nft_property_decoded(908								collection_id,909								token_id,910								UserProperty(key.as_ref()),911							),912							None => Self::get_collection_property_decoded(913								collection_id,914								UserProperty(key.as_ref()),915							),916						}917						.ok()?;918919						Some(mapper(key, value))920					})921					.collect();922923				Ok(properties)924			})925			.unwrap_or_else(|| {926				let properties =927					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();928929				Ok(properties)930			})931	}932933	pub fn iterate_user_properties<Key, Value, R, Mapper>(934		collection_id: CollectionId,935		token_id: Option<TokenId>,936		mapper: Mapper,937	) -> Result<impl Iterator<Item = R>, DispatchError>938	where939		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,940		Value: Decode + Default,941		Mapper: Fn(Key, Value) -> R,942	{943		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;944945		let properties = match token_id {946			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),947			None => <PalletCommon<T>>::collection_properties(collection_id),948		};949950		let properties = properties.into_iter().filter_map(move |(key, value)| {951			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;952953			let key: Key = key.to_vec().try_into().ok()?;954			let value: Value = value.decode().ok()?;955956			Some(mapper(key, value))957		});958959		Ok(properties)960	}961962	fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {963		map_common_err_to_proxy! {964			match err {965				NoPermission => NoPermission,966				CollectionTokenLimitExceeded => CollectionFullOrLocked,967				PublicMintingNotAllowed => NoPermission,968				TokenNotFound => NoAvailableNftId969			}970		}971	}972}