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

difftreelog

source

pallets/proxy-rmrk-core/src/lib.rs36.3 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		NFTSent {102			sender: T::AccountId,103			recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,104			collection_id: RmrkCollectionId,105			nft_id: RmrkNftId,106			approval_required: bool,107		},108		PropertySet {109			collection_id: RmrkCollectionId,110			maybe_nft_id: Option<RmrkNftId>,111			key: RmrkKeyString,112			value: RmrkValueString,113		},114		ResourceAdded {115			nft_id: RmrkNftId,116			resource_id: RmrkResourceId,117		},118		ResourceRemoval {119			nft_id: RmrkNftId,120			resource_id: RmrkResourceId,121		},122	}123124	#[pallet::error]125	pub enum Error<T> {126		/* Unique-specific events */127		CorruptedCollectionType,128		NftTypeEncodeError,129		RmrkPropertyKeyIsTooLong,130		RmrkPropertyValueIsTooLong,131132		/* RMRK compatible events */133		CollectionNotEmpty,134		NoAvailableCollectionId,135		NoAvailableNftId,136		CollectionUnknown,137		NoPermission,138		NonTransferable,139		CollectionFullOrLocked,140		ResourceDoesntExist,141		CannotSendToDescendentOrSelf,142		CannotAcceptNonOwnedNft,143		CannotRejectNonOwnedNft,144		ResourceNotPending,145	}146147	#[pallet::call]148	impl<T: Config> Pallet<T> {149		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]150		#[transactional]151		pub fn create_collection(152			origin: OriginFor<T>,153			metadata: RmrkString,154			max: Option<u32>,155			symbol: RmrkCollectionSymbol,156		) -> DispatchResult {157			let sender = ensure_signed(origin)?;158159			let limits = CollectionLimits {160				owner_can_transfer: Some(false),161				token_limit: max,162				..Default::default()163			};164165			let data = CreateCollectionData {166				limits: Some(limits),167				token_prefix: symbol168					.into_inner()169					.try_into()170					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,171				permissions: Some(CollectionPermissions {172					nesting: Some(NestingRule::Owner),173					..Default::default()174				}),175				..Default::default()176			};177178			let unique_collection_id = Self::init_collection(179				T::CrossAccountId::from_sub(sender.clone()),180				data,181				[182					Self::rmrk_property(Metadata, &metadata)?,183					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,184				]185				.into_iter(),186			)?;187			let rmrk_collection_id = <CollectionIndex<T>>::get();188189			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);190			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);191192			<CollectionIndex<T>>::mutate(|n| *n += 1);193194			Self::deposit_event(Event::CollectionCreated {195				issuer: sender,196				collection_id: rmrk_collection_id,197			});198199			Ok(())200		}201202		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]203		#[transactional]204		pub fn destroy_collection(205			origin: OriginFor<T>,206			collection_id: RmrkCollectionId,207		) -> DispatchResult {208			let sender = ensure_signed(origin)?;209			let cross_sender = T::CrossAccountId::from_sub(sender.clone());210211			let collection = Self::get_typed_nft_collection(212				Self::unique_collection_id(collection_id)?,213				misc::CollectionType::Regular,214			)?;215216			<PalletNft<T>>::destroy_collection(collection, &cross_sender)217				.map_err(Self::map_unique_err_to_proxy)?;218219			Self::deposit_event(Event::CollectionDestroyed {220				issuer: sender,221				collection_id,222			});223224			Ok(())225		}226227		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]228		#[transactional]229		pub fn change_collection_issuer(230			origin: OriginFor<T>,231			collection_id: RmrkCollectionId,232			new_issuer: <T::Lookup as StaticLookup>::Source,233		) -> DispatchResult {234			let sender = ensure_signed(origin)?;235236			let new_issuer = T::Lookup::lookup(new_issuer)?;237238			Self::change_collection_owner(239				Self::unique_collection_id(collection_id)?,240				misc::CollectionType::Regular,241				sender.clone(),242				new_issuer.clone(),243			)?;244245			Self::deposit_event(Event::IssuerChanged {246				old_issuer: sender,247				new_issuer,248				collection_id,249			});250251			Ok(())252		}253254		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]255		#[transactional]256		pub fn lock_collection(257			origin: OriginFor<T>,258			collection_id: RmrkCollectionId,259		) -> DispatchResult {260			let sender = ensure_signed(origin)?;261			let cross_sender = T::CrossAccountId::from_sub(sender.clone());262263			let collection = Self::get_typed_nft_collection(264				Self::unique_collection_id(collection_id)?,265				misc::CollectionType::Regular,266			)?;267268			Self::check_collection_owner(&collection, &cross_sender)?;269270			let token_count = collection.total_supply();271272			let mut collection = collection.into_inner();273			collection.limits.token_limit = Some(token_count);274			collection.save()?;275276			Self::deposit_event(Event::CollectionLocked {277				issuer: sender,278				collection_id,279			});280281			Ok(())282		}283284		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]285		#[transactional]286		pub fn mint_nft(287			origin: OriginFor<T>,288			owner: T::AccountId,289			collection_id: RmrkCollectionId,290			recipient: Option<T::AccountId>,291			royalty_amount: Option<Permill>,292			metadata: RmrkString,293			transferable: bool,294		) -> DispatchResult {295			let sender = ensure_signed(origin)?;296			let sender = T::CrossAccountId::from_sub(sender);297			let cross_owner = T::CrossAccountId::from_sub(owner.clone());298299			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {300				recipient: recipient.unwrap_or_else(|| owner.clone()),301				amount,302			});303304			let collection = Self::get_typed_nft_collection(305				Self::unique_collection_id(collection_id)?,306				misc::CollectionType::Regular,307			)?;308309			let nft_id = Self::create_nft(310				&sender,311				&cross_owner,312				&collection,313				[314					Self::rmrk_property(TokenType, &NftType::Regular)?,315					Self::rmrk_property(Transferable, &transferable)?,316					Self::rmrk_property(PendingNftAccept, &false)?,317					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,318					Self::rmrk_property(Metadata, &metadata)?,319					Self::rmrk_property(Equipped, &false)?,320					Self::rmrk_property(321						ResourceCollection,322						&Self::init_collection(323							sender.clone(),324							CreateCollectionData {325								..Default::default()326							},327							[Self::rmrk_property(328								CollectionType,329								&misc::CollectionType::Resource,330							)?]331							.into_iter(),332						)?,333					)?, // todo possibly add limits to the collection if rmrk warrants them334					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,335				]336				.into_iter(),337			)338			.map_err(|err| match err {339				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),340				err => Self::map_unique_err_to_proxy(err),341			})?;342343			Self::deposit_event(Event::NftMinted {344				owner,345				collection_id,346				nft_id: nft_id.0,347			});348349			Ok(())350		}351352		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]353		#[transactional]354		pub fn burn_nft(355			origin: OriginFor<T>,356			collection_id: RmrkCollectionId,357			nft_id: RmrkNftId,358		) -> DispatchResult {359			let sender = ensure_signed(origin)?;360			let cross_sender = T::CrossAccountId::from_sub(sender.clone());361362			Self::destroy_nft(363				cross_sender,364				Self::unique_collection_id(collection_id)?,365				nft_id.into(),366			)367			.map_err(Self::map_unique_err_to_proxy)?;368369			Self::deposit_event(Event::NFTBurned {370				owner: sender,371				nft_id,372			});373374			Ok(())375		}376377		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]378		#[transactional]379		pub fn send(380			origin: OriginFor<T>,381			rmrk_collection_id: RmrkCollectionId,382			rmrk_nft_id: RmrkNftId,383			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,384		) -> DispatchResult {385			let sender = ensure_signed(origin.clone())?;386			let cross_sender = T::CrossAccountId::from_sub(sender.clone());387388			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;389			let nft_id = rmrk_nft_id.into();390391			let token_data =392				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;393394			let from = token_data.owner;395396			let collection =397				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;398399			ensure!(400				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,401				<Error<T>>::NonTransferable402			);403404			ensure!(405				!Self::get_nft_property_decoded(406					collection_id,407					nft_id,408					RmrkProperty::PendingNftAccept409				)?,410				<Error<T>>::NoPermission411			);412413			let target_owner;414			let approval_required;415416			match new_owner {417				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {418					target_owner = T::CrossAccountId::from_sub(account_id.clone());419					approval_required = false;420				}421				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(422					target_collection_id,423					target_nft_id,424				) => {425					let target_collection_id = Self::unique_collection_id(target_collection_id)?;426427					let target_nft_budget = budget::Value::new(NESTING_BUDGET);428429					let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(430						target_collection_id,431						target_nft_id.into(),432						Some((collection_id, nft_id)),433						&target_nft_budget,434					)435					.map_err(Self::map_unique_err_to_proxy)?;436437					approval_required = cross_sender != target_nft_owner;438439					if approval_required {440						target_owner = target_nft_owner;441442						<PalletNft<T>>::set_scoped_token_property(443							collection.id,444							nft_id,445							PropertyScope::Rmrk,446							Self::rmrk_property(PendingNftAccept, &approval_required)?,447						)?;448					} else {449						target_owner = T::CrossTokenAddressMapping::token_to_address(450							target_collection_id,451							target_nft_id.into(),452						);453					}454				}455			}456457			let src_nft_budget = budget::Value::new(NESTING_BUDGET);458459			<PalletNft<T>>::transfer_from(460				&collection,461				&cross_sender,462				&from,463				&target_owner,464				nft_id,465				&src_nft_budget,466			)467			.map_err(Self::map_unique_err_to_proxy)?;468469			Self::deposit_event(Event::NFTSent {470				sender,471				recipient: new_owner,472				collection_id: rmrk_collection_id,473				nft_id: rmrk_nft_id,474				approval_required,475			});476477			Ok(())478		}479480		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]481		#[transactional]482		pub fn accept_nft(483			origin: OriginFor<T>,484			rmrk_collection_id: RmrkCollectionId,485			rmrk_nft_id: RmrkNftId,486			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,487		) -> DispatchResult {488			let sender = ensure_signed(origin.clone())?;489			let cross_sender = T::CrossAccountId::from_sub(sender);490491			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;492			let nft_id = rmrk_nft_id.into();493494			let collection =495				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;496497			let new_owner = match new_owner {498				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {499					T::CrossAccountId::from_sub(account_id)500				}501				RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(502					target_collection_id,503					target_nft_id,504				) => {505					let target_collection_id = Self::unique_collection_id(target_collection_id)?;506507					T::CrossTokenAddressMapping::token_to_address(508						target_collection_id,509						TokenId(target_nft_id),510					)511				}512			};513514			let budget = budget::Value::new(NESTING_BUDGET);515516			<PalletNft<T>>::transfer(&collection, &cross_sender, &new_owner, nft_id, &budget)517				.map_err(|err| {518					if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {519						<Error<T>>::CannotAcceptNonOwnedNft.into()520					} else {521						Self::map_unique_err_to_proxy(err)522					}523				})?;524525			<PalletNft<T>>::set_scoped_token_property(526				collection.id,527				nft_id,528				PropertyScope::Rmrk,529				Self::rmrk_property(PendingNftAccept, &false)?,530			)?;531532			Ok(())533		}534535		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]536		#[transactional]537		pub fn reject_nft(538			origin: OriginFor<T>,539			collection_id: RmrkCollectionId,540			nft_id: RmrkNftId,541		) -> DispatchResult {542			let sender = ensure_signed(origin)?;543			let cross_sender = T::CrossAccountId::from_sub(sender);544545			let collection_id = Self::unique_collection_id(collection_id)?;546			let nft_id = nft_id.into();547548			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {549				if err == <CommonError<T>>::NoPermission.into()550					|| err == <CommonError<T>>::ApprovedValueTooLow.into()551				{552					<Error<T>>::CannotRejectNonOwnedNft.into()553				} else {554					Self::map_unique_err_to_proxy(err)555				}556			})?;557558			Ok(())559		}560561		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]562		#[transactional]563		pub fn accept_resource(564			origin: OriginFor<T>,565			rmrk_collection_id: RmrkCollectionId,566			rmrk_nft_id: RmrkNftId,567			rmrk_resource_id: RmrkResourceId,568		) -> DispatchResult {569			let sender = ensure_signed(origin)?;570			let cross_sender = T::CrossAccountId::from_sub(sender);571572			let collection_id = Self::unique_collection_id(rmrk_collection_id)573				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;574575			let nft_id = rmrk_nft_id.into();576			let resource_id = rmrk_resource_id.into();577578			let budget = budget::Value::new(NESTING_BUDGET);579580			let nft_owner =581				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)582					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;583584			let resource_collection_id: CollectionId =585				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)586					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;587588			let is_pending: bool = Self::get_nft_property_decoded(589				resource_collection_id,590				resource_id,591				PendingResourceAccept,592			)593			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;594595			ensure!(is_pending, <Error<T>>::ResourceNotPending);596597			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);598599			<PalletNft<T>>::set_scoped_token_property(600				resource_collection_id,601				rmrk_resource_id.into(),602				PropertyScope::Rmrk,603				Self::rmrk_property(PendingResourceAccept, &false)?,604			)?;605606			Ok(())607		}608609		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]610		#[transactional]611		pub fn accept_resource_removal(612			origin: OriginFor<T>,613			rmrk_collection_id: RmrkCollectionId,614			rmrk_nft_id: RmrkNftId,615			rmrk_resource_id: RmrkResourceId,616		) -> DispatchResult {617			let sender = ensure_signed(origin)?;618			let cross_sender = T::CrossAccountId::from_sub(sender);619620			let collection_id = Self::unique_collection_id(rmrk_collection_id)621				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;622623			let nft_id = rmrk_nft_id.into();624			let resource_id = rmrk_resource_id.into();625626			let budget = budget::Value::new(NESTING_BUDGET);627628			let nft_owner =629				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)630					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;631632			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);633634			let resource_collection_id: CollectionId =635				Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)636					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;637638			let is_pending: bool = Self::get_nft_property_decoded(639				resource_collection_id,640				resource_id,641				PendingResourceRemoval,642			)643			.map_err(|_| <Error<T>>::ResourceDoesntExist)?;644645			ensure!(is_pending, <Error<T>>::ResourceNotPending);646647			let resource_collection = Self::get_typed_nft_collection(648				resource_collection_id,649				misc::CollectionType::Resource,650			)?;651652			<PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())653				.map_err(Self::map_unique_err_to_proxy)?;654655			Ok(())656		}657658		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]659		#[transactional]660		pub fn set_property(661			origin: OriginFor<T>,662			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,663			maybe_nft_id: Option<RmrkNftId>,664			key: RmrkKeyString,665			value: RmrkValueString,666		) -> DispatchResult {667			let sender = ensure_signed(origin)?;668			let sender = T::CrossAccountId::from_sub(sender);669670			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;671			let budget = budget::Value::new(NESTING_BUDGET);672673			match maybe_nft_id {674				Some(nft_id) => {675					let token_id: TokenId = nft_id.into();676677					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;678					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;679680					<PalletNft<T>>::set_scoped_token_property(681						collection_id,682						token_id,683						PropertyScope::Rmrk,684						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,685					)?;686				}687				None => {688					let collection = Self::get_typed_nft_collection(689						collection_id,690						misc::CollectionType::Regular,691					)?;692693					Self::check_collection_owner(&collection, &sender)?;694695					<PalletCommon<T>>::set_scoped_collection_property(696						collection_id,697						PropertyScope::Rmrk,698						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,699					)?;700				}701			}702703			Self::deposit_event(Event::PropertySet {704				collection_id: rmrk_collection_id,705				maybe_nft_id,706				key,707				value,708			});709710			Ok(())711		}712713		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]714		#[transactional]715		pub fn set_priority(716			origin: OriginFor<T>,717			rmrk_collection_id: RmrkCollectionId,718			rmrk_nft_id: RmrkNftId,719			priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,720		) -> DispatchResult {721			let sender = ensure_signed(origin)?;722			let sender = T::CrossAccountId::from_sub(sender);723724			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;725			let nft_id = rmrk_nft_id.into();726			let budget = budget::Value::new(NESTING_BUDGET);727728			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;729			Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;730731			<PalletNft<T>>::set_scoped_token_property(732				collection_id,733				nft_id,734				PropertyScope::Rmrk,735				Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,736			)?;737738			Ok(())739		}740741		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]742		#[transactional]743		pub fn add_basic_resource(744			origin: OriginFor<T>,745			collection_id: RmrkCollectionId,746			nft_id: RmrkNftId,747			resource: RmrkBasicResource,748		) -> DispatchResult {749			let sender = ensure_signed(origin.clone())?;750751			let resource_id = Self::resource_add(752				sender,753				Self::unique_collection_id(collection_id)?,754				nft_id.into(),755				[756					Self::rmrk_property(TokenType, &NftType::Resource)?,757					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,758					Self::rmrk_property(Src, &resource.src)?,759					Self::rmrk_property(Metadata, &resource.metadata)?,760					Self::rmrk_property(License, &resource.license)?,761					Self::rmrk_property(Thumb, &resource.thumb)?,762				]763				.into_iter(),764			)?;765766			Self::deposit_event(Event::ResourceAdded {767				nft_id,768				resource_id,769			});770			Ok(())771		}772773		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]774		#[transactional]775		pub fn add_composable_resource(776			origin: OriginFor<T>,777			collection_id: RmrkCollectionId,778			nft_id: RmrkNftId,779			_resource_id: RmrkBoundedResource,780			resource: RmrkComposableResource,781		) -> DispatchResult {782			let sender = ensure_signed(origin.clone())?;783784			let resource_id = Self::resource_add(785				sender,786				Self::unique_collection_id(collection_id)?,787				nft_id.into(),788				[789					Self::rmrk_property(TokenType, &NftType::Resource)?,790					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,791					Self::rmrk_property(Parts, &resource.parts)?,792					Self::rmrk_property(Base, &resource.base)?,793					Self::rmrk_property(Src, &resource.src)?,794					Self::rmrk_property(Metadata, &resource.metadata)?,795					Self::rmrk_property(License, &resource.license)?,796					Self::rmrk_property(Thumb, &resource.thumb)?,797				]798				.into_iter(),799			)?;800801			Self::deposit_event(Event::ResourceAdded {802				nft_id,803				resource_id,804			});805			Ok(())806		}807808		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]809		#[transactional]810		pub fn add_slot_resource(811			origin: OriginFor<T>,812			collection_id: RmrkCollectionId,813			nft_id: RmrkNftId,814			resource: RmrkSlotResource,815		) -> DispatchResult {816			let sender = ensure_signed(origin.clone())?;817818			let resource_id = Self::resource_add(819				sender,820				Self::unique_collection_id(collection_id)?,821				nft_id.into(),822				[823					Self::rmrk_property(TokenType, &NftType::Resource)?,824					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,825					Self::rmrk_property(Base, &resource.base)?,826					Self::rmrk_property(Src, &resource.src)?,827					Self::rmrk_property(Metadata, &resource.metadata)?,828					Self::rmrk_property(Slot, &resource.slot)?,829					Self::rmrk_property(License, &resource.license)?,830					Self::rmrk_property(Thumb, &resource.thumb)?,831				]832				.into_iter(),833			)?;834835			Self::deposit_event(Event::ResourceAdded {836				nft_id,837				resource_id,838			});839			Ok(())840		}841842		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]843		#[transactional]844		pub fn remove_resource(845			origin: OriginFor<T>,846			collection_id: RmrkCollectionId,847			nft_id: RmrkNftId,848			resource_id: RmrkResourceId,849		) -> DispatchResult {850			let sender = ensure_signed(origin.clone())?;851852			Self::resource_remove(853				sender,854				Self::unique_collection_id(collection_id)?,855				nft_id.into(),856				resource_id.into(),857			)?;858859			Self::deposit_event(Event::ResourceRemoval {860				nft_id,861				resource_id,862			});863			Ok(())864		}865	}866}867868impl<T: Config> Pallet<T> {869	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {870		let key = rmrk_key.to_key::<T>()?;871872		let scoped_key = PropertyScope::Rmrk873			.apply(key)874			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;875876		Ok(scoped_key)877	}878879	// todo think about renaming these880	pub fn rmrk_property<E: Encode>(881		rmrk_key: RmrkProperty,882		value: &E,883	) -> Result<Property, DispatchError> {884		let key = rmrk_key.to_key::<T>()?;885886		let value = value887			.encode()888			.try_into()889			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;890891		let property = Property { key, value };892893		Ok(property)894	}895896	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {897		vec.decode()898			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())899	}900901	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>902	where903		BoundedVec<u8, S>: TryFrom<Vec<u8>>,904	{905		vec.rebind()906			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())907	}908909	fn init_collection(910		sender: T::CrossAccountId,911		data: CreateCollectionData<T::AccountId>,912		properties: impl Iterator<Item = Property>,913	) -> Result<CollectionId, DispatchError> {914		let collection_id = <PalletNft<T>>::init_collection(sender, data);915916		if let Err(DispatchError::Arithmetic(_)) = &collection_id {917			return Err(<Error<T>>::NoAvailableCollectionId.into());918		}919920		<PalletCommon<T>>::set_scoped_collection_properties(921			collection_id?,922			PropertyScope::Rmrk,923			properties,924		)?;925926		collection_id927	}928929	pub fn create_nft(930		sender: &T::CrossAccountId,931		owner: &T::CrossAccountId,932		collection: &NonfungibleHandle<T>,933		properties: impl Iterator<Item = Property>,934	) -> Result<TokenId, DispatchError> {935		let data = CreateNftExData {936			properties: BoundedVec::default(),937			owner: owner.clone(),938		};939940		let budget = budget::Value::new(NESTING_BUDGET);941942		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;943944		let nft_id = <PalletNft<T>>::current_token_id(collection.id);945946		<PalletNft<T>>::set_scoped_token_properties(947			collection.id,948			nft_id,949			PropertyScope::Rmrk,950			properties,951		)?;952953		Ok(nft_id)954	}955956	fn destroy_nft(957		sender: T::CrossAccountId,958		collection_id: CollectionId,959		token_id: TokenId,960	) -> DispatchResult {961		let collection =962			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;963964		let token_data =965			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;966967		let from = token_data.owner;968969		let budget = budget::Value::new(NESTING_BUDGET);970971		<PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)972	}973974	fn resource_add(975		sender: T::AccountId,976		collection_id: CollectionId,977		token_id: TokenId,978		resource_properties: impl Iterator<Item = Property>,979	) -> Result<RmrkResourceId, DispatchError> {980		let collection =981			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;982		ensure!(collection.owner == sender, Error::<T>::NoPermission);983984		let sender = T::CrossAccountId::from_sub(sender);985		let budget = budget::Value::new(NESTING_BUDGET);986987		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)988			.map_err(Self::map_unique_err_to_proxy)?;989990		let pending = sender != nft_owner;991992		let resource_collection_id: CollectionId =993			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;994		let resource_collection =995			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;996997		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them998999		let resource_id = Self::create_nft(1000			&sender,1001			&nft_owner,1002			&resource_collection,1003			resource_properties.chain(1004				[1005					Self::rmrk_property(PendingResourceAccept, &pending)?,1006					Self::rmrk_property(PendingResourceRemoval, &false)?,1007				]1008				.into_iter(),1009			),1010		)1011		.map_err(|err| match err {1012			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1013			err => Self::map_unique_err_to_proxy(err),1014		})?;10151016		Ok(resource_id.0)1017	}10181019	fn resource_remove(1020		sender: T::AccountId,1021		collection_id: CollectionId,1022		nft_id: TokenId,1023		resource_id: TokenId,1024	) -> DispatchResult {1025		let collection =1026			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1027		ensure!(collection.owner == sender, Error::<T>::NoPermission);10281029		let resource_collection_id: CollectionId =1030			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;1031		let resource_collection =1032			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1033		ensure!(1034			<PalletNft<T>>::token_exists(&resource_collection, resource_id),1035			Error::<T>::ResourceDoesntExist1036		);10371038		let budget = up_data_structs::budget::Value::new(10);1039		let topmost_owner =1040			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;10411042		let sender = T::CrossAccountId::from_sub(sender);1043		if topmost_owner == sender {1044			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1045				.map_err(Self::map_unique_err_to_proxy)?;1046		} else {1047			<PalletNft<T>>::set_scoped_token_property(1048				resource_collection_id,1049				resource_id,1050				PropertyScope::Rmrk,1051				Self::rmrk_property(PendingResourceRemoval, &true)?,1052			)?;1053		}10541055		Ok(())1056	}10571058	fn change_collection_owner(1059		collection_id: CollectionId,1060		collection_type: misc::CollectionType,1061		sender: T::AccountId,1062		new_owner: T::AccountId,1063	) -> DispatchResult {1064		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1065		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;10661067		let mut collection = collection.into_inner();10681069		collection.owner = new_owner;1070		collection.save()1071	}10721073	fn check_collection_owner(1074		collection: &NonfungibleHandle<T>,1075		account: &T::CrossAccountId,1076	) -> DispatchResult {1077		collection1078			.check_is_owner(account)1079			.map_err(Self::map_unique_err_to_proxy)1080	}10811082	pub fn last_collection_idx() -> RmrkCollectionId {1083		<CollectionIndex<T>>::get()1084	}10851086	pub fn unique_collection_id(1087		rmrk_collection_id: RmrkCollectionId,1088	) -> Result<CollectionId, DispatchError> {1089		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)1090			.map_err(|_| <Error<T>>::CollectionUnknown.into())1091	}10921093	pub fn rmrk_collection_id(1094		unique_collection_id: CollectionId,1095	) -> Result<RmrkCollectionId, DispatchError> {1096		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1097			.map_err(|_| <Error<T>>::CollectionUnknown.into())1098	}10991100	pub fn get_nft_collection(1101		collection_id: CollectionId,1102	) -> Result<NonfungibleHandle<T>, DispatchError> {1103		let collection = <CollectionHandle<T>>::try_get(collection_id)1104			.map_err(|_| <Error<T>>::CollectionUnknown)?;11051106		match collection.mode {1107			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1108			_ => Err(<Error<T>>::CollectionUnknown.into()),1109		}1110	}11111112	pub fn collection_exists(collection_id: CollectionId) -> bool {1113		<CollectionHandle<T>>::try_get(collection_id).is_ok()1114	}11151116	pub fn get_collection_property(1117		collection_id: CollectionId,1118		key: RmrkProperty,1119	) -> Result<PropertyValue, DispatchError> {1120		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1121			.get(&Self::rmrk_property_key(key)?)1122			.ok_or(<Error<T>>::CollectionUnknown)?1123			.clone();11241125		Ok(collection_property)1126	}11271128	pub fn get_collection_property_decoded<V: Decode>(1129		collection_id: CollectionId,1130		key: RmrkProperty,1131	) -> Result<V, DispatchError> {1132		Self::decode_property(Self::get_collection_property(collection_id, key)?)1133	}11341135	pub fn get_collection_type(1136		collection_id: CollectionId,1137	) -> Result<misc::CollectionType, DispatchError> {1138		Self::get_collection_property_decoded(collection_id, CollectionType)1139			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())1140	}11411142	pub fn ensure_collection_type(1143		collection_id: CollectionId,1144		collection_type: misc::CollectionType,1145	) -> DispatchResult {1146		let actual_type = Self::get_collection_type(collection_id)?;1147		ensure!(1148			actual_type == collection_type,1149			<CommonError<T>>::NoPermission1150		);11511152		Ok(())1153	}11541155	pub fn get_typed_nft_collection(1156		collection_id: CollectionId,1157		collection_type: misc::CollectionType,1158	) -> Result<NonfungibleHandle<T>, DispatchError> {1159		Self::ensure_collection_type(collection_id, collection_type)?;11601161		Self::get_nft_collection(collection_id)1162	}11631164	pub fn get_typed_nft_collection_mapped(1165		rmrk_collection_id: RmrkCollectionId,1166		collection_type: misc::CollectionType,1167	) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1168		let unique_collection_id = match collection_type {1169			misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1170			_ => rmrk_collection_id.into(),1171		};11721173		let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;11741175		Ok((collection, unique_collection_id))1176	}11771178	pub fn get_nft_property(1179		collection_id: CollectionId,1180		nft_id: TokenId,1181		key: RmrkProperty,1182	) -> Result<PropertyValue, DispatchError> {1183		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1184			.get(&Self::rmrk_property_key(key)?)1185			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1186			.clone();11871188		Ok(nft_property)1189	}11901191	pub fn get_nft_property_decoded<V: Decode>(1192		collection_id: CollectionId,1193		nft_id: TokenId,1194		key: RmrkProperty,1195	) -> Result<V, DispatchError> {1196		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1197	}11981199	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1200		<TokenData<T>>::contains_key((collection_id, nft_id))1201	}12021203	pub fn get_nft_type(1204		collection_id: CollectionId,1205		token_id: TokenId,1206	) -> Result<NftType, DispatchError> {1207		Self::get_nft_property_decoded(collection_id, token_id, TokenType)1208			.map_err(|_| <Error<T>>::NoAvailableNftId.into())1209	}12101211	pub fn ensure_nft_type(1212		collection_id: CollectionId,1213		token_id: TokenId,1214		nft_type: NftType,1215	) -> DispatchResult {1216		let actual_type = Self::get_nft_type(collection_id, token_id)?;1217		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);12181219		Ok(())1220	}12211222	pub fn ensure_nft_owner(1223		collection_id: CollectionId,1224		token_id: TokenId,1225		possible_owner: &T::CrossAccountId,1226		nesting_budget: &dyn budget::Budget,1227	) -> DispatchResult {1228		let is_owned = <PalletStructure<T>>::check_indirectly_owned(1229			possible_owner.clone(),1230			collection_id,1231			token_id,1232			None,1233			nesting_budget,1234		)1235		.map_err(Self::map_unique_err_to_proxy)?;12361237		ensure!(is_owned, <Error<T>>::NoPermission);12381239		Ok(())1240	}12411242	pub fn filter_user_properties<Key, Value, R, Mapper>(1243		collection_id: CollectionId,1244		token_id: Option<TokenId>,1245		filter_keys: Option<Vec<RmrkPropertyKey>>,1246		mapper: Mapper,1247	) -> Result<Vec<R>, DispatchError>1248	where1249		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1250		Value: Decode + Default,1251		Mapper: Fn(Key, Value) -> R,1252	{1253		filter_keys1254			.map(|keys| {1255				let properties = keys1256					.into_iter()1257					.filter_map(|key| {1258						let key: Key = key.try_into().ok()?;12591260						let value = match token_id {1261							Some(token_id) => Self::get_nft_property_decoded(1262								collection_id,1263								token_id,1264								UserProperty(key.as_ref()),1265							),1266							None => Self::get_collection_property_decoded(1267								collection_id,1268								UserProperty(key.as_ref()),1269							),1270						}1271						.ok()?;12721273						Some(mapper(key, value))1274					})1275					.collect();12761277				Ok(properties)1278			})1279			.unwrap_or_else(|| {1280				let properties =1281					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();12821283				Ok(properties)1284			})1285	}12861287	pub fn iterate_user_properties<Key, Value, R, Mapper>(1288		collection_id: CollectionId,1289		token_id: Option<TokenId>,1290		mapper: Mapper,1291	) -> Result<impl Iterator<Item = R>, DispatchError>1292	where1293		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1294		Value: Decode + Default,1295		Mapper: Fn(Key, Value) -> R,1296	{1297		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;12981299		let properties = match token_id {1300			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1301			None => <PalletCommon<T>>::collection_properties(collection_id),1302		};13031304		let properties = properties.into_iter().filter_map(move |(key, value)| {1305			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;13061307			let key: Key = key.to_vec().try_into().ok()?;1308			let value: Value = value.decode().ok()?;13091310			Some(mapper(key, value))1311		});13121313		Ok(properties)1314	}13151316	fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1317		map_unique_err_to_proxy! {1318			match err {1319				CommonError::NoPermission => NoPermission,1320				CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1321				CommonError::PublicMintingNotAllowed => NoPermission,1322				CommonError::TokenNotFound => NoAvailableNftId,1323				CommonError::ApprovedValueTooLow => NoPermission,1324				CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1325				StructureError::TokenNotFound => NoAvailableNftId,1326				StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1327			}1328		}1329	}1330}