1234567891011121314151617#![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 115 CorruptedCollectionType,116 NftTypeEncodeError,117 RmrkPropertyKeyIsTooLong,118 RmrkPropertyValueIsTooLong,119120 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 )?, 314 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 550 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 651 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 669670 let resource_id = Self::create_nft(671 &sender, 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)? 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}