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::{*, 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::*;3334#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod misc;37pub mod property;38pub mod weights;3940pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4142use weights::WeightInfo;43use misc::*;44pub use property::*;4546use RmrkProperty::*;4748pub const NESTING_BUDGET: u32 = 5;4950#[frame_support::pallet]51pub mod pallet {52 use super::*;53 use pallet_evm::account;5455 #[pallet::config]56 pub trait Config:57 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config58 {59 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;60 type WeightInfo: WeightInfo;61 }6263 #[pallet::storage]64 #[pallet::getter(fn collection_index)]65 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;6667 #[pallet::storage]68 pub type UniqueCollectionId<T: Config> =69 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;7071 #[pallet::pallet]72 #[pallet::generate_store(pub(super) trait Store)]73 pub struct Pallet<T>(_);7475 #[pallet::event]76 #[pallet::generate_deposit(pub(super) fn deposit_event)]77 pub enum Event<T: Config> {78 CollectionCreated {79 issuer: T::AccountId,80 collection_id: RmrkCollectionId,81 },82 CollectionDestroyed {83 issuer: T::AccountId,84 collection_id: RmrkCollectionId,85 },86 IssuerChanged {87 old_issuer: T::AccountId,88 new_issuer: T::AccountId,89 collection_id: RmrkCollectionId,90 },91 CollectionLocked {92 issuer: T::AccountId,93 collection_id: RmrkCollectionId,94 },95 NftMinted {96 owner: T::AccountId,97 collection_id: RmrkCollectionId,98 nft_id: RmrkNftId,99 },100 NFTBurned {101 owner: T::AccountId,102 nft_id: RmrkNftId,103 },104 NFTSent {105 sender: T::AccountId,106 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,107 collection_id: RmrkCollectionId,108 nft_id: RmrkNftId,109 approval_required: bool,110 },111 NFTAccepted {112 sender: T::AccountId,113 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,114 collection_id: RmrkCollectionId,115 nft_id: RmrkNftId,116 },117 NFTRejected {118 sender: T::AccountId,119 collection_id: RmrkCollectionId,120 nft_id: RmrkNftId,121 },122 PropertySet {123 collection_id: RmrkCollectionId,124 maybe_nft_id: Option<RmrkNftId>,125 key: RmrkKeyString,126 value: RmrkValueString,127 },128 ResourceAdded {129 nft_id: RmrkNftId,130 resource_id: RmrkResourceId,131 },132 ResourceRemoval {133 nft_id: RmrkNftId,134 resource_id: RmrkResourceId,135 },136 ResourceAccepted {137 nft_id: RmrkNftId,138 resource_id: RmrkResourceId,139 },140 ResourceRemovalAccepted {141 nft_id: RmrkNftId,142 resource_id: RmrkResourceId,143 },144 PrioritySet {145 collection_id: RmrkCollectionId,146 nft_id: RmrkNftId,147 },148 }149150 #[pallet::error]151 pub enum Error<T> {152 153 CorruptedCollectionType,154 NftTypeEncodeError,155 RmrkPropertyKeyIsTooLong,156 RmrkPropertyValueIsTooLong,157 UnableToDecodeRmrkData,158159 160 CollectionNotEmpty,161 NoAvailableCollectionId,162 NoAvailableNftId,163 CollectionUnknown,164 NoPermission,165 NonTransferable,166 CollectionFullOrLocked,167 ResourceDoesntExist,168 CannotSendToDescendentOrSelf,169 CannotAcceptNonOwnedNft,170 CannotRejectNonOwnedNft,171 ResourceNotPending,172 }173174 #[pallet::call]175 impl<T: Config> Pallet<T> {176 177 #[transactional]178 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]179 pub fn create_collection(180 origin: OriginFor<T>,181 metadata: RmrkString,182 max: Option<u32>,183 symbol: RmrkCollectionSymbol,184 ) -> DispatchResult {185 let sender = ensure_signed(origin)?;186187 let limits = CollectionLimits {188 owner_can_transfer: Some(false),189 token_limit: max,190 ..Default::default()191 };192193 let data = CreateCollectionData {194 limits: Some(limits),195 token_prefix: symbol196 .into_inner()197 .try_into()198 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,199 permissions: Some(CollectionPermissions {200 nesting: Some(NestingPermissions {201 token_owner: true,202 admin: false,203 restricted: None,204205 permissive: false,206 }),207 ..Default::default()208 }),209 ..Default::default()210 };211212 let unique_collection_id = Self::init_collection(213 T::CrossAccountId::from_sub(sender.clone()),214 data,215 [216 Self::rmrk_property(Metadata, &metadata)?,217 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,218 ]219 .into_iter(),220 )?;221 let rmrk_collection_id = <CollectionIndex<T>>::get();222223 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);224225 <PalletCommon<T>>::set_scoped_collection_property(226 unique_collection_id,227 PropertyScope::Rmrk,228 Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,229 )?;230231 <CollectionIndex<T>>::mutate(|n| *n += 1);232233 Self::deposit_event(Event::CollectionCreated {234 issuer: sender,235 collection_id: rmrk_collection_id,236 });237238 Ok(())239 }240241 242 #[transactional]243 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]244 pub fn destroy_collection(245 origin: OriginFor<T>,246 collection_id: RmrkCollectionId,247 ) -> DispatchResult {248 let sender = ensure_signed(origin)?;249 let cross_sender = T::CrossAccountId::from_sub(sender.clone());250251 let collection = Self::get_typed_nft_collection(252 Self::unique_collection_id(collection_id)?,253 misc::CollectionType::Regular,254 )?;255 collection.check_is_external()?;256257 <PalletNft<T>>::destroy_collection(collection, &cross_sender)258 .map_err(Self::map_unique_err_to_proxy)?;259260 Self::deposit_event(Event::CollectionDestroyed {261 issuer: sender,262 collection_id,263 });264265 Ok(())266 }267268 269 270 271 272 273 274 #[transactional]275 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]276 pub fn change_collection_issuer(277 origin: OriginFor<T>,278 collection_id: RmrkCollectionId,279 new_issuer: <T::Lookup as StaticLookup>::Source,280 ) -> DispatchResult {281 let sender = ensure_signed(origin)?;282283 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;284 collection.check_is_external()?;285286 let new_issuer = T::Lookup::lookup(new_issuer)?;287288 Self::change_collection_owner(289 Self::unique_collection_id(collection_id)?,290 misc::CollectionType::Regular,291 sender.clone(),292 new_issuer.clone(),293 )?;294295 Self::deposit_event(Event::IssuerChanged {296 old_issuer: sender,297 new_issuer,298 collection_id,299 });300301 Ok(())302 }303304 305 #[transactional]306 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]307 pub fn lock_collection(308 origin: OriginFor<T>,309 collection_id: RmrkCollectionId,310 ) -> DispatchResult {311 let sender = ensure_signed(origin)?;312 let cross_sender = T::CrossAccountId::from_sub(sender.clone());313314 let collection = Self::get_typed_nft_collection(315 Self::unique_collection_id(collection_id)?,316 misc::CollectionType::Regular,317 )?;318 collection.check_is_external()?;319320 Self::check_collection_owner(&collection, &cross_sender)?;321322 let token_count = collection.total_supply();323324 let mut collection = collection.into_inner();325 collection.limits.token_limit = Some(token_count);326 collection.save()?;327328 Self::deposit_event(Event::CollectionLocked {329 issuer: sender,330 collection_id,331 });332333 Ok(())334 }335336 337 338 339 340 341 342 343 344 345 346 #[transactional]347 #[pallet::weight(<SelfWeightOf<T>>::mint_nft())]348 pub fn mint_nft(349 origin: OriginFor<T>,350 owner: T::AccountId,351 collection_id: RmrkCollectionId,352 recipient: Option<T::AccountId>,353 royalty_amount: Option<Permill>,354 metadata: RmrkString,355 transferable: bool,356 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>357 ) -> DispatchResult {358 let sender = ensure_signed(origin)?;359 let cross_sender = T::CrossAccountId::from_sub(sender.clone());360 let cross_owner = T::CrossAccountId::from_sub(owner.clone());361362 let collection = Self::get_typed_nft_collection(363 Self::unique_collection_id(collection_id)?,364 misc::CollectionType::Regular,365 )?;366 collection.check_is_external()?;367368 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {369 recipient: recipient.unwrap_or_else(|| owner.clone()),370 amount,371 });372373 let nft_id = Self::create_nft(374 &cross_sender,375 &cross_owner,376 &collection,377 [378 Self::rmrk_property(TokenType, &NftType::Regular)?,379 Self::rmrk_property(Transferable, &transferable)?,380 Self::rmrk_property(PendingNftAccept, &false)?,381 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,382 Self::rmrk_property(Metadata, &metadata)?,383 Self::rmrk_property(Equipped, &false)?,384 Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,385 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,386 ]387 .into_iter(),388 )389 .map_err(|err| match err {390 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),391 err => Self::map_unique_err_to_proxy(err),392 })?;393394 if let Some(resources) = resources {395 for resource in resources {396 Self::resource_add(397 sender.clone(),398 collection.id,399 nft_id,400 resource401 )?;402 }403 }404405 Self::deposit_event(Event::NftMinted {406 owner,407 collection_id,408 nft_id: nft_id.0,409 });410411 Ok(())412 }413414 415 #[transactional]416 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]417 pub fn burn_nft(418 origin: OriginFor<T>,419 collection_id: RmrkCollectionId,420 nft_id: RmrkNftId,421 max_burns: u32,422 ) -> DispatchResult {423 let sender = ensure_signed(origin)?;424 let cross_sender = T::CrossAccountId::from_sub(sender.clone());425426 let collection = Self::get_typed_nft_collection(427 Self::unique_collection_id(collection_id)?,428 misc::CollectionType::Regular,429 )?;430 collection.check_is_external()?;431432 Self::destroy_nft(433 cross_sender,434 Self::unique_collection_id(collection_id)?,435 nft_id.into(),436 max_burns,437 <Error<T>>::NoPermission,438 )439 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;440441 Self::deposit_event(Event::NFTBurned {442 owner: sender,443 nft_id,444 });445446 Ok(())447 }448449 450 451 452 453 454 455 456 #[transactional]457 #[pallet::weight(<SelfWeightOf<T>>::send())]458 pub fn send(459 origin: OriginFor<T>,460 rmrk_collection_id: RmrkCollectionId,461 rmrk_nft_id: RmrkNftId,462 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,463 ) -> DispatchResult {464 let sender = ensure_signed(origin.clone())?;465 let cross_sender = T::CrossAccountId::from_sub(sender.clone());466467 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;468 let nft_id = rmrk_nft_id.into();469470 let collection =471 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;472 collection.check_is_external()?;473474 let token_data =475 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;476477 let from = token_data.owner;478479 ensure!(480 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,481 <Error<T>>::NonTransferable482 );483484 ensure!(485 !Self::get_nft_property_decoded(486 collection_id,487 nft_id,488 RmrkProperty::PendingNftAccept489 )?,490 <Error<T>>::NoPermission491 );492493 let target_owner;494 let approval_required;495496 match new_owner {497 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {498 target_owner = T::CrossAccountId::from_sub(account_id.clone());499 approval_required = false;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 let target_nft_budget = budget::Value::new(NESTING_BUDGET);508509 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(510 target_collection_id,511 target_nft_id.into(),512 Some((collection_id, nft_id)),513 &target_nft_budget,514 )515 .map_err(Self::map_unique_err_to_proxy)?;516517 approval_required = cross_sender != target_nft_owner;518519 if approval_required {520 target_owner = target_nft_owner;521522 <PalletNft<T>>::set_scoped_token_property(523 collection.id,524 nft_id,525 PropertyScope::Rmrk,526 Self::rmrk_property(PendingNftAccept, &approval_required)?,527 )?;528 } else {529 target_owner = T::CrossTokenAddressMapping::token_to_address(530 target_collection_id,531 target_nft_id.into(),532 );533 }534 }535 }536537 let src_nft_budget = budget::Value::new(NESTING_BUDGET);538539 <PalletNft<T>>::transfer_from(540 &collection,541 &cross_sender,542 &from,543 &target_owner,544 nft_id,545 &src_nft_budget,546 )547 .map_err(Self::map_unique_err_to_proxy)?;548549 Self::deposit_event(Event::NFTSent {550 sender,551 recipient: new_owner,552 collection_id: rmrk_collection_id,553 nft_id: rmrk_nft_id,554 approval_required,555 });556557 Ok(())558 }559560 561 562 563 564 565 566 567 568 #[transactional]569 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]570 pub fn accept_nft(571 origin: OriginFor<T>,572 rmrk_collection_id: RmrkCollectionId,573 rmrk_nft_id: RmrkNftId,574 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,575 ) -> DispatchResult {576 let sender = ensure_signed(origin.clone())?;577 let cross_sender = T::CrossAccountId::from_sub(sender.clone());578579 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;580 let nft_id = rmrk_nft_id.into();581582 let collection =583 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;584 collection.check_is_external()?;585586 let new_cross_owner = match new_owner {587 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {588 T::CrossAccountId::from_sub(account_id.clone())589 }590 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(591 target_collection_id,592 target_nft_id,593 ) => {594 let target_collection_id = Self::unique_collection_id(target_collection_id)?;595596 T::CrossTokenAddressMapping::token_to_address(597 target_collection_id,598 TokenId(target_nft_id),599 )600 }601 };602603 let budget = budget::Value::new(NESTING_BUDGET);604605 <PalletNft<T>>::transfer(606 &collection,607 &cross_sender,608 &new_cross_owner,609 nft_id,610 &budget,611 )612 .map_err(|err| {613 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {614 <Error<T>>::CannotAcceptNonOwnedNft.into()615 } else {616 Self::map_unique_err_to_proxy(err)617 }618 })?;619620 <PalletNft<T>>::set_scoped_token_property(621 collection.id,622 nft_id,623 PropertyScope::Rmrk,624 Self::rmrk_property(PendingNftAccept, &false)?,625 )?;626627 Self::deposit_event(Event::NFTAccepted {628 sender,629 recipient: new_owner,630 collection_id: rmrk_collection_id,631 nft_id: rmrk_nft_id,632 });633634 Ok(())635 }636637 638 639 640 641 642 643 #[transactional]644 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]645 pub fn reject_nft(646 origin: OriginFor<T>,647 rmrk_collection_id: RmrkCollectionId,648 rmrk_nft_id: RmrkNftId,649 ) -> DispatchResult {650 let sender = ensure_signed(origin)?;651 let cross_sender = T::CrossAccountId::from_sub(sender.clone());652653 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;654 let nft_id = rmrk_nft_id.into();655656 let collection =657 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;658 collection.check_is_external()?;659660 ensure!(661 Self::get_nft_property_decoded(662 collection_id,663 nft_id,664 RmrkProperty::PendingNftAccept665 )?,666 <Error<T>>::NoPermission667 );668669 Self::destroy_nft(670 cross_sender,671 collection_id,672 nft_id,673 NESTING_BUDGET,674 <Error<T>>::CannotRejectNonOwnedNft,675 )676 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;677678 Self::deposit_event(Event::NFTRejected {679 sender,680 collection_id: rmrk_collection_id,681 nft_id: rmrk_nft_id,682 });683684 Ok(())685 }686687 688 #[transactional]689 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]690 pub fn accept_resource(691 origin: OriginFor<T>,692 rmrk_collection_id: RmrkCollectionId,693 rmrk_nft_id: RmrkNftId,694 rmrk_resource_id: RmrkResourceId,695 ) -> DispatchResult {696 let sender = ensure_signed(origin)?;697 let cross_sender = T::CrossAccountId::from_sub(sender);698699 let collection_id = Self::unique_collection_id(rmrk_collection_id)700 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;701 let collection =702 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;703 collection.check_is_external()?;704705 let nft_id = rmrk_nft_id.into();706 let resource_id = rmrk_resource_id.into();707708 let budget = budget::Value::new(NESTING_BUDGET);709710 let nft_owner =711 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)712 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;713714 let resource_collection_id: Option<CollectionId> =715 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)716 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;717718 let resource_collection_id =719 resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;720721 let is_pending: bool = Self::get_nft_property_decoded(722 resource_collection_id,723 resource_id,724 PendingResourceAccept,725 )726 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;727728 ensure!(is_pending, <Error<T>>::ResourceNotPending);729730 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);731732 <PalletNft<T>>::set_scoped_token_property(733 resource_collection_id,734 rmrk_resource_id.into(),735 PropertyScope::Rmrk,736 Self::rmrk_property(PendingResourceAccept, &false)?,737 )?;738739 Self::deposit_event(Event::<T>::ResourceAccepted {740 nft_id: rmrk_nft_id,741 resource_id: rmrk_resource_id,742 });743744 Ok(())745 }746747 748 #[transactional]749 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]750 pub fn accept_resource_removal(751 origin: OriginFor<T>,752 rmrk_collection_id: RmrkCollectionId,753 rmrk_nft_id: RmrkNftId,754 rmrk_resource_id: RmrkResourceId,755 ) -> DispatchResult {756 let sender = ensure_signed(origin)?;757 let cross_sender = T::CrossAccountId::from_sub(sender);758759 let collection_id = Self::unique_collection_id(rmrk_collection_id)760 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;761 let collection =762 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;763 collection.check_is_external()?;764765 let nft_id = rmrk_nft_id.into();766 let resource_id = rmrk_resource_id.into();767768 let budget = budget::Value::new(NESTING_BUDGET);769770 let nft_owner =771 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)772 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;773774 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);775776 let resource_collection_id: Option<CollectionId> =777 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)778 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;779780 let resource_collection_id =781 resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;782783 let is_pending: bool = Self::get_nft_property_decoded(784 resource_collection_id,785 resource_id,786 PendingResourceRemoval,787 )788 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;789790 ensure!(is_pending, <Error<T>>::ResourceNotPending);791792 let resource_collection = Self::get_typed_nft_collection(793 resource_collection_id,794 misc::CollectionType::Resource,795 )?;796797 let resource_data = <TokenData<T>>::get((resource_collection_id, resource_id))798 .ok_or(<Error<T>>::ResourceDoesntExist)?;799800 let resource_owner = resource_data.owner;801802 <PalletNft<T>>::burn(803 &resource_collection,804 &resource_owner,805 rmrk_resource_id.into(),806 )807 .map_err(Self::map_unique_err_to_proxy)?;808809 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {810 nft_id: rmrk_nft_id,811 resource_id: rmrk_resource_id,812 });813814 Ok(())815 }816817 818 #[transactional]819 #[pallet::weight(<SelfWeightOf<T>>::set_property())]820 pub fn set_property(821 origin: OriginFor<T>,822 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,823 maybe_nft_id: Option<RmrkNftId>,824 key: RmrkKeyString,825 value: RmrkValueString,826 ) -> DispatchResult {827 let sender = ensure_signed(origin)?;828 let sender = T::CrossAccountId::from_sub(sender);829830 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;831 let collection =832 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;833 collection.check_is_external()?;834835 let budget = budget::Value::new(NESTING_BUDGET);836837 match maybe_nft_id {838 Some(nft_id) => {839 let token_id: TokenId = nft_id.into();840841 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;842 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;843844 <PalletNft<T>>::set_scoped_token_property(845 collection_id,846 token_id,847 PropertyScope::Rmrk,848 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,849 )?;850 }851 None => {852 let collection = Self::get_typed_nft_collection(853 collection_id,854 misc::CollectionType::Regular,855 )?;856857 Self::check_collection_owner(&collection, &sender)?;858859 <PalletCommon<T>>::set_scoped_collection_property(860 collection_id,861 PropertyScope::Rmrk,862 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,863 )?;864 }865 }866867 Self::deposit_event(Event::PropertySet {868 collection_id: rmrk_collection_id,869 maybe_nft_id,870 key,871 value,872 });873874 Ok(())875 }876877 878 #[transactional]879 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]880 pub fn set_priority(881 origin: OriginFor<T>,882 rmrk_collection_id: RmrkCollectionId,883 rmrk_nft_id: RmrkNftId,884 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,885 ) -> DispatchResult {886 let sender = ensure_signed(origin)?;887 let sender = T::CrossAccountId::from_sub(sender);888889 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;890 let nft_id = rmrk_nft_id.into();891892 let collection =893 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;894 collection.check_is_external()?;895896 let budget = budget::Value::new(NESTING_BUDGET);897898 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;899 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;900901 <PalletNft<T>>::set_scoped_token_property(902 collection_id,903 nft_id,904 PropertyScope::Rmrk,905 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,906 )?;907908 Self::deposit_event(Event::<T>::PrioritySet {909 collection_id: rmrk_collection_id,910 nft_id: rmrk_nft_id,911 });912913 Ok(())914 }915916 917 #[transactional]918 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]919 pub fn add_basic_resource(920 origin: OriginFor<T>,921 rmrk_collection_id: RmrkCollectionId,922 nft_id: RmrkNftId,923 resource: RmrkBasicResource,924 ) -> DispatchResult {925 let sender = ensure_signed(origin.clone())?;926927 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;928 let collection =929 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;930 collection.check_is_external()?;931932 let resource_id = Self::resource_add(933 sender,934 collection_id,935 nft_id.into(),936 RmrkResourceTypes::Basic(resource),937 )?;938939 Self::deposit_event(Event::ResourceAdded {940 nft_id,941 resource_id,942 });943 Ok(())944 }945946 947 #[transactional]948 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]949 pub fn add_composable_resource(950 origin: OriginFor<T>,951 rmrk_collection_id: RmrkCollectionId,952 nft_id: RmrkNftId,953 resource: RmrkComposableResource,954 ) -> DispatchResult {955 let sender = ensure_signed(origin.clone())?;956957 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;958 let collection =959 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;960 collection.check_is_external()?;961962 let resource_id = Self::resource_add(963 sender,964 collection_id,965 nft_id.into(),966 RmrkResourceTypes::Composable(resource)967 )?;968969 Self::deposit_event(Event::ResourceAdded {970 nft_id,971 resource_id,972 });973 Ok(())974 }975976 977 #[transactional]978 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]979 pub fn add_slot_resource(980 origin: OriginFor<T>,981 rmrk_collection_id: RmrkCollectionId,982 nft_id: RmrkNftId,983 resource: RmrkSlotResource,984 ) -> DispatchResult {985 let sender = ensure_signed(origin.clone())?;986987 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;988 let collection =989 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;990 collection.check_is_external()?;991992 let resource_id = Self::resource_add(993 sender,994 collection_id,995 nft_id.into(),996 RmrkResourceTypes::Slot(resource),997 )?;998999 Self::deposit_event(Event::ResourceAdded {1000 nft_id,1001 resource_id,1002 });1003 Ok(())1004 }10051006 1007 #[transactional]1008 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1009 pub fn remove_resource(1010 origin: OriginFor<T>,1011 rmrk_collection_id: RmrkCollectionId,1012 nft_id: RmrkNftId,1013 resource_id: RmrkResourceId,1014 ) -> DispatchResult {1015 let sender = ensure_signed(origin.clone())?;10161017 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1018 let collection =1019 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1020 collection.check_is_external()?;10211022 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10231024 Self::deposit_event(Event::ResourceRemoval {1025 nft_id,1026 resource_id,1027 });1028 Ok(())1029 }1030 }1031}10321033impl<T: Config> Pallet<T> {1034 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1035 let key = rmrk_key.to_key::<T>()?;10361037 let scoped_key = PropertyScope::Rmrk1038 .apply(key)1039 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10401041 Ok(scoped_key)1042 }10431044 1045 pub fn rmrk_property<E: Encode>(1046 rmrk_key: RmrkProperty,1047 value: &E,1048 ) -> Result<Property, DispatchError> {1049 let key = rmrk_key.to_key::<T>()?;10501051 let value = value1052 .encode()1053 .try_into()1054 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10551056 let property = Property { key, value };10571058 Ok(property)1059 }10601061 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1062 vec.decode()1063 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1064 }10651066 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1067 where1068 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1069 {1070 vec.rebind()1071 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1072 }10731074 fn init_collection(1075 sender: T::CrossAccountId,1076 data: CreateCollectionData<T::AccountId>,1077 properties: impl Iterator<Item = Property>,1078 ) -> Result<CollectionId, DispatchError> {1079 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10801081 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1082 return Err(<Error<T>>::NoAvailableCollectionId.into());1083 }10841085 <PalletCommon<T>>::set_scoped_collection_properties(1086 collection_id?,1087 PropertyScope::Rmrk,1088 properties,1089 )?;10901091 collection_id1092 }10931094 pub fn create_nft(1095 sender: &T::CrossAccountId,1096 owner: &T::CrossAccountId,1097 collection: &NonfungibleHandle<T>,1098 properties: impl Iterator<Item = Property>,1099 ) -> Result<TokenId, DispatchError> {1100 let data = CreateNftExData {1101 properties: BoundedVec::default(),1102 owner: owner.clone(),1103 };11041105 let budget = budget::Value::new(NESTING_BUDGET);11061107 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;11081109 let nft_id = <PalletNft<T>>::current_token_id(collection.id);11101111 <PalletNft<T>>::set_scoped_token_properties(1112 collection.id,1113 nft_id,1114 PropertyScope::Rmrk,1115 properties,1116 )?;11171118 Ok(nft_id)1119 }11201121 fn destroy_nft(1122 sender: T::CrossAccountId,1123 collection_id: CollectionId,1124 token_id: TokenId,1125 max_burns: u32,1126 error_if_not_owned: Error<T>,1127 ) -> DispatchResultWithPostInfo {1128 let collection =1129 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11301131 let token_data =1132 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11331134 let from = token_data.owner;11351136 let owner_check_budget = budget::Value::new(NESTING_BUDGET);11371138 ensure!(1139 <PalletStructure<T>>::check_indirectly_owned(1140 sender.clone(),1141 collection_id,1142 token_id,1143 None,1144 &owner_check_budget1145 )?,1146 error_if_not_owned,1147 );11481149 let burns_budget = budget::Value::new(max_burns);1150 let breadth_budget = budget::Value::new(max_burns);11511152 <PalletNft<T>>::burn_recursively(1153 &collection,1154 &from,1155 token_id,1156 &burns_budget,1157 &breadth_budget,1158 )1159 }11601161 fn resource_add(1162 sender: T::AccountId,1163 collection_id: CollectionId,1164 nft_id: TokenId,1165 resource: RmrkResourceTypes1166 ) -> Result<RmrkResourceId, DispatchError> {1167 match resource {1168 RmrkResourceTypes::Basic(resource) => {1169 Self::resource_add_helper(1170 sender,1171 collection_id,1172 nft_id,1173 [1174 Self::rmrk_property(TokenType, &NftType::Resource)?,1175 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,1176 Self::rmrk_property(Src, &resource.src)?,1177 Self::rmrk_property(Metadata, &resource.metadata)?,1178 Self::rmrk_property(License, &resource.license)?,1179 Self::rmrk_property(Thumb, &resource.thumb)?,1180 ]1181 .into_iter(),1182 )1183 },1184 RmrkResourceTypes::Composable(resource) => {1185 Self::resource_add_helper(1186 sender,1187 collection_id,1188 nft_id.into(),1189 [1190 Self::rmrk_property(TokenType, &NftType::Resource)?,1191 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,1192 Self::rmrk_property(Parts, &resource.parts)?,1193 Self::rmrk_property(Base, &resource.base)?,1194 Self::rmrk_property(Src, &resource.src)?,1195 Self::rmrk_property(Metadata, &resource.metadata)?,1196 Self::rmrk_property(License, &resource.license)?,1197 Self::rmrk_property(Thumb, &resource.thumb)?,1198 ]1199 .into_iter(),1200 )1201 },1202 RmrkResourceTypes::Slot(resource) => {1203 Self::resource_add_helper(1204 sender,1205 collection_id,1206 nft_id.into(),1207 [1208 Self::rmrk_property(TokenType, &NftType::Resource)?,1209 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,1210 Self::rmrk_property(Base, &resource.base)?,1211 Self::rmrk_property(Src, &resource.src)?,1212 Self::rmrk_property(Metadata, &resource.metadata)?,1213 Self::rmrk_property(Slot, &resource.slot)?,1214 Self::rmrk_property(License, &resource.license)?,1215 Self::rmrk_property(Thumb, &resource.thumb)?,1216 ]1217 .into_iter(),1218 )1219 }1220 }1221 }12221223 fn resource_add_helper(1224 sender: T::AccountId,1225 collection_id: CollectionId,1226 token_id: TokenId,1227 resource_properties: impl Iterator<Item = Property>,1228 ) -> Result<RmrkResourceId, DispatchError> {1229 let collection =1230 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1231 ensure!(collection.owner == sender, Error::<T>::NoPermission);12321233 let sender = T::CrossAccountId::from_sub(sender);1234 let budget = budget::Value::new(NESTING_BUDGET);12351236 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1237 .map_err(Self::map_unique_err_to_proxy)?;12381239 let pending = sender != nft_owner;12401241 let resource_collection_id: Option<CollectionId> =1242 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;12431244 let resource_collection_id = match resource_collection_id {1245 Some(id) => id,1246 None => {1247 let resource_collection_id = Self::init_collection(1248 sender.clone(),1249 CreateCollectionData {1250 ..Default::default()1251 },1252 [Self::rmrk_property(1253 CollectionType,1254 &misc::CollectionType::Resource,1255 )?]1256 .into_iter(),1257 )?;12581259 <PalletNft<T>>::set_scoped_token_property(1260 collection_id,1261 token_id,1262 PropertyScope::Rmrk,1263 Self::rmrk_property(ResourceCollection, &Some(resource_collection_id))?,1264 )?;12651266 resource_collection_id1267 }1268 };12691270 let resource_collection =1271 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;12721273 12741275 let resource_id = Self::create_nft(1276 &sender,1277 &nft_owner,1278 &resource_collection,1279 resource_properties.chain(1280 [1281 Self::rmrk_property(PendingResourceAccept, &pending)?,1282 Self::rmrk_property(PendingResourceRemoval, &false)?,1283 ]1284 .into_iter(),1285 ),1286 )1287 .map_err(|err| match err {1288 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1289 err => Self::map_unique_err_to_proxy(err),1290 })?;12911292 Ok(resource_id.0)1293 }12941295 fn resource_remove(1296 sender: T::AccountId,1297 collection_id: CollectionId,1298 nft_id: TokenId,1299 resource_id: TokenId,1300 ) -> DispatchResult {1301 let collection =1302 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1303 ensure!(collection.owner == sender, Error::<T>::NoPermission);13041305 let resource_collection_id: Option<CollectionId> =1306 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;13071308 let resource_collection_id =1309 resource_collection_id.ok_or(Error::<T>::ResourceDoesntExist)?;13101311 let resource_collection =1312 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1313 ensure!(1314 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1315 Error::<T>::ResourceDoesntExist1316 );13171318 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1319 let topmost_owner =1320 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;13211322 let sender = T::CrossAccountId::from_sub(sender);1323 if topmost_owner == sender {1324 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1325 .map_err(Self::map_unique_err_to_proxy)?;1326 } else {1327 <PalletNft<T>>::set_scoped_token_property(1328 resource_collection_id,1329 resource_id,1330 PropertyScope::Rmrk,1331 Self::rmrk_property(PendingResourceRemoval, &true)?,1332 )?;1333 }13341335 Ok(())1336 }13371338 fn change_collection_owner(1339 collection_id: CollectionId,1340 collection_type: misc::CollectionType,1341 sender: T::AccountId,1342 new_owner: T::AccountId,1343 ) -> DispatchResult {1344 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1345 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;13461347 let mut collection = collection.into_inner();13481349 collection.owner = new_owner;1350 collection.save()1351 }13521353 fn check_collection_owner(1354 collection: &NonfungibleHandle<T>,1355 account: &T::CrossAccountId,1356 ) -> DispatchResult {1357 collection1358 .check_is_owner(account)1359 .map_err(Self::map_unique_err_to_proxy)1360 }13611362 pub fn last_collection_idx() -> RmrkCollectionId {1363 <CollectionIndex<T>>::get()1364 }13651366 pub fn unique_collection_id(1367 rmrk_collection_id: RmrkCollectionId,1368 ) -> Result<CollectionId, DispatchError> {1369 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1370 .map_err(|_| <Error<T>>::CollectionUnknown.into())1371 }13721373 pub fn rmrk_collection_id(1374 unique_collection_id: CollectionId,1375 ) -> Result<RmrkCollectionId, DispatchError> {1376 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1377 }13781379 pub fn get_nft_collection(1380 collection_id: CollectionId,1381 ) -> Result<NonfungibleHandle<T>, DispatchError> {1382 let collection = <CollectionHandle<T>>::try_get(collection_id)1383 .map_err(|_| <Error<T>>::CollectionUnknown)?;13841385 match collection.mode {1386 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1387 _ => Err(<Error<T>>::CollectionUnknown.into()),1388 }1389 }13901391 pub fn collection_exists(collection_id: CollectionId) -> bool {1392 <CollectionHandle<T>>::try_get(collection_id).is_ok()1393 }13941395 pub fn get_collection_property(1396 collection_id: CollectionId,1397 key: RmrkProperty,1398 ) -> Result<PropertyValue, DispatchError> {1399 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1400 .get(&Self::rmrk_property_key(key)?)1401 .ok_or(<Error<T>>::CollectionUnknown)?1402 .clone();14031404 Ok(collection_property)1405 }14061407 pub fn get_collection_property_decoded<V: Decode>(1408 collection_id: CollectionId,1409 key: RmrkProperty,1410 ) -> Result<V, DispatchError> {1411 Self::decode_property(Self::get_collection_property(collection_id, key)?)1412 }14131414 pub fn get_collection_type(1415 collection_id: CollectionId,1416 ) -> Result<misc::CollectionType, DispatchError> {1417 Self::get_collection_property_decoded(collection_id, CollectionType)1418 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1419 }14201421 pub fn ensure_collection_type(1422 collection_id: CollectionId,1423 collection_type: misc::CollectionType,1424 ) -> DispatchResult {1425 let actual_type = Self::get_collection_type(collection_id)?;1426 ensure!(1427 actual_type == collection_type,1428 <CommonError<T>>::NoPermission1429 );14301431 Ok(())1432 }14331434 pub fn get_typed_nft_collection(1435 collection_id: CollectionId,1436 collection_type: misc::CollectionType,1437 ) -> Result<NonfungibleHandle<T>, DispatchError> {1438 Self::ensure_collection_type(collection_id, collection_type)?;14391440 Self::get_nft_collection(collection_id)1441 }14421443 pub fn get_typed_nft_collection_mapped(1444 rmrk_collection_id: RmrkCollectionId,1445 collection_type: misc::CollectionType,1446 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1447 let unique_collection_id = match collection_type {1448 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1449 _ => rmrk_collection_id.into(),1450 };14511452 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;14531454 Ok((collection, unique_collection_id))1455 }14561457 pub fn get_nft_property(1458 collection_id: CollectionId,1459 nft_id: TokenId,1460 key: RmrkProperty,1461 ) -> Result<PropertyValue, DispatchError> {1462 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1463 .get(&Self::rmrk_property_key(key)?)1464 .ok_or(<Error<T>>::NoAvailableNftId)? 1465 .clone();14661467 Ok(nft_property)1468 }14691470 pub fn get_nft_property_decoded<V: Decode>(1471 collection_id: CollectionId,1472 nft_id: TokenId,1473 key: RmrkProperty,1474 ) -> Result<V, DispatchError> {1475 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1476 }14771478 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1479 <TokenData<T>>::contains_key((collection_id, nft_id))1480 }14811482 pub fn get_nft_type(1483 collection_id: CollectionId,1484 token_id: TokenId,1485 ) -> Result<NftType, DispatchError> {1486 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1487 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1488 }14891490 pub fn ensure_nft_type(1491 collection_id: CollectionId,1492 token_id: TokenId,1493 nft_type: NftType,1494 ) -> DispatchResult {1495 let actual_type = Self::get_nft_type(collection_id, token_id)?;1496 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14971498 Ok(())1499 }15001501 pub fn ensure_nft_owner(1502 collection_id: CollectionId,1503 token_id: TokenId,1504 possible_owner: &T::CrossAccountId,1505 nesting_budget: &dyn budget::Budget,1506 ) -> DispatchResult {1507 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1508 possible_owner.clone(),1509 collection_id,1510 token_id,1511 None,1512 nesting_budget,1513 )1514 .map_err(Self::map_unique_err_to_proxy)?;15151516 ensure!(is_owned, <Error<T>>::NoPermission);15171518 Ok(())1519 }15201521 pub fn filter_user_properties<Key, Value, R, Mapper>(1522 collection_id: CollectionId,1523 token_id: Option<TokenId>,1524 filter_keys: Option<Vec<RmrkPropertyKey>>,1525 mapper: Mapper,1526 ) -> Result<Vec<R>, DispatchError>1527 where1528 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1529 Value: Decode + Default,1530 Mapper: Fn(Key, Value) -> R,1531 {1532 filter_keys1533 .map(|keys| {1534 let properties = keys1535 .into_iter()1536 .filter_map(|key| {1537 let key: Key = key.try_into().ok()?;15381539 let value = match token_id {1540 Some(token_id) => Self::get_nft_property_decoded(1541 collection_id,1542 token_id,1543 UserProperty(key.as_ref()),1544 ),1545 None => Self::get_collection_property_decoded(1546 collection_id,1547 UserProperty(key.as_ref()),1548 ),1549 }1550 .ok()?;15511552 Some(mapper(key, value))1553 })1554 .collect();15551556 Ok(properties)1557 })1558 .unwrap_or_else(|| {1559 let properties =1560 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15611562 Ok(properties)1563 })1564 }15651566 pub fn iterate_user_properties<Key, Value, R, Mapper>(1567 collection_id: CollectionId,1568 token_id: Option<TokenId>,1569 mapper: Mapper,1570 ) -> Result<impl Iterator<Item = R>, DispatchError>1571 where1572 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1573 Value: Decode + Default,1574 Mapper: Fn(Key, Value) -> R,1575 {1576 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15771578 let properties = match token_id {1579 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1580 None => <PalletCommon<T>>::collection_properties(collection_id),1581 };15821583 let properties = properties.into_iter().filter_map(move |(key, value)| {1584 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15851586 let key: Key = key.to_vec().try_into().ok()?;1587 let value: Value = value.decode().ok()?;15881589 Some(mapper(key, value))1590 });15911592 Ok(properties)1593 }15941595 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1596 map_unique_err_to_proxy! {1597 match err {1598 CommonError::NoPermission => NoPermission,1599 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1600 CommonError::PublicMintingNotAllowed => NoPermission,1601 CommonError::TokenNotFound => NoAvailableNftId,1602 CommonError::ApprovedValueTooLow => NoPermission,1603 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1604 StructureError::TokenNotFound => NoAvailableNftId,1605 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1606 }1607 }1608 }1609}