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 }107108 #[pallet::error]109 pub enum Error<T> {110 111 CorruptedCollectionType,112 NftTypeEncodeError,113 RmrkPropertyKeyIsTooLong,114 RmrkPropertyValueIsTooLong, 115116 117 CollectionNotEmpty,118 NoAvailableCollectionId,119 NoAvailableNftId,120 CollectionUnknown,121 NoPermission,122 CollectionFullOrLocked,123 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 )?; 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 )?, 310 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?, 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 rmrk_key: RmrkProperty,523 value: &E,524 ) -> Result<Property, DispatchError> {525 let key = rmrk_key.to_key::<T>()?;526527 let value = value528 .encode()529 .try_into()530 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;531532 let property = Property { key, value };533534 Ok(property)535 }536537 fn init_collection(538 sender: T::CrossAccountId,539 data: CreateCollectionData<T::AccountId>,540 properties: impl Iterator<Item = Property>,541 ) -> Result<CollectionId, DispatchError> {542 let collection_id = <PalletNft<T>>::init_collection(sender, data);543544 if let Err(DispatchError::Arithmetic(_)) = &collection_id {545 return Err(<Error<T>>::NoAvailableCollectionId.into());546 }547548 <PalletCommon<T>>::set_scoped_collection_properties(549 collection_id?,550 PropertyScope::Rmrk,551 properties,552 )?;553554 collection_id555 }556557 pub fn create_nft(558 sender: &T::CrossAccountId,559 owner: &T::CrossAccountId,560 collection: &NonfungibleHandle<T>,561 properties: impl Iterator<Item = Property>,562 ) -> Result<TokenId, DispatchError> {563 let data = CreateNftExData {564 properties: BoundedVec::default(),565 owner: owner.clone(),566 };567568 let budget = budget::Value::new(2);569570 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;571572 let nft_id = <PalletNft<T>>::current_token_id(collection.id);573574 <PalletNft<T>>::set_scoped_token_properties(575 collection.id,576 nft_id,577 PropertyScope::Rmrk,578 properties,579 )?;580581 Ok(nft_id)582 }583584 fn destroy_nft(585 sender: T::CrossAccountId,586 collection_id: CollectionId,587 collection_type: misc::CollectionType,588 token_id: TokenId,589 ) -> DispatchResult {590 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;591592 <PalletNft<T>>::burn(&collection, &sender, token_id)593 .map_err(Self::map_common_err_to_proxy)?;594595 Ok(())596 }597598 fn resource_add(599 sender: T::AccountId,600 collection_id: CollectionId,601 token_id: TokenId,602 resource_properties: impl Iterator<Item = Property>,603 ) -> Result<RmrkResourceId, DispatchError> {604 let collection =605 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;606 ensure!(collection.owner == sender, Error::<T>::NoPermission);607608 609 610611 let sender = T::CrossAccountId::from_sub(sender);612 let budget = budget::Value::new(10);613 let pending = <PalletStructure<T>>::check_indirectly_owned(614 sender.clone(),615 collection_id,616 token_id,617 None,618 &budget,619 )?;620621 let resource_collection_id: CollectionId =622 Self::get_nft_property(collection_id, token_id, ResourceCollection)?623 .decode_or_default();624 let resource_collection =625 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;626627 628629 let resource_id = Self::create_nft(630 &sender, 631 &sender,632 &resource_collection,633 resource_properties.chain(634 [635 Self::rmrk_property(PendingResourceAccept, &pending)?,636 Self::rmrk_property(PendingResourceRemoval, &false)?,637 ]638 .into_iter(),639 ),640 )641 .map_err(|err| match err {642 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),643 err => Self::map_common_err_to_proxy(err),644 })?;645646 Ok(resource_id.0)647 }648649 fn change_collection_owner(650 collection_id: CollectionId,651 collection_type: misc::CollectionType,652 sender: T::AccountId,653 new_owner: T::AccountId,654 ) -> DispatchResult {655 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;656 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;657658 let mut collection = collection.into_inner();659660 collection.owner = new_owner;661 collection.save()662 }663664 fn check_collection_owner(665 collection: &NonfungibleHandle<T>,666 account: &T::CrossAccountId,667 ) -> DispatchResult {668 collection669 .check_is_owner(account)670 .map_err(Self::map_common_err_to_proxy)671 }672673 pub fn last_collection_idx() -> RmrkCollectionId {674 <CollectionIndex<T>>::get()675 }676677 pub fn unique_collection_id(678 rmrk_collection_id: RmrkCollectionId,679 ) -> Result<CollectionId, DispatchError> {680 <CollectionIndexMap<T>>::try_get(rmrk_collection_id)681 .map_err(|_| <Error<T>>::CollectionUnknown.into())682 }683684 pub fn get_nft_collection(685 collection_id: CollectionId,686 ) -> Result<NonfungibleHandle<T>, DispatchError> {687 let collection = <CollectionHandle<T>>::try_get(collection_id)688 .map_err(|_| <Error<T>>::CollectionUnknown)?;689690 match collection.mode {691 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),692 _ => Err(<Error<T>>::CollectionUnknown.into()),693 }694 }695696 pub fn collection_exists(collection_id: CollectionId) -> bool {697 <CollectionHandle<T>>::try_get(collection_id).is_ok()698 }699700 pub fn get_collection_property(701 collection_id: CollectionId,702 key: RmrkProperty,703 ) -> Result<PropertyValue, DispatchError> {704 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)705 .get(&Self::rmrk_property_key(key)?)706 .ok_or(<Error<T>>::CollectionUnknown)?707 .clone();708709 Ok(collection_property)710 }711712 pub fn get_collection_type(713 collection_id: CollectionId,714 ) -> Result<misc::CollectionType, DispatchError> {715 let value = Self::get_collection_property(collection_id, CollectionType)?;716717 let mut value = value.as_slice();718719 misc::CollectionType::decode(&mut value)720 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())721 }722723 pub fn ensure_collection_type(724 collection_id: CollectionId,725 collection_type: misc::CollectionType,726 ) -> DispatchResult {727 let actual_type = Self::get_collection_type(collection_id)?;728 ensure!(729 actual_type == collection_type,730 <CommonError<T>>::NoPermission731 );732733 Ok(())734 }735736 pub fn get_typed_nft_collection(737 collection_id: CollectionId,738 collection_type: misc::CollectionType,739 ) -> Result<NonfungibleHandle<T>, DispatchError> {740 Self::ensure_collection_type(collection_id, collection_type)?;741742 Self::get_nft_collection(collection_id)743 }744745 pub fn get_nft_property(746 collection_id: CollectionId,747 nft_id: TokenId,748 key: RmrkProperty,749 ) -> Result<PropertyValue, DispatchError> {750 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))751 .get(&Self::rmrk_property_key(key)?)752 .ok_or(<Error<T>>::NoAvailableNftId)? 753 .clone();754755 Ok(nft_property)756 }757758 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {759 <TokenData<T>>::contains_key((collection_id, nft_id))760 }761762 pub fn get_nft_type(763 collection_id: CollectionId,764 token_id: TokenId,765 ) -> Result<NftType, DispatchError> {766 Ok(Self::get_nft_property(collection_id, token_id, TokenType)?.decode_or_default())767 768 769 }770771 pub fn ensure_nft_type(772 collection_id: CollectionId,773 token_id: TokenId,774 nft_type: NftType,775 ) -> DispatchResult {776 let actual_type = Self::get_nft_type(collection_id, token_id)?;777 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);778779 Ok(())780 }781782 pub fn ensure_nft_owner(783 collection_id: CollectionId,784 token_id: TokenId,785 possible_owner: &T::CrossAccountId,786 ) -> DispatchResult {787 let token_data =788 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;789790 ensure!(791 token_data.owner == *possible_owner,792 <Error<T>>::NoPermission793 );794795 Ok(())796 }797798 pub fn filter_user_properties<Key, Value, R, Mapper>(799 collection_id: CollectionId,800 token_id: Option<TokenId>,801 filter_keys: Option<Vec<RmrkPropertyKey>>,802 mapper: Mapper,803 ) -> Result<Vec<R>, DispatchError>804 where805 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,806 Value: Decode + Default,807 Mapper: Fn(Key, Value) -> R,808 {809 filter_keys810 .map(|keys| {811 let properties = keys812 .into_iter()813 .filter_map(|key| {814 let key: Key = key.try_into().ok()?;815816 let value = match token_id {817 Some(token_id) => Self::get_nft_property(818 collection_id,819 token_id,820 UserProperty(key.as_ref()),821 ),822 None => Self::get_collection_property(823 collection_id,824 UserProperty(key.as_ref()),825 ),826 }827 .ok()?828 .decode_or_default();829830 Some(mapper(key, value))831 })832 .collect();833834 Ok(properties)835 })836 .unwrap_or_else(|| {837 let properties =838 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();839840 Ok(properties)841 })842 }843844 pub fn iterate_user_properties<Key, Value, R, Mapper>(845 collection_id: CollectionId,846 token_id: Option<TokenId>,847 mapper: Mapper,848 ) -> Result<impl Iterator<Item = R>, DispatchError>849 where850 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,851 Value: Decode + Default,852 Mapper: Fn(Key, Value) -> R,853 {854 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;855856 let properties = match token_id {857 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),858 None => <PalletCommon<T>>::collection_properties(collection_id),859 };860861 let properties = properties.into_iter().filter_map(move |(key, value)| {862 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;863864 let key: Key = key.to_vec().try_into().ok()?;865 let value: Value = value.decode_or_default();866867 Some(mapper(key, value))868 });869870 Ok(properties)871 }872873 fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {874 map_common_err_to_proxy! {875 match err {876 NoPermission => NoPermission,877 CollectionTokenLimitExceeded => CollectionFullOrLocked,878 PublicMintingNotAllowed => NoPermission,879 TokenNotFound => NoAvailableNftId880 }881 }882 }883}