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 523 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 623 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 641642 let resource_id = Self::create_nft(643 &sender, 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)? 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}