difftreelog
feat(rmrk) mint_nft allows optional owner
in: master
1 file changed
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;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 /* Unique-specific events */153 CorruptedCollectionType,154 NftTypeEncodeError,155 RmrkPropertyKeyIsTooLong,156 RmrkPropertyValueIsTooLong,157 RmrkPropertyIsNotFound,158 UnableToDecodeRmrkData,159160 /* RMRK compatible events */161 CollectionNotEmpty,162 NoAvailableCollectionId,163 NoAvailableNftId,164 CollectionUnknown,165 NoPermission,166 NonTransferable,167 CollectionFullOrLocked,168 ResourceDoesntExist,169 CannotSendToDescendentOrSelf,170 CannotAcceptNonOwnedNft,171 CannotRejectNonOwnedNft,172 CannotRejectNonPendingNft,173 ResourceNotPending,174 NoAvailableResourceId,175 }176177 #[pallet::call]178 impl<T: Config> Pallet<T> {179 /// Create a collection180 #[transactional]181 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]182 pub fn create_collection(183 origin: OriginFor<T>,184 metadata: RmrkString,185 max: Option<u32>,186 symbol: RmrkCollectionSymbol,187 ) -> DispatchResult {188 let sender = ensure_signed(origin)?;189190 let limits = CollectionLimits {191 owner_can_transfer: Some(false),192 token_limit: max,193 ..Default::default()194 };195196 let data = CreateCollectionData {197 limits: Some(limits),198 token_prefix: symbol199 .into_inner()200 .try_into()201 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,202 permissions: Some(CollectionPermissions {203 nesting: Some(NestingPermissions {204 token_owner: true,205 collection_admin: false,206 restricted: None,207208 permissive: false,209 }),210 ..Default::default()211 }),212 ..Default::default()213 };214215 let unique_collection_id = Self::init_collection(216 T::CrossAccountId::from_sub(sender.clone()),217 data,218 [219 Self::rmrk_property(Metadata, &metadata)?,220 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,221 ]222 .into_iter(),223 )?;224 let rmrk_collection_id = <CollectionIndex<T>>::get();225226 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);227228 <PalletCommon<T>>::set_scoped_collection_property(229 unique_collection_id,230 PropertyScope::Rmrk,231 Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,232 )?;233234 <CollectionIndex<T>>::mutate(|n| *n += 1);235236 Self::deposit_event(Event::CollectionCreated {237 issuer: sender,238 collection_id: rmrk_collection_id,239 });240241 Ok(())242 }243244 /// destroy collection245 #[transactional]246 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]247 pub fn destroy_collection(248 origin: OriginFor<T>,249 collection_id: RmrkCollectionId,250 ) -> DispatchResult {251 let sender = ensure_signed(origin)?;252 let cross_sender = T::CrossAccountId::from_sub(sender.clone());253254 let collection = Self::get_typed_nft_collection(255 Self::unique_collection_id(collection_id)?,256 misc::CollectionType::Regular,257 )?;258 collection.check_is_external()?;259260 <PalletNft<T>>::destroy_collection(collection, &cross_sender)261 .map_err(Self::map_unique_err_to_proxy)?;262263 Self::deposit_event(Event::CollectionDestroyed {264 issuer: sender,265 collection_id,266 });267268 Ok(())269 }270271 /// Change the issuer of a collection272 ///273 /// Parameters:274 /// - `origin`: sender of the transaction275 /// - `collection_id`: collection id of the nft to change issuer of276 /// - `new_issuer`: Collection's new issuer277 #[transactional]278 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]279 pub fn change_collection_issuer(280 origin: OriginFor<T>,281 collection_id: RmrkCollectionId,282 new_issuer: <T::Lookup as StaticLookup>::Source,283 ) -> DispatchResult {284 let sender = ensure_signed(origin)?;285286 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;287 collection.check_is_external()?;288289 let new_issuer = T::Lookup::lookup(new_issuer)?;290291 Self::change_collection_owner(292 Self::unique_collection_id(collection_id)?,293 misc::CollectionType::Regular,294 sender.clone(),295 new_issuer.clone(),296 )?;297298 Self::deposit_event(Event::IssuerChanged {299 old_issuer: sender,300 new_issuer,301 collection_id,302 });303304 Ok(())305 }306307 /// lock collection308 #[transactional]309 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]310 pub fn lock_collection(311 origin: OriginFor<T>,312 collection_id: RmrkCollectionId,313 ) -> DispatchResult {314 let sender = ensure_signed(origin)?;315 let cross_sender = T::CrossAccountId::from_sub(sender.clone());316317 let collection = Self::get_typed_nft_collection(318 Self::unique_collection_id(collection_id)?,319 misc::CollectionType::Regular,320 )?;321 collection.check_is_external()?;322323 Self::check_collection_owner(&collection, &cross_sender)?;324325 let token_count = collection.total_supply();326327 let mut collection = collection.into_inner();328 collection.limits.token_limit = Some(token_count);329 collection.save()?;330331 Self::deposit_event(Event::CollectionLocked {332 issuer: sender,333 collection_id,334 });335336 Ok(())337 }338339 /// Mints an NFT in the specified collection340 /// Sets metadata and the royalty attribute341 ///342 /// Parameters:343 /// - `collection_id`: The class of the asset to be minted.344 /// - `nft_id`: The nft value of the asset to be minted.345 /// - `recipient`: Receiver of the royalty346 /// - `royalty`: Permillage reward from each trade for the Recipient347 /// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash348 /// - `transferable`: Ability to transfer this NFT349 #[transactional]350 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]351 pub fn mint_nft(352 origin: OriginFor<T>,353 owner: T::AccountId,354 collection_id: RmrkCollectionId,355 recipient: Option<T::AccountId>,356 royalty_amount: Option<Permill>,357 metadata: RmrkString,358 transferable: bool,359 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,360 ) -> DispatchResult {361 let sender = ensure_signed(origin)?;362 let cross_sender = T::CrossAccountId::from_sub(sender.clone());363 let cross_owner = T::CrossAccountId::from_sub(owner.clone());364365 let collection = Self::get_typed_nft_collection(366 Self::unique_collection_id(collection_id)?,367 misc::CollectionType::Regular,368 )?;369 collection.check_is_external()?;370371 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {372 recipient: recipient.unwrap_or_else(|| owner.clone()),373 amount,374 });375376 let nft_id = Self::create_nft(377 &cross_sender,378 &cross_owner,379 &collection,380 [381 Self::rmrk_property(TokenType, &NftType::Regular)?,382 Self::rmrk_property(Transferable, &transferable)?,383 Self::rmrk_property(PendingNftAccept, &false)?,384 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,385 Self::rmrk_property(Metadata, &metadata)?,386 Self::rmrk_property(Equipped, &false)?,387 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,388 Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,389 ]390 .into_iter(),391 )392 .map_err(|err| match err {393 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),394 err => Self::map_unique_err_to_proxy(err),395 })?;396397 if let Some(resources) = resources {398 for resource in resources {399 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;400 }401 }402403 Self::deposit_event(Event::NftMinted {404 owner,405 collection_id,406 nft_id: nft_id.0,407 });408409 Ok(())410 }411412 /// burn nft413 #[transactional]414 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]415 pub fn burn_nft(416 origin: OriginFor<T>,417 collection_id: RmrkCollectionId,418 nft_id: RmrkNftId,419 max_burns: u32,420 ) -> DispatchResult {421 let sender = ensure_signed(origin)?;422 let cross_sender = T::CrossAccountId::from_sub(sender.clone());423424 let collection = Self::get_typed_nft_collection(425 Self::unique_collection_id(collection_id)?,426 misc::CollectionType::Regular,427 )?;428 collection.check_is_external()?;429430 Self::destroy_nft(431 cross_sender,432 Self::unique_collection_id(collection_id)?,433 nft_id.into(),434 max_burns,435 <Error<T>>::NoPermission,436 )437 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;438439 Self::deposit_event(Event::NFTBurned {440 owner: sender,441 nft_id,442 });443444 Ok(())445 }446447 /// Transfers a NFT from an Account or NFT A to another Account or NFT B448 ///449 /// Parameters:450 /// - `origin`: sender of the transaction451 /// - `rmrk_collection_id`: collection id of the nft to be transferred452 /// - `rmrk_nft_id`: nft id of the nft to be transferred453 /// - `new_owner`: new owner of the nft which can be either an account or a NFT454 #[transactional]455 #[pallet::weight(<SelfWeightOf<T>>::send())]456 pub fn send(457 origin: OriginFor<T>,458 rmrk_collection_id: RmrkCollectionId,459 rmrk_nft_id: RmrkNftId,460 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,461 ) -> DispatchResult {462 let sender = ensure_signed(origin.clone())?;463 let cross_sender = T::CrossAccountId::from_sub(sender.clone());464465 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;466 let nft_id = rmrk_nft_id.into();467468 let collection =469 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;470 collection.check_is_external()?;471472 let token_data =473 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;474475 let from = token_data.owner;476477 ensure!(478 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,479 <Error<T>>::NonTransferable480 );481482 ensure!(483 !Self::get_nft_property_decoded(484 collection_id,485 nft_id,486 RmrkProperty::PendingNftAccept487 )?,488 <Error<T>>::NoPermission489 );490491 let target_owner;492 let approval_required;493494 match new_owner {495 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {496 target_owner = T::CrossAccountId::from_sub(account_id.clone());497 approval_required = false;498 }499 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(500 target_collection_id,501 target_nft_id,502 ) => {503 let target_collection_id = Self::unique_collection_id(target_collection_id)?;504505 let target_nft_budget = budget::Value::new(NESTING_BUDGET);506507 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(508 target_collection_id,509 target_nft_id.into(),510 Some((collection_id, nft_id)),511 &target_nft_budget,512 )513 .map_err(Self::map_unique_err_to_proxy)?;514515 approval_required = cross_sender != target_nft_owner;516517 if approval_required {518 target_owner = target_nft_owner;519520 <PalletNft<T>>::set_scoped_token_property(521 collection.id,522 nft_id,523 PropertyScope::Rmrk,524 Self::rmrk_property(PendingNftAccept, &approval_required)?,525 )?;526 } else {527 target_owner = T::CrossTokenAddressMapping::token_to_address(528 target_collection_id,529 target_nft_id.into(),530 );531 }532 }533 }534535 let src_nft_budget = budget::Value::new(NESTING_BUDGET);536537 <PalletNft<T>>::transfer_from(538 &collection,539 &cross_sender,540 &from,541 &target_owner,542 nft_id,543 &src_nft_budget,544 )545 .map_err(Self::map_unique_err_to_proxy)?;546547 Self::deposit_event(Event::NFTSent {548 sender,549 recipient: new_owner,550 collection_id: rmrk_collection_id,551 nft_id: rmrk_nft_id,552 approval_required,553 });554555 Ok(())556 }557558 /// Accepts an NFT sent from another account to self or owned NFT559 ///560 /// Parameters:561 /// - `origin`: sender of the transaction562 /// - `rmrk_collection_id`: collection id of the nft to be accepted563 /// - `rmrk_nft_id`: nft id of the nft to be accepted564 /// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was565 /// sent to566 #[transactional]567 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]568 pub fn accept_nft(569 origin: OriginFor<T>,570 rmrk_collection_id: RmrkCollectionId,571 rmrk_nft_id: RmrkNftId,572 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,573 ) -> DispatchResult {574 let sender = ensure_signed(origin.clone())?;575 let cross_sender = T::CrossAccountId::from_sub(sender.clone());576577 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;578 let nft_id = rmrk_nft_id.into();579580 let collection =581 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;582 collection.check_is_external()?;583584 let new_cross_owner = match new_owner {585 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {586 T::CrossAccountId::from_sub(account_id.clone())587 }588 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(589 target_collection_id,590 target_nft_id,591 ) => {592 let target_collection_id = Self::unique_collection_id(target_collection_id)?;593594 T::CrossTokenAddressMapping::token_to_address(595 target_collection_id,596 TokenId(target_nft_id),597 )598 }599 };600601 let budget = budget::Value::new(NESTING_BUDGET);602603 <PalletNft<T>>::transfer(604 &collection,605 &cross_sender,606 &new_cross_owner,607 nft_id,608 &budget,609 )610 .map_err(|err| {611 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {612 <Error<T>>::CannotAcceptNonOwnedNft.into()613 } else {614 Self::map_unique_err_to_proxy(err)615 }616 })?;617618 <PalletNft<T>>::set_scoped_token_property(619 collection.id,620 nft_id,621 PropertyScope::Rmrk,622 Self::rmrk_property(PendingNftAccept, &false)?,623 )?;624625 Self::deposit_event(Event::NFTAccepted {626 sender,627 recipient: new_owner,628 collection_id: rmrk_collection_id,629 nft_id: rmrk_nft_id,630 });631632 Ok(())633 }634635 /// Rejects an NFT sent from another account to self or owned NFT636 ///637 /// Parameters:638 /// - `origin`: sender of the transaction639 /// - `rmrk_collection_id`: collection id of the nft to be accepted640 /// - `rmrk_nft_id`: nft id of the nft to be accepted641 #[transactional]642 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]643 pub fn reject_nft(644 origin: OriginFor<T>,645 rmrk_collection_id: RmrkCollectionId,646 rmrk_nft_id: RmrkNftId,647 ) -> DispatchResult {648 let sender = ensure_signed(origin)?;649 let cross_sender = T::CrossAccountId::from_sub(sender.clone());650651 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;652 let nft_id = rmrk_nft_id.into();653654 let collection =655 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;656 collection.check_is_external()?;657658 ensure!(659 <TokenData<T>>::get((collection_id, nft_id)).is_some(),660 <Error<T>>::NoAvailableNftId661 );662663 ensure!(664 Self::get_nft_property_decoded(665 collection_id,666 nft_id,667 RmrkProperty::PendingNftAccept668 )?,669 <Error<T>>::CannotRejectNonPendingNft670 );671672 Self::destroy_nft(673 cross_sender,674 collection_id,675 nft_id,676 NESTING_BUDGET,677 <Error<T>>::CannotRejectNonOwnedNft,678 )679 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;680681 Self::deposit_event(Event::NFTRejected {682 sender,683 collection_id: rmrk_collection_id,684 nft_id: rmrk_nft_id,685 });686687 Ok(())688 }689690 /// accept the addition of a new resource to an existing NFT691 #[transactional]692 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]693 pub fn accept_resource(694 origin: OriginFor<T>,695 rmrk_collection_id: RmrkCollectionId,696 rmrk_nft_id: RmrkNftId,697 resource_id: RmrkResourceId,698 ) -> DispatchResult {699 let sender = ensure_signed(origin)?;700 let cross_sender = T::CrossAccountId::from_sub(sender);701702 let collection_id = Self::unique_collection_id(rmrk_collection_id)703 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;704 let collection =705 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;706 collection.check_is_external()?;707708 let nft_id = rmrk_nft_id.into();709710 let budget = budget::Value::new(NESTING_BUDGET);711712 let nft_owner =713 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)714 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;715716 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {717 ensure!(res.pending, <Error<T>>::ResourceNotPending);718 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);719720 res.pending = false;721722 Ok(())723 })?;724725 Self::deposit_event(Event::<T>::ResourceAccepted {726 nft_id: rmrk_nft_id,727 resource_id,728 });729730 Ok(())731 }732733 /// accept the removal of a resource of an existing NFT734 #[transactional]735 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]736 pub fn accept_resource_removal(737 origin: OriginFor<T>,738 rmrk_collection_id: RmrkCollectionId,739 rmrk_nft_id: RmrkNftId,740 resource_id: RmrkResourceId,741 ) -> DispatchResult {742 let sender = ensure_signed(origin)?;743 let cross_sender = T::CrossAccountId::from_sub(sender);744745 let collection_id = Self::unique_collection_id(rmrk_collection_id)746 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;747 let collection =748 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;749 collection.check_is_external()?;750751 let nft_id = rmrk_nft_id.into();752753 let budget = budget::Value::new(NESTING_BUDGET);754755 let nft_owner =756 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)757 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;758759 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);760761 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;762763 let resource_info = <PalletNft<T>>::token_sys_property((764 collection_id,765 nft_id,766 PropertyScope::Rmrk,767 resource_id_key.clone(),768 ))769 .ok_or(<Error<T>>::ResourceDoesntExist)?;770771 let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;772773 ensure!(774 resource_info.pending_removal,775 <Error<T>>::ResourceNotPending776 );777778 <PalletNft<T>>::remove_token_sys_property(779 collection_id,780 nft_id,781 PropertyScope::Rmrk,782 resource_id_key,783 );784785 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {786 nft_id: rmrk_nft_id,787 resource_id,788 });789790 Ok(())791 }792793 /// set a custom value on an NFT794 #[transactional]795 #[pallet::weight(<SelfWeightOf<T>>::set_property())]796 pub fn set_property(797 origin: OriginFor<T>,798 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,799 maybe_nft_id: Option<RmrkNftId>,800 key: RmrkKeyString,801 value: RmrkValueString,802 ) -> DispatchResult {803 let sender = ensure_signed(origin)?;804 let sender = T::CrossAccountId::from_sub(sender);805806 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;807 let collection =808 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;809 collection.check_is_external()?;810811 let budget = budget::Value::new(NESTING_BUDGET);812813 match maybe_nft_id {814 Some(nft_id) => {815 let token_id: TokenId = nft_id.into();816817 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;818 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;819820 <PalletNft<T>>::set_scoped_token_property(821 collection_id,822 token_id,823 PropertyScope::Rmrk,824 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,825 )?;826 }827 None => {828 let collection = Self::get_typed_nft_collection(829 collection_id,830 misc::CollectionType::Regular,831 )?;832833 Self::check_collection_owner(&collection, &sender)?;834835 <PalletCommon<T>>::set_scoped_collection_property(836 collection_id,837 PropertyScope::Rmrk,838 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,839 )?;840 }841 }842843 Self::deposit_event(Event::PropertySet {844 collection_id: rmrk_collection_id,845 maybe_nft_id,846 key,847 value,848 });849850 Ok(())851 }852853 /// set a different order of resource priority854 #[transactional]855 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]856 pub fn set_priority(857 origin: OriginFor<T>,858 rmrk_collection_id: RmrkCollectionId,859 rmrk_nft_id: RmrkNftId,860 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,861 ) -> DispatchResult {862 let sender = ensure_signed(origin)?;863 let sender = T::CrossAccountId::from_sub(sender);864865 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;866 let nft_id = rmrk_nft_id.into();867868 let collection =869 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;870 collection.check_is_external()?;871872 let budget = budget::Value::new(NESTING_BUDGET);873874 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;875 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;876877 <PalletNft<T>>::set_scoped_token_property(878 collection_id,879 nft_id,880 PropertyScope::Rmrk,881 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,882 )?;883884 Self::deposit_event(Event::<T>::PrioritySet {885 collection_id: rmrk_collection_id,886 nft_id: rmrk_nft_id,887 });888889 Ok(())890 }891892 /// Create basic resource893 #[transactional]894 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]895 pub fn add_basic_resource(896 origin: OriginFor<T>,897 rmrk_collection_id: RmrkCollectionId,898 nft_id: RmrkNftId,899 resource: RmrkBasicResource,900 ) -> DispatchResult {901 let sender = ensure_signed(origin.clone())?;902903 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;904 let collection =905 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;906 collection.check_is_external()?;907908 let resource_id = Self::resource_add(909 sender,910 collection_id,911 nft_id.into(),912 RmrkResourceTypes::Basic(resource),913 )?;914915 Self::deposit_event(Event::ResourceAdded {916 nft_id,917 resource_id,918 });919 Ok(())920 }921922 /// Create composable resource923 #[transactional]924 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]925 pub fn add_composable_resource(926 origin: OriginFor<T>,927 rmrk_collection_id: RmrkCollectionId,928 nft_id: RmrkNftId,929 resource: RmrkComposableResource,930 ) -> DispatchResult {931 let sender = ensure_signed(origin.clone())?;932933 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;934 let collection =935 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;936 collection.check_is_external()?;937938 let resource_id = Self::resource_add(939 sender,940 collection_id,941 nft_id.into(),942 RmrkResourceTypes::Composable(resource),943 )?;944945 Self::deposit_event(Event::ResourceAdded {946 nft_id,947 resource_id,948 });949 Ok(())950 }951952 /// Create slot resource953 #[transactional]954 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]955 pub fn add_slot_resource(956 origin: OriginFor<T>,957 rmrk_collection_id: RmrkCollectionId,958 nft_id: RmrkNftId,959 resource: RmrkSlotResource,960 ) -> DispatchResult {961 let sender = ensure_signed(origin.clone())?;962963 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;964 let collection =965 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;966 collection.check_is_external()?;967968 let resource_id = Self::resource_add(969 sender,970 collection_id,971 nft_id.into(),972 RmrkResourceTypes::Slot(resource),973 )?;974975 Self::deposit_event(Event::ResourceAdded {976 nft_id,977 resource_id,978 });979 Ok(())980 }981982 /// remove resource983 #[transactional]984 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]985 pub fn remove_resource(986 origin: OriginFor<T>,987 rmrk_collection_id: RmrkCollectionId,988 nft_id: RmrkNftId,989 resource_id: RmrkResourceId,990 ) -> DispatchResult {991 let sender = ensure_signed(origin.clone())?;992993 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;994 let collection =995 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;996 collection.check_is_external()?;997998 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;9991000 Self::deposit_event(Event::ResourceRemoval {1001 nft_id,1002 resource_id,1003 });1004 Ok(())1005 }1006 }1007}10081009impl<T: Config> Pallet<T> {1010 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1011 let key = rmrk_key.to_key::<T>()?;10121013 let scoped_key = PropertyScope::Rmrk1014 .apply(key)1015 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10161017 Ok(scoped_key)1018 }10191020 // todo think about renaming these1021 pub fn rmrk_property<E: Encode>(1022 rmrk_key: RmrkProperty,1023 value: &E,1024 ) -> Result<Property, DispatchError> {1025 let key = rmrk_key.to_key::<T>()?;10261027 let value = Self::encode_property(value)?;10281029 let property = Property { key, value };10301031 Ok(property)1032 }10331034 pub fn encode_property<E: Encode, S: Get<u32>>(1035 value: &E,1036 ) -> Result<BoundedBytes<S>, DispatchError> {1037 let value = value1038 .encode()1039 .try_into()1040 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10411042 Ok(value)1043 }10441045 pub fn decode_property<D: Decode, S: Get<u32>>(1046 vec: &BoundedBytes<S>,1047 ) -> Result<D, DispatchError> {1048 vec.decode()1049 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1050 }10511052 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1053 where1054 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1055 {1056 vec.rebind()1057 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1058 }10591060 fn init_collection(1061 sender: T::CrossAccountId,1062 data: CreateCollectionData<T::AccountId>,1063 properties: impl Iterator<Item = Property>,1064 ) -> Result<CollectionId, DispatchError> {1065 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10661067 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1068 return Err(<Error<T>>::NoAvailableCollectionId.into());1069 }10701071 <PalletCommon<T>>::set_scoped_collection_properties(1072 collection_id?,1073 PropertyScope::Rmrk,1074 properties,1075 )?;10761077 collection_id1078 }10791080 pub fn create_nft(1081 sender: &T::CrossAccountId,1082 owner: &T::CrossAccountId,1083 collection: &NonfungibleHandle<T>,1084 properties: impl Iterator<Item = Property>,1085 ) -> Result<TokenId, DispatchError> {1086 let data = CreateNftExData {1087 properties: BoundedVec::default(),1088 owner: owner.clone(),1089 };10901091 let budget = budget::Value::new(NESTING_BUDGET);10921093 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;10941095 let nft_id = <PalletNft<T>>::current_token_id(collection.id);10961097 <PalletNft<T>>::set_scoped_token_properties(1098 collection.id,1099 nft_id,1100 PropertyScope::Rmrk,1101 properties,1102 )?;11031104 Ok(nft_id)1105 }11061107 fn destroy_nft(1108 sender: T::CrossAccountId,1109 collection_id: CollectionId,1110 token_id: TokenId,1111 max_burns: u32,1112 error_if_not_owned: Error<T>,1113 ) -> DispatchResultWithPostInfo {1114 let collection =1115 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11161117 let token_data =1118 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11191120 let from = token_data.owner;11211122 let owner_check_budget = budget::Value::new(NESTING_BUDGET);11231124 ensure!(1125 <PalletStructure<T>>::check_indirectly_owned(1126 sender.clone(),1127 collection_id,1128 token_id,1129 None,1130 &owner_check_budget1131 )?,1132 error_if_not_owned,1133 );11341135 let burns_budget = budget::Value::new(max_burns);1136 let breadth_budget = budget::Value::new(max_burns);11371138 <PalletNft<T>>::burn_recursively(1139 &collection,1140 &from,1141 token_id,1142 &burns_budget,1143 &breadth_budget,1144 )1145 }11461147 fn acquire_next_resource_id(1148 collection_id: CollectionId,1149 nft_id: TokenId,1150 ) -> Result<RmrkResourceId, DispatchError> {1151 let resource_id: RmrkResourceId =1152 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;11531154 let next_id = resource_id1155 .checked_add(1)1156 .ok_or(<Error<T>>::NoAvailableResourceId)?;11571158 <PalletNft<T>>::set_scoped_token_property(1159 collection_id,1160 nft_id,1161 PropertyScope::Rmrk,1162 Self::rmrk_property(NextResourceId, &next_id)?,1163 )?;11641165 Ok(resource_id)1166 }11671168 fn resource_add(1169 sender: T::AccountId,1170 collection_id: CollectionId,1171 nft_id: TokenId,1172 resource: RmrkResourceTypes,1173 ) -> Result<RmrkResourceId, DispatchError> {1174 let collection =1175 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1176 ensure!(collection.owner == sender, Error::<T>::NoPermission);11771178 let sender = T::CrossAccountId::from_sub(sender);1179 let budget = budget::Value::new(NESTING_BUDGET);11801181 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1182 .map_err(Self::map_unique_err_to_proxy)?;11831184 let pending = sender != nft_owner;11851186 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;11871188 let resource_info = RmrkResourceInfo {1189 id,1190 resource,1191 pending,1192 pending_removal: false,1193 };11941195 <PalletNft<T>>::try_mutate_token_sys_property(1196 collection_id,1197 nft_id,1198 PropertyScope::Rmrk,1199 Self::rmrk_property_key(ResourceId(id))?,1200 |value| -> DispatchResult {1201 *value = Some(Self::encode_property(&resource_info)?);12021203 Ok(())1204 },1205 )?;12061207 Ok(id)1208 }12091210 fn resource_remove(1211 sender: T::AccountId,1212 collection_id: CollectionId,1213 nft_id: TokenId,1214 resource_id: RmrkResourceId,1215 ) -> DispatchResult {1216 let collection =1217 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1218 ensure!(collection.owner == sender, Error::<T>::NoPermission);12191220 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1221 let scope = PropertyScope::Rmrk;12221223 ensure!(1224 <PalletNft<T>>::token_sys_property((1225 collection_id,1226 nft_id,1227 scope,1228 resource_id_key.clone()1229 ))1230 .is_some(),1231 <Error<T>>::ResourceDoesntExist1232 );12331234 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1235 let topmost_owner =1236 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12371238 let sender = T::CrossAccountId::from_sub(sender);1239 if topmost_owner == sender {1240 <PalletNft<T>>::remove_token_sys_property(1241 collection_id,1242 nft_id,1243 PropertyScope::Rmrk,1244 Self::rmrk_property_key(ResourceId(resource_id))?,1245 );1246 } else {1247 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1248 res.pending_removal = true;12491250 Ok(())1251 })?;1252 }12531254 Ok(())1255 }12561257 fn try_mutate_resource_info(1258 collection_id: CollectionId,1259 nft_id: TokenId,1260 resource_id: RmrkResourceId,1261 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1262 ) -> DispatchResult {1263 <PalletNft<T>>::try_mutate_token_sys_property(1264 collection_id,1265 nft_id,1266 PropertyScope::Rmrk,1267 Self::rmrk_property_key(ResourceId(resource_id))?,1268 |value| match value {1269 Some(value) => {1270 let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;12711272 f(&mut resource_info)?;12731274 *value = Self::encode_property(&resource_info)?;12751276 Ok(())1277 }1278 None => Err(<Error<T>>::ResourceDoesntExist.into()),1279 },1280 )1281 }12821283 fn change_collection_owner(1284 collection_id: CollectionId,1285 collection_type: misc::CollectionType,1286 sender: T::AccountId,1287 new_owner: T::AccountId,1288 ) -> DispatchResult {1289 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1290 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12911292 let mut collection = collection.into_inner();12931294 collection.owner = new_owner;1295 collection.save()1296 }12971298 fn check_collection_owner(1299 collection: &NonfungibleHandle<T>,1300 account: &T::CrossAccountId,1301 ) -> DispatchResult {1302 collection1303 .check_is_owner(account)1304 .map_err(Self::map_unique_err_to_proxy)1305 }13061307 pub fn last_collection_idx() -> RmrkCollectionId {1308 <CollectionIndex<T>>::get()1309 }13101311 pub fn unique_collection_id(1312 rmrk_collection_id: RmrkCollectionId,1313 ) -> Result<CollectionId, DispatchError> {1314 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1315 .map_err(|_| <Error<T>>::CollectionUnknown.into())1316 }13171318 pub fn rmrk_collection_id(1319 unique_collection_id: CollectionId,1320 ) -> Result<RmrkCollectionId, DispatchError> {1321 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1322 }13231324 pub fn get_nft_collection(1325 collection_id: CollectionId,1326 ) -> Result<NonfungibleHandle<T>, DispatchError> {1327 let collection = <CollectionHandle<T>>::try_get(collection_id)1328 .map_err(|_| <Error<T>>::CollectionUnknown)?;13291330 match collection.mode {1331 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1332 _ => Err(<Error<T>>::CollectionUnknown.into()),1333 }1334 }13351336 pub fn collection_exists(collection_id: CollectionId) -> bool {1337 <CollectionHandle<T>>::try_get(collection_id).is_ok()1338 }13391340 pub fn get_collection_property(1341 collection_id: CollectionId,1342 key: RmrkProperty,1343 ) -> Result<PropertyValue, DispatchError> {1344 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1345 .get(&Self::rmrk_property_key(key)?)1346 .ok_or(<Error<T>>::CollectionUnknown)?1347 .clone();13481349 Ok(collection_property)1350 }13511352 pub fn get_collection_property_decoded<V: Decode>(1353 collection_id: CollectionId,1354 key: RmrkProperty,1355 ) -> Result<V, DispatchError> {1356 Self::decode_property(&Self::get_collection_property(collection_id, key)?)1357 }13581359 pub fn get_collection_type(1360 collection_id: CollectionId,1361 ) -> Result<misc::CollectionType, DispatchError> {1362 Self::get_collection_property_decoded(collection_id, CollectionType)1363 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1364 }13651366 pub fn ensure_collection_type(1367 collection_id: CollectionId,1368 collection_type: misc::CollectionType,1369 ) -> DispatchResult {1370 let actual_type = Self::get_collection_type(collection_id)?;1371 ensure!(1372 actual_type == collection_type,1373 <CommonError<T>>::NoPermission1374 );13751376 Ok(())1377 }13781379 pub fn get_typed_nft_collection(1380 collection_id: CollectionId,1381 collection_type: misc::CollectionType,1382 ) -> Result<NonfungibleHandle<T>, DispatchError> {1383 Self::ensure_collection_type(collection_id, collection_type)?;13841385 Self::get_nft_collection(collection_id)1386 }13871388 pub fn get_typed_nft_collection_mapped(1389 rmrk_collection_id: RmrkCollectionId,1390 collection_type: misc::CollectionType,1391 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1392 let unique_collection_id = match collection_type {1393 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1394 _ => rmrk_collection_id.into(),1395 };13961397 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;13981399 Ok((collection, unique_collection_id))1400 }14011402 pub fn get_nft_property(1403 collection_id: CollectionId,1404 nft_id: TokenId,1405 key: RmrkProperty,1406 ) -> Result<PropertyValue, DispatchError> {1407 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1408 .get(&Self::rmrk_property_key(key)?)1409 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1410 .clone();14111412 Ok(nft_property)1413 }14141415 pub fn get_nft_property_decoded<V: Decode>(1416 collection_id: CollectionId,1417 nft_id: TokenId,1418 key: RmrkProperty,1419 ) -> Result<V, DispatchError> {1420 Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1421 }14221423 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1424 <TokenData<T>>::contains_key((collection_id, nft_id))1425 }14261427 pub fn get_nft_type(1428 collection_id: CollectionId,1429 token_id: TokenId,1430 ) -> Result<NftType, DispatchError> {1431 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1432 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1433 }14341435 pub fn ensure_nft_type(1436 collection_id: CollectionId,1437 token_id: TokenId,1438 nft_type: NftType,1439 ) -> DispatchResult {1440 let actual_type = Self::get_nft_type(collection_id, token_id)?;1441 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14421443 Ok(())1444 }14451446 pub fn ensure_nft_owner(1447 collection_id: CollectionId,1448 token_id: TokenId,1449 possible_owner: &T::CrossAccountId,1450 nesting_budget: &dyn budget::Budget,1451 ) -> DispatchResult {1452 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1453 possible_owner.clone(),1454 collection_id,1455 token_id,1456 None,1457 nesting_budget,1458 )1459 .map_err(Self::map_unique_err_to_proxy)?;14601461 ensure!(is_owned, <Error<T>>::NoPermission);14621463 Ok(())1464 }14651466 pub fn filter_user_properties<Key, Value, R, Mapper>(1467 collection_id: CollectionId,1468 token_id: Option<TokenId>,1469 filter_keys: Option<Vec<RmrkPropertyKey>>,1470 mapper: Mapper,1471 ) -> Result<Vec<R>, DispatchError>1472 where1473 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1474 Value: Decode + Default,1475 Mapper: Fn(Key, Value) -> R,1476 {1477 filter_keys1478 .map(|keys| {1479 let properties = keys1480 .into_iter()1481 .filter_map(|key| {1482 let key: Key = key.try_into().ok()?;14831484 let value = match token_id {1485 Some(token_id) => Self::get_nft_property_decoded(1486 collection_id,1487 token_id,1488 UserProperty(key.as_ref()),1489 ),1490 None => Self::get_collection_property_decoded(1491 collection_id,1492 UserProperty(key.as_ref()),1493 ),1494 }1495 .ok()?;14961497 Some(mapper(key, value))1498 })1499 .collect();15001501 Ok(properties)1502 })1503 .unwrap_or_else(|| {1504 let properties =1505 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15061507 Ok(properties)1508 })1509 }15101511 pub fn iterate_user_properties<Key, Value, R, Mapper>(1512 collection_id: CollectionId,1513 token_id: Option<TokenId>,1514 mapper: Mapper,1515 ) -> Result<impl Iterator<Item = R>, DispatchError>1516 where1517 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1518 Value: Decode + Default,1519 Mapper: Fn(Key, Value) -> R,1520 {1521 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15221523 let properties = match token_id {1524 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1525 None => <PalletCommon<T>>::collection_properties(collection_id),1526 };15271528 let properties = properties.into_iter().filter_map(move |(key, value)| {1529 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15301531 let key: Key = key.to_vec().try_into().ok()?;1532 let value: Value = value.decode().ok()?;15331534 Some(mapper(key, value))1535 });15361537 Ok(properties)1538 }15391540 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1541 map_unique_err_to_proxy! {1542 match err {1543 CommonError::NoPermission => NoPermission,1544 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1545 CommonError::PublicMintingNotAllowed => NoPermission,1546 CommonError::TokenNotFound => NoAvailableNftId,1547 CommonError::ApprovedValueTooLow => NoPermission,1548 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1549 StructureError::TokenNotFound => NoAvailableNftId,1550 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1551 }1552 }1553 }1554}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;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 /* Unique-specific events */153 CorruptedCollectionType,154 NftTypeEncodeError,155 RmrkPropertyKeyIsTooLong,156 RmrkPropertyValueIsTooLong,157 RmrkPropertyIsNotFound,158 UnableToDecodeRmrkData,159160 /* RMRK compatible events */161 CollectionNotEmpty,162 NoAvailableCollectionId,163 NoAvailableNftId,164 CollectionUnknown,165 NoPermission,166 NonTransferable,167 CollectionFullOrLocked,168 ResourceDoesntExist,169 CannotSendToDescendentOrSelf,170 CannotAcceptNonOwnedNft,171 CannotRejectNonOwnedNft,172 CannotRejectNonPendingNft,173 ResourceNotPending,174 NoAvailableResourceId,175 }176177 #[pallet::call]178 impl<T: Config> Pallet<T> {179 /// Create a collection180 #[transactional]181 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]182 pub fn create_collection(183 origin: OriginFor<T>,184 metadata: RmrkString,185 max: Option<u32>,186 symbol: RmrkCollectionSymbol,187 ) -> DispatchResult {188 let sender = ensure_signed(origin)?;189190 let limits = CollectionLimits {191 owner_can_transfer: Some(false),192 token_limit: max,193 ..Default::default()194 };195196 let data = CreateCollectionData {197 limits: Some(limits),198 token_prefix: symbol199 .into_inner()200 .try_into()201 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,202 permissions: Some(CollectionPermissions {203 nesting: Some(NestingPermissions {204 token_owner: true,205 collection_admin: false,206 restricted: None,207208 permissive: false,209 }),210 ..Default::default()211 }),212 ..Default::default()213 };214215 let unique_collection_id = Self::init_collection(216 T::CrossAccountId::from_sub(sender.clone()),217 data,218 [219 Self::rmrk_property(Metadata, &metadata)?,220 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,221 ]222 .into_iter(),223 )?;224 let rmrk_collection_id = <CollectionIndex<T>>::get();225226 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);227228 <PalletCommon<T>>::set_scoped_collection_property(229 unique_collection_id,230 PropertyScope::Rmrk,231 Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,232 )?;233234 <CollectionIndex<T>>::mutate(|n| *n += 1);235236 Self::deposit_event(Event::CollectionCreated {237 issuer: sender,238 collection_id: rmrk_collection_id,239 });240241 Ok(())242 }243244 /// destroy collection245 #[transactional]246 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]247 pub fn destroy_collection(248 origin: OriginFor<T>,249 collection_id: RmrkCollectionId,250 ) -> DispatchResult {251 let sender = ensure_signed(origin)?;252 let cross_sender = T::CrossAccountId::from_sub(sender.clone());253254 let collection = Self::get_typed_nft_collection(255 Self::unique_collection_id(collection_id)?,256 misc::CollectionType::Regular,257 )?;258 collection.check_is_external()?;259260 <PalletNft<T>>::destroy_collection(collection, &cross_sender)261 .map_err(Self::map_unique_err_to_proxy)?;262263 Self::deposit_event(Event::CollectionDestroyed {264 issuer: sender,265 collection_id,266 });267268 Ok(())269 }270271 /// Change the issuer of a collection272 ///273 /// Parameters:274 /// - `origin`: sender of the transaction275 /// - `collection_id`: collection id of the nft to change issuer of276 /// - `new_issuer`: Collection's new issuer277 #[transactional]278 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]279 pub fn change_collection_issuer(280 origin: OriginFor<T>,281 collection_id: RmrkCollectionId,282 new_issuer: <T::Lookup as StaticLookup>::Source,283 ) -> DispatchResult {284 let sender = ensure_signed(origin)?;285286 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;287 collection.check_is_external()?;288289 let new_issuer = T::Lookup::lookup(new_issuer)?;290291 Self::change_collection_owner(292 Self::unique_collection_id(collection_id)?,293 misc::CollectionType::Regular,294 sender.clone(),295 new_issuer.clone(),296 )?;297298 Self::deposit_event(Event::IssuerChanged {299 old_issuer: sender,300 new_issuer,301 collection_id,302 });303304 Ok(())305 }306307 /// lock collection308 #[transactional]309 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]310 pub fn lock_collection(311 origin: OriginFor<T>,312 collection_id: RmrkCollectionId,313 ) -> DispatchResult {314 let sender = ensure_signed(origin)?;315 let cross_sender = T::CrossAccountId::from_sub(sender.clone());316317 let collection = Self::get_typed_nft_collection(318 Self::unique_collection_id(collection_id)?,319 misc::CollectionType::Regular,320 )?;321 collection.check_is_external()?;322323 Self::check_collection_owner(&collection, &cross_sender)?;324325 let token_count = collection.total_supply();326327 let mut collection = collection.into_inner();328 collection.limits.token_limit = Some(token_count);329 collection.save()?;330331 Self::deposit_event(Event::CollectionLocked {332 issuer: sender,333 collection_id,334 });335336 Ok(())337 }338339 /// Mints an NFT in the specified collection340 /// Sets metadata and the royalty attribute341 ///342 /// Parameters:343 /// - `collection_id`: The class of the asset to be minted.344 /// - `nft_id`: The nft value of the asset to be minted.345 /// - `recipient`: Receiver of the royalty346 /// - `royalty`: Permillage reward from each trade for the Recipient347 /// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash348 /// - `transferable`: Ability to transfer this NFT349 #[transactional]350 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]351 pub fn mint_nft(352 origin: OriginFor<T>,353 owner: Option<T::AccountId>,354 collection_id: RmrkCollectionId,355 recipient: Option<T::AccountId>,356 royalty_amount: Option<Permill>,357 metadata: RmrkString,358 transferable: bool,359 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,360 ) -> DispatchResult {361 let sender = ensure_signed(origin)?;362 let cross_sender = T::CrossAccountId::from_sub(sender.clone());363364 let owner = owner.unwrap_or(sender.clone());365 let cross_owner = T::CrossAccountId::from_sub(owner.clone());366367 let collection = Self::get_typed_nft_collection(368 Self::unique_collection_id(collection_id)?,369 misc::CollectionType::Regular,370 )?;371 collection.check_is_external()?;372373 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {374 recipient: recipient.unwrap_or_else(|| owner.clone()),375 amount,376 });377378 let nft_id = Self::create_nft(379 &cross_sender,380 &cross_owner,381 &collection,382 [383 Self::rmrk_property(TokenType, &NftType::Regular)?,384 Self::rmrk_property(Transferable, &transferable)?,385 Self::rmrk_property(PendingNftAccept, &false)?,386 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,387 Self::rmrk_property(Metadata, &metadata)?,388 Self::rmrk_property(Equipped, &false)?,389 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,390 Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,391 ]392 .into_iter(),393 )394 .map_err(|err| match err {395 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),396 err => Self::map_unique_err_to_proxy(err),397 })?;398399 if let Some(resources) = resources {400 for resource in resources {401 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;402 }403 }404405 Self::deposit_event(Event::NftMinted {406 owner,407 collection_id,408 nft_id: nft_id.0,409 });410411 Ok(())412 }413414 /// burn nft415 #[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 /// Transfers a NFT from an Account or NFT A to another Account or NFT B450 ///451 /// Parameters:452 /// - `origin`: sender of the transaction453 /// - `rmrk_collection_id`: collection id of the nft to be transferred454 /// - `rmrk_nft_id`: nft id of the nft to be transferred455 /// - `new_owner`: new owner of the nft which can be either an account or a NFT456 #[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 /// Accepts an NFT sent from another account to self or owned NFT561 ///562 /// Parameters:563 /// - `origin`: sender of the transaction564 /// - `rmrk_collection_id`: collection id of the nft to be accepted565 /// - `rmrk_nft_id`: nft id of the nft to be accepted566 /// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was567 /// sent to568 #[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 /// Rejects an NFT sent from another account to self or owned NFT638 ///639 /// Parameters:640 /// - `origin`: sender of the transaction641 /// - `rmrk_collection_id`: collection id of the nft to be accepted642 /// - `rmrk_nft_id`: nft id of the nft to be accepted643 #[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 <TokenData<T>>::get((collection_id, nft_id)).is_some(),662 <Error<T>>::NoAvailableNftId663 );664665 ensure!(666 Self::get_nft_property_decoded(667 collection_id,668 nft_id,669 RmrkProperty::PendingNftAccept670 )?,671 <Error<T>>::CannotRejectNonPendingNft672 );673674 Self::destroy_nft(675 cross_sender,676 collection_id,677 nft_id,678 NESTING_BUDGET,679 <Error<T>>::CannotRejectNonOwnedNft,680 )681 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;682683 Self::deposit_event(Event::NFTRejected {684 sender,685 collection_id: rmrk_collection_id,686 nft_id: rmrk_nft_id,687 });688689 Ok(())690 }691692 /// accept the addition of a new resource to an existing NFT693 #[transactional]694 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]695 pub fn accept_resource(696 origin: OriginFor<T>,697 rmrk_collection_id: RmrkCollectionId,698 rmrk_nft_id: RmrkNftId,699 resource_id: RmrkResourceId,700 ) -> DispatchResult {701 let sender = ensure_signed(origin)?;702 let cross_sender = T::CrossAccountId::from_sub(sender);703704 let collection_id = Self::unique_collection_id(rmrk_collection_id)705 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;706 let collection =707 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;708 collection.check_is_external()?;709710 let nft_id = rmrk_nft_id.into();711712 let budget = budget::Value::new(NESTING_BUDGET);713714 let nft_owner =715 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)716 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;717718 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {719 ensure!(res.pending, <Error<T>>::ResourceNotPending);720 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);721722 res.pending = false;723724 Ok(())725 })?;726727 Self::deposit_event(Event::<T>::ResourceAccepted {728 nft_id: rmrk_nft_id,729 resource_id,730 });731732 Ok(())733 }734735 /// accept the removal of a resource of an existing NFT736 #[transactional]737 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]738 pub fn accept_resource_removal(739 origin: OriginFor<T>,740 rmrk_collection_id: RmrkCollectionId,741 rmrk_nft_id: RmrkNftId,742 resource_id: RmrkResourceId,743 ) -> DispatchResult {744 let sender = ensure_signed(origin)?;745 let cross_sender = T::CrossAccountId::from_sub(sender);746747 let collection_id = Self::unique_collection_id(rmrk_collection_id)748 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;749 let collection =750 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;751 collection.check_is_external()?;752753 let nft_id = rmrk_nft_id.into();754755 let budget = budget::Value::new(NESTING_BUDGET);756757 let nft_owner =758 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)759 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;760761 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);762763 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;764765 let resource_info = <PalletNft<T>>::token_sys_property((766 collection_id,767 nft_id,768 PropertyScope::Rmrk,769 resource_id_key.clone(),770 ))771 .ok_or(<Error<T>>::ResourceDoesntExist)?;772773 let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;774775 ensure!(776 resource_info.pending_removal,777 <Error<T>>::ResourceNotPending778 );779780 <PalletNft<T>>::remove_token_sys_property(781 collection_id,782 nft_id,783 PropertyScope::Rmrk,784 resource_id_key,785 );786787 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {788 nft_id: rmrk_nft_id,789 resource_id,790 });791792 Ok(())793 }794795 /// set a custom value on an NFT796 #[transactional]797 #[pallet::weight(<SelfWeightOf<T>>::set_property())]798 pub fn set_property(799 origin: OriginFor<T>,800 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,801 maybe_nft_id: Option<RmrkNftId>,802 key: RmrkKeyString,803 value: RmrkValueString,804 ) -> DispatchResult {805 let sender = ensure_signed(origin)?;806 let sender = T::CrossAccountId::from_sub(sender);807808 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;809 let collection =810 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;811 collection.check_is_external()?;812813 let budget = budget::Value::new(NESTING_BUDGET);814815 match maybe_nft_id {816 Some(nft_id) => {817 let token_id: TokenId = nft_id.into();818819 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;820 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;821822 <PalletNft<T>>::set_scoped_token_property(823 collection_id,824 token_id,825 PropertyScope::Rmrk,826 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,827 )?;828 }829 None => {830 let collection = Self::get_typed_nft_collection(831 collection_id,832 misc::CollectionType::Regular,833 )?;834835 Self::check_collection_owner(&collection, &sender)?;836837 <PalletCommon<T>>::set_scoped_collection_property(838 collection_id,839 PropertyScope::Rmrk,840 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,841 )?;842 }843 }844845 Self::deposit_event(Event::PropertySet {846 collection_id: rmrk_collection_id,847 maybe_nft_id,848 key,849 value,850 });851852 Ok(())853 }854855 /// set a different order of resource priority856 #[transactional]857 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]858 pub fn set_priority(859 origin: OriginFor<T>,860 rmrk_collection_id: RmrkCollectionId,861 rmrk_nft_id: RmrkNftId,862 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,863 ) -> DispatchResult {864 let sender = ensure_signed(origin)?;865 let sender = T::CrossAccountId::from_sub(sender);866867 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;868 let nft_id = rmrk_nft_id.into();869870 let collection =871 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;872 collection.check_is_external()?;873874 let budget = budget::Value::new(NESTING_BUDGET);875876 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;877 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;878879 <PalletNft<T>>::set_scoped_token_property(880 collection_id,881 nft_id,882 PropertyScope::Rmrk,883 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,884 )?;885886 Self::deposit_event(Event::<T>::PrioritySet {887 collection_id: rmrk_collection_id,888 nft_id: rmrk_nft_id,889 });890891 Ok(())892 }893894 /// Create basic resource895 #[transactional]896 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]897 pub fn add_basic_resource(898 origin: OriginFor<T>,899 rmrk_collection_id: RmrkCollectionId,900 nft_id: RmrkNftId,901 resource: RmrkBasicResource,902 ) -> DispatchResult {903 let sender = ensure_signed(origin.clone())?;904905 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;906 let collection =907 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;908 collection.check_is_external()?;909910 let resource_id = Self::resource_add(911 sender,912 collection_id,913 nft_id.into(),914 RmrkResourceTypes::Basic(resource),915 )?;916917 Self::deposit_event(Event::ResourceAdded {918 nft_id,919 resource_id,920 });921 Ok(())922 }923924 /// Create composable resource925 #[transactional]926 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]927 pub fn add_composable_resource(928 origin: OriginFor<T>,929 rmrk_collection_id: RmrkCollectionId,930 nft_id: RmrkNftId,931 resource: RmrkComposableResource,932 ) -> DispatchResult {933 let sender = ensure_signed(origin.clone())?;934935 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;936 let collection =937 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;938 collection.check_is_external()?;939940 let resource_id = Self::resource_add(941 sender,942 collection_id,943 nft_id.into(),944 RmrkResourceTypes::Composable(resource),945 )?;946947 Self::deposit_event(Event::ResourceAdded {948 nft_id,949 resource_id,950 });951 Ok(())952 }953954 /// Create slot resource955 #[transactional]956 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]957 pub fn add_slot_resource(958 origin: OriginFor<T>,959 rmrk_collection_id: RmrkCollectionId,960 nft_id: RmrkNftId,961 resource: RmrkSlotResource,962 ) -> DispatchResult {963 let sender = ensure_signed(origin.clone())?;964965 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;966 let collection =967 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;968 collection.check_is_external()?;969970 let resource_id = Self::resource_add(971 sender,972 collection_id,973 nft_id.into(),974 RmrkResourceTypes::Slot(resource),975 )?;976977 Self::deposit_event(Event::ResourceAdded {978 nft_id,979 resource_id,980 });981 Ok(())982 }983984 /// remove resource985 #[transactional]986 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]987 pub fn remove_resource(988 origin: OriginFor<T>,989 rmrk_collection_id: RmrkCollectionId,990 nft_id: RmrkNftId,991 resource_id: RmrkResourceId,992 ) -> DispatchResult {993 let sender = ensure_signed(origin.clone())?;994995 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;996 let collection =997 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;998 collection.check_is_external()?;9991000 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;10011002 Self::deposit_event(Event::ResourceRemoval {1003 nft_id,1004 resource_id,1005 });1006 Ok(())1007 }1008 }1009}10101011impl<T: Config> Pallet<T> {1012 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1013 let key = rmrk_key.to_key::<T>()?;10141015 let scoped_key = PropertyScope::Rmrk1016 .apply(key)1017 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10181019 Ok(scoped_key)1020 }10211022 // todo think about renaming these1023 pub fn rmrk_property<E: Encode>(1024 rmrk_key: RmrkProperty,1025 value: &E,1026 ) -> Result<Property, DispatchError> {1027 let key = rmrk_key.to_key::<T>()?;10281029 let value = Self::encode_property(value)?;10301031 let property = Property { key, value };10321033 Ok(property)1034 }10351036 pub fn encode_property<E: Encode, S: Get<u32>>(1037 value: &E,1038 ) -> Result<BoundedBytes<S>, DispatchError> {1039 let value = value1040 .encode()1041 .try_into()1042 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10431044 Ok(value)1045 }10461047 pub fn decode_property<D: Decode, S: Get<u32>>(1048 vec: &BoundedBytes<S>,1049 ) -> Result<D, DispatchError> {1050 vec.decode()1051 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1052 }10531054 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1055 where1056 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1057 {1058 vec.rebind()1059 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1060 }10611062 fn init_collection(1063 sender: T::CrossAccountId,1064 data: CreateCollectionData<T::AccountId>,1065 properties: impl Iterator<Item = Property>,1066 ) -> Result<CollectionId, DispatchError> {1067 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10681069 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1070 return Err(<Error<T>>::NoAvailableCollectionId.into());1071 }10721073 <PalletCommon<T>>::set_scoped_collection_properties(1074 collection_id?,1075 PropertyScope::Rmrk,1076 properties,1077 )?;10781079 collection_id1080 }10811082 pub fn create_nft(1083 sender: &T::CrossAccountId,1084 owner: &T::CrossAccountId,1085 collection: &NonfungibleHandle<T>,1086 properties: impl Iterator<Item = Property>,1087 ) -> Result<TokenId, DispatchError> {1088 let data = CreateNftExData {1089 properties: BoundedVec::default(),1090 owner: owner.clone(),1091 };10921093 let budget = budget::Value::new(NESTING_BUDGET);10941095 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;10961097 let nft_id = <PalletNft<T>>::current_token_id(collection.id);10981099 <PalletNft<T>>::set_scoped_token_properties(1100 collection.id,1101 nft_id,1102 PropertyScope::Rmrk,1103 properties,1104 )?;11051106 Ok(nft_id)1107 }11081109 fn destroy_nft(1110 sender: T::CrossAccountId,1111 collection_id: CollectionId,1112 token_id: TokenId,1113 max_burns: u32,1114 error_if_not_owned: Error<T>,1115 ) -> DispatchResultWithPostInfo {1116 let collection =1117 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11181119 let token_data =1120 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11211122 let from = token_data.owner;11231124 let owner_check_budget = budget::Value::new(NESTING_BUDGET);11251126 ensure!(1127 <PalletStructure<T>>::check_indirectly_owned(1128 sender.clone(),1129 collection_id,1130 token_id,1131 None,1132 &owner_check_budget1133 )?,1134 error_if_not_owned,1135 );11361137 let burns_budget = budget::Value::new(max_burns);1138 let breadth_budget = budget::Value::new(max_burns);11391140 <PalletNft<T>>::burn_recursively(1141 &collection,1142 &from,1143 token_id,1144 &burns_budget,1145 &breadth_budget,1146 )1147 }11481149 fn acquire_next_resource_id(1150 collection_id: CollectionId,1151 nft_id: TokenId,1152 ) -> Result<RmrkResourceId, DispatchError> {1153 let resource_id: RmrkResourceId =1154 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;11551156 let next_id = resource_id1157 .checked_add(1)1158 .ok_or(<Error<T>>::NoAvailableResourceId)?;11591160 <PalletNft<T>>::set_scoped_token_property(1161 collection_id,1162 nft_id,1163 PropertyScope::Rmrk,1164 Self::rmrk_property(NextResourceId, &next_id)?,1165 )?;11661167 Ok(resource_id)1168 }11691170 fn resource_add(1171 sender: T::AccountId,1172 collection_id: CollectionId,1173 nft_id: TokenId,1174 resource: RmrkResourceTypes,1175 ) -> Result<RmrkResourceId, DispatchError> {1176 let collection =1177 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1178 ensure!(collection.owner == sender, Error::<T>::NoPermission);11791180 let sender = T::CrossAccountId::from_sub(sender);1181 let budget = budget::Value::new(NESTING_BUDGET);11821183 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1184 .map_err(Self::map_unique_err_to_proxy)?;11851186 let pending = sender != nft_owner;11871188 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;11891190 let resource_info = RmrkResourceInfo {1191 id,1192 resource,1193 pending,1194 pending_removal: false,1195 };11961197 <PalletNft<T>>::try_mutate_token_sys_property(1198 collection_id,1199 nft_id,1200 PropertyScope::Rmrk,1201 Self::rmrk_property_key(ResourceId(id))?,1202 |value| -> DispatchResult {1203 *value = Some(Self::encode_property(&resource_info)?);12041205 Ok(())1206 },1207 )?;12081209 Ok(id)1210 }12111212 fn resource_remove(1213 sender: T::AccountId,1214 collection_id: CollectionId,1215 nft_id: TokenId,1216 resource_id: RmrkResourceId,1217 ) -> DispatchResult {1218 let collection =1219 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1220 ensure!(collection.owner == sender, Error::<T>::NoPermission);12211222 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1223 let scope = PropertyScope::Rmrk;12241225 ensure!(1226 <PalletNft<T>>::token_sys_property((1227 collection_id,1228 nft_id,1229 scope,1230 resource_id_key.clone()1231 ))1232 .is_some(),1233 <Error<T>>::ResourceDoesntExist1234 );12351236 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1237 let topmost_owner =1238 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12391240 let sender = T::CrossAccountId::from_sub(sender);1241 if topmost_owner == sender {1242 <PalletNft<T>>::remove_token_sys_property(1243 collection_id,1244 nft_id,1245 PropertyScope::Rmrk,1246 Self::rmrk_property_key(ResourceId(resource_id))?,1247 );1248 } else {1249 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1250 res.pending_removal = true;12511252 Ok(())1253 })?;1254 }12551256 Ok(())1257 }12581259 fn try_mutate_resource_info(1260 collection_id: CollectionId,1261 nft_id: TokenId,1262 resource_id: RmrkResourceId,1263 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1264 ) -> DispatchResult {1265 <PalletNft<T>>::try_mutate_token_sys_property(1266 collection_id,1267 nft_id,1268 PropertyScope::Rmrk,1269 Self::rmrk_property_key(ResourceId(resource_id))?,1270 |value| match value {1271 Some(value) => {1272 let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;12731274 f(&mut resource_info)?;12751276 *value = Self::encode_property(&resource_info)?;12771278 Ok(())1279 }1280 None => Err(<Error<T>>::ResourceDoesntExist.into()),1281 },1282 )1283 }12841285 fn change_collection_owner(1286 collection_id: CollectionId,1287 collection_type: misc::CollectionType,1288 sender: T::AccountId,1289 new_owner: T::AccountId,1290 ) -> DispatchResult {1291 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1292 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12931294 let mut collection = collection.into_inner();12951296 collection.owner = new_owner;1297 collection.save()1298 }12991300 fn check_collection_owner(1301 collection: &NonfungibleHandle<T>,1302 account: &T::CrossAccountId,1303 ) -> DispatchResult {1304 collection1305 .check_is_owner(account)1306 .map_err(Self::map_unique_err_to_proxy)1307 }13081309 pub fn last_collection_idx() -> RmrkCollectionId {1310 <CollectionIndex<T>>::get()1311 }13121313 pub fn unique_collection_id(1314 rmrk_collection_id: RmrkCollectionId,1315 ) -> Result<CollectionId, DispatchError> {1316 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1317 .map_err(|_| <Error<T>>::CollectionUnknown.into())1318 }13191320 pub fn rmrk_collection_id(1321 unique_collection_id: CollectionId,1322 ) -> Result<RmrkCollectionId, DispatchError> {1323 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1324 }13251326 pub fn get_nft_collection(1327 collection_id: CollectionId,1328 ) -> Result<NonfungibleHandle<T>, DispatchError> {1329 let collection = <CollectionHandle<T>>::try_get(collection_id)1330 .map_err(|_| <Error<T>>::CollectionUnknown)?;13311332 match collection.mode {1333 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1334 _ => Err(<Error<T>>::CollectionUnknown.into()),1335 }1336 }13371338 pub fn collection_exists(collection_id: CollectionId) -> bool {1339 <CollectionHandle<T>>::try_get(collection_id).is_ok()1340 }13411342 pub fn get_collection_property(1343 collection_id: CollectionId,1344 key: RmrkProperty,1345 ) -> Result<PropertyValue, DispatchError> {1346 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1347 .get(&Self::rmrk_property_key(key)?)1348 .ok_or(<Error<T>>::CollectionUnknown)?1349 .clone();13501351 Ok(collection_property)1352 }13531354 pub fn get_collection_property_decoded<V: Decode>(1355 collection_id: CollectionId,1356 key: RmrkProperty,1357 ) -> Result<V, DispatchError> {1358 Self::decode_property(&Self::get_collection_property(collection_id, key)?)1359 }13601361 pub fn get_collection_type(1362 collection_id: CollectionId,1363 ) -> Result<misc::CollectionType, DispatchError> {1364 Self::get_collection_property_decoded(collection_id, CollectionType)1365 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1366 }13671368 pub fn ensure_collection_type(1369 collection_id: CollectionId,1370 collection_type: misc::CollectionType,1371 ) -> DispatchResult {1372 let actual_type = Self::get_collection_type(collection_id)?;1373 ensure!(1374 actual_type == collection_type,1375 <CommonError<T>>::NoPermission1376 );13771378 Ok(())1379 }13801381 pub fn get_typed_nft_collection(1382 collection_id: CollectionId,1383 collection_type: misc::CollectionType,1384 ) -> Result<NonfungibleHandle<T>, DispatchError> {1385 Self::ensure_collection_type(collection_id, collection_type)?;13861387 Self::get_nft_collection(collection_id)1388 }13891390 pub fn get_typed_nft_collection_mapped(1391 rmrk_collection_id: RmrkCollectionId,1392 collection_type: misc::CollectionType,1393 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1394 let unique_collection_id = match collection_type {1395 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1396 _ => rmrk_collection_id.into(),1397 };13981399 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;14001401 Ok((collection, unique_collection_id))1402 }14031404 pub fn get_nft_property(1405 collection_id: CollectionId,1406 nft_id: TokenId,1407 key: RmrkProperty,1408 ) -> Result<PropertyValue, DispatchError> {1409 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1410 .get(&Self::rmrk_property_key(key)?)1411 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1412 .clone();14131414 Ok(nft_property)1415 }14161417 pub fn get_nft_property_decoded<V: Decode>(1418 collection_id: CollectionId,1419 nft_id: TokenId,1420 key: RmrkProperty,1421 ) -> Result<V, DispatchError> {1422 Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1423 }14241425 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1426 <TokenData<T>>::contains_key((collection_id, nft_id))1427 }14281429 pub fn get_nft_type(1430 collection_id: CollectionId,1431 token_id: TokenId,1432 ) -> Result<NftType, DispatchError> {1433 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1434 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1435 }14361437 pub fn ensure_nft_type(1438 collection_id: CollectionId,1439 token_id: TokenId,1440 nft_type: NftType,1441 ) -> DispatchResult {1442 let actual_type = Self::get_nft_type(collection_id, token_id)?;1443 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14441445 Ok(())1446 }14471448 pub fn ensure_nft_owner(1449 collection_id: CollectionId,1450 token_id: TokenId,1451 possible_owner: &T::CrossAccountId,1452 nesting_budget: &dyn budget::Budget,1453 ) -> DispatchResult {1454 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1455 possible_owner.clone(),1456 collection_id,1457 token_id,1458 None,1459 nesting_budget,1460 )1461 .map_err(Self::map_unique_err_to_proxy)?;14621463 ensure!(is_owned, <Error<T>>::NoPermission);14641465 Ok(())1466 }14671468 pub fn filter_user_properties<Key, Value, R, Mapper>(1469 collection_id: CollectionId,1470 token_id: Option<TokenId>,1471 filter_keys: Option<Vec<RmrkPropertyKey>>,1472 mapper: Mapper,1473 ) -> Result<Vec<R>, DispatchError>1474 where1475 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1476 Value: Decode + Default,1477 Mapper: Fn(Key, Value) -> R,1478 {1479 filter_keys1480 .map(|keys| {1481 let properties = keys1482 .into_iter()1483 .filter_map(|key| {1484 let key: Key = key.try_into().ok()?;14851486 let value = match token_id {1487 Some(token_id) => Self::get_nft_property_decoded(1488 collection_id,1489 token_id,1490 UserProperty(key.as_ref()),1491 ),1492 None => Self::get_collection_property_decoded(1493 collection_id,1494 UserProperty(key.as_ref()),1495 ),1496 }1497 .ok()?;14981499 Some(mapper(key, value))1500 })1501 .collect();15021503 Ok(properties)1504 })1505 .unwrap_or_else(|| {1506 let properties =1507 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15081509 Ok(properties)1510 })1511 }15121513 pub fn iterate_user_properties<Key, Value, R, Mapper>(1514 collection_id: CollectionId,1515 token_id: Option<TokenId>,1516 mapper: Mapper,1517 ) -> Result<impl Iterator<Item = R>, DispatchError>1518 where1519 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1520 Value: Decode + Default,1521 Mapper: Fn(Key, Value) -> R,1522 {1523 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15241525 let properties = match token_id {1526 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1527 None => <PalletCommon<T>>::collection_properties(collection_id),1528 };15291530 let properties = properties.into_iter().filter_map(move |(key, value)| {1531 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15321533 let key: Key = key.to_vec().try_into().ok()?;1534 let value: Value = value.decode().ok()?;15351536 Some(mapper(key, value))1537 });15381539 Ok(properties)1540 }15411542 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1543 map_unique_err_to_proxy! {1544 match err {1545 CommonError::NoPermission => NoPermission,1546 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1547 CommonError::PublicMintingNotAllowed => NoPermission,1548 CommonError::TokenNotFound => NoAvailableNftId,1549 CommonError::ApprovedValueTooLow => NoPermission,1550 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1551 StructureError::TokenNotFound => NoAvailableNftId,1552 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1553 }1554 }1555 }1556}