difftreelog
fix(rmrk) remove max_burns from reject_nft
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 UnableToDecodeRmrkData,158159 /* RMRK compatible events */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 /// Create a collection177 #[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 /// destroy collection242 #[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 /// Change the issuer of a collection269 ///270 /// Parameters:271 /// - `origin`: sender of the transaction272 /// - `collection_id`: collection id of the nft to change issuer of273 /// - `new_issuer`: Collection's new issuer274 #[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 /// lock collection305 #[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 /// Mints an NFT in the specified collection337 /// Sets metadata and the royalty attribute338 ///339 /// Parameters:340 /// - `collection_id`: The class of the asset to be minted.341 /// - `nft_id`: The nft value of the asset to be minted.342 /// - `recipient`: Receiver of the royalty343 /// - `royalty`: Permillage reward from each trade for the Recipient344 /// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash345 /// - `transferable`: Ability to transfer this NFT346 #[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 ) -> DispatchResult {357 let sender = ensure_signed(origin)?;358 let sender = T::CrossAccountId::from_sub(sender);359 let cross_owner = T::CrossAccountId::from_sub(owner.clone());360361 let collection = Self::get_typed_nft_collection(362 Self::unique_collection_id(collection_id)?,363 misc::CollectionType::Regular,364 )?;365 collection.check_is_external()?;366367 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {368 recipient: recipient.unwrap_or_else(|| owner.clone()),369 amount,370 });371372 let nft_id = Self::create_nft(373 &sender,374 &cross_owner,375 &collection,376 [377 Self::rmrk_property(TokenType, &NftType::Regular)?,378 Self::rmrk_property(Transferable, &transferable)?,379 Self::rmrk_property(PendingNftAccept, &false)?,380 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,381 Self::rmrk_property(Metadata, &metadata)?,382 Self::rmrk_property(Equipped, &false)?,383 Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,384 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,385 ]386 .into_iter(),387 )388 .map_err(|err| match err {389 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),390 err => Self::map_unique_err_to_proxy(err),391 })?;392393 Self::deposit_event(Event::NftMinted {394 owner,395 collection_id,396 nft_id: nft_id.0,397 });398399 Ok(())400 }401402 /// burn nft403 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]404 #[transactional]405 pub fn burn_nft(406 origin: OriginFor<T>,407 collection_id: RmrkCollectionId,408 nft_id: RmrkNftId,409 max_burns: u32,410 ) -> DispatchResult {411 let sender = ensure_signed(origin)?;412 let cross_sender = T::CrossAccountId::from_sub(sender.clone());413414 let collection = Self::get_typed_nft_collection(415 Self::unique_collection_id(collection_id)?,416 misc::CollectionType::Regular,417 )?;418 collection.check_is_external()?;419420 Self::destroy_nft(421 cross_sender,422 Self::unique_collection_id(collection_id)?,423 nft_id.into(),424 max_burns,425 <Error<T>>::NoPermission,426 )427 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;428429 Self::deposit_event(Event::NFTBurned {430 owner: sender,431 nft_id,432 });433434 Ok(())435 }436437 /// Transfers a NFT from an Account or NFT A to another Account or NFT B438 ///439 /// Parameters:440 /// - `origin`: sender of the transaction441 /// - `rmrk_collection_id`: collection id of the nft to be transferred442 /// - `rmrk_nft_id`: nft id of the nft to be transferred443 /// - `new_owner`: new owner of the nft which can be either an account or a NFT444 #[transactional]445 #[pallet::weight(<SelfWeightOf<T>>::send())]446 pub fn send(447 origin: OriginFor<T>,448 rmrk_collection_id: RmrkCollectionId,449 rmrk_nft_id: RmrkNftId,450 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,451 ) -> DispatchResult {452 let sender = ensure_signed(origin.clone())?;453 let cross_sender = T::CrossAccountId::from_sub(sender.clone());454455 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;456 let nft_id = rmrk_nft_id.into();457458 let collection =459 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;460 collection.check_is_external()?;461462 let token_data =463 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;464465 let from = token_data.owner;466467 ensure!(468 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,469 <Error<T>>::NonTransferable470 );471472 ensure!(473 !Self::get_nft_property_decoded(474 collection_id,475 nft_id,476 RmrkProperty::PendingNftAccept477 )?,478 <Error<T>>::NoPermission479 );480481 let target_owner;482 let approval_required;483484 match new_owner {485 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {486 target_owner = T::CrossAccountId::from_sub(account_id.clone());487 approval_required = false;488 }489 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(490 target_collection_id,491 target_nft_id,492 ) => {493 let target_collection_id = Self::unique_collection_id(target_collection_id)?;494495 let target_nft_budget = budget::Value::new(NESTING_BUDGET);496497 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(498 target_collection_id,499 target_nft_id.into(),500 Some((collection_id, nft_id)),501 &target_nft_budget,502 )503 .map_err(Self::map_unique_err_to_proxy)?;504505 approval_required = cross_sender != target_nft_owner;506507 if approval_required {508 target_owner = target_nft_owner;509510 <PalletNft<T>>::set_scoped_token_property(511 collection.id,512 nft_id,513 PropertyScope::Rmrk,514 Self::rmrk_property(PendingNftAccept, &approval_required)?,515 )?;516 } else {517 target_owner = T::CrossTokenAddressMapping::token_to_address(518 target_collection_id,519 target_nft_id.into(),520 );521 }522 }523 }524525 let src_nft_budget = budget::Value::new(NESTING_BUDGET);526527 <PalletNft<T>>::transfer_from(528 &collection,529 &cross_sender,530 &from,531 &target_owner,532 nft_id,533 &src_nft_budget,534 )535 .map_err(Self::map_unique_err_to_proxy)?;536537 Self::deposit_event(Event::NFTSent {538 sender,539 recipient: new_owner,540 collection_id: rmrk_collection_id,541 nft_id: rmrk_nft_id,542 approval_required,543 });544545 Ok(())546 }547548 /// Accepts an NFT sent from another account to self or owned NFT549 ///550 /// Parameters:551 /// - `origin`: sender of the transaction552 /// - `rmrk_collection_id`: collection id of the nft to be accepted553 /// - `rmrk_nft_id`: nft id of the nft to be accepted554 /// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was555 /// sent to556 #[transactional]557 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]558 pub fn accept_nft(559 origin: OriginFor<T>,560 rmrk_collection_id: RmrkCollectionId,561 rmrk_nft_id: RmrkNftId,562 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,563 ) -> DispatchResult {564 let sender = ensure_signed(origin.clone())?;565 let cross_sender = T::CrossAccountId::from_sub(sender.clone());566567 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;568 let nft_id = rmrk_nft_id.into();569570 let collection =571 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;572 collection.check_is_external()?;573574 let new_cross_owner = match new_owner {575 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {576 T::CrossAccountId::from_sub(account_id.clone())577 }578 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(579 target_collection_id,580 target_nft_id,581 ) => {582 let target_collection_id = Self::unique_collection_id(target_collection_id)?;583584 T::CrossTokenAddressMapping::token_to_address(585 target_collection_id,586 TokenId(target_nft_id),587 )588 }589 };590591 let budget = budget::Value::new(NESTING_BUDGET);592593 <PalletNft<T>>::transfer(594 &collection,595 &cross_sender,596 &new_cross_owner,597 nft_id,598 &budget,599 )600 .map_err(|err| {601 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {602 <Error<T>>::CannotAcceptNonOwnedNft.into()603 } else {604 Self::map_unique_err_to_proxy(err)605 }606 })?;607608 <PalletNft<T>>::set_scoped_token_property(609 collection.id,610 nft_id,611 PropertyScope::Rmrk,612 Self::rmrk_property(PendingNftAccept, &false)?,613 )?;614615 Self::deposit_event(Event::NFTAccepted {616 sender,617 recipient: new_owner,618 collection_id: rmrk_collection_id,619 nft_id: rmrk_nft_id,620 });621622 Ok(())623 }624625 /// Rejects an NFT sent from another account to self or owned NFT626 ///627 /// Parameters:628 /// - `origin`: sender of the transaction629 /// - `rmrk_collection_id`: collection id of the nft to be accepted630 /// - `rmrk_nft_id`: nft id of the nft to be accepted631 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]632 #[transactional]633 pub fn reject_nft(634 origin: OriginFor<T>,635 rmrk_collection_id: RmrkCollectionId,636 rmrk_nft_id: RmrkNftId,637 max_burns: u32,638 ) -> DispatchResult {639 let sender = ensure_signed(origin)?;640 let cross_sender = T::CrossAccountId::from_sub(sender.clone());641642 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;643 let nft_id = rmrk_nft_id.into();644645 let collection =646 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;647 collection.check_is_external()?;648649 Self::destroy_nft(650 cross_sender,651 collection_id,652 nft_id,653 max_burns,654 <Error<T>>::CannotRejectNonOwnedNft,655 )656 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;657658 Self::deposit_event(Event::NFTRejected {659 sender,660 collection_id: rmrk_collection_id,661 nft_id: rmrk_nft_id,662 });663664 Ok(())665 }666667 /// accept the addition of a new resource to an existing NFT668 #[transactional]669 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]670 pub fn accept_resource(671 origin: OriginFor<T>,672 rmrk_collection_id: RmrkCollectionId,673 rmrk_nft_id: RmrkNftId,674 rmrk_resource_id: RmrkResourceId,675 ) -> DispatchResult {676 let sender = ensure_signed(origin)?;677 let cross_sender = T::CrossAccountId::from_sub(sender);678679 let collection_id = Self::unique_collection_id(rmrk_collection_id)680 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;681 let collection =682 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;683 collection.check_is_external()?;684685 let nft_id = rmrk_nft_id.into();686 let resource_id = rmrk_resource_id.into();687688 let budget = budget::Value::new(NESTING_BUDGET);689690 let nft_owner =691 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)692 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;693694 let resource_collection_id: Option<CollectionId> =695 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)696 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;697698 let resource_collection_id =699 resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;700701 let is_pending: bool = Self::get_nft_property_decoded(702 resource_collection_id,703 resource_id,704 PendingResourceAccept,705 )706 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;707708 ensure!(is_pending, <Error<T>>::ResourceNotPending);709710 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);711712 <PalletNft<T>>::set_scoped_token_property(713 resource_collection_id,714 rmrk_resource_id.into(),715 PropertyScope::Rmrk,716 Self::rmrk_property(PendingResourceAccept, &false)?,717 )?;718719 Self::deposit_event(Event::<T>::ResourceAccepted {720 nft_id: rmrk_nft_id,721 resource_id: rmrk_resource_id,722 });723724 Ok(())725 }726727 /// accept the removal of a resource of an existing NFT728 #[transactional]729 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]730 pub fn accept_resource_removal(731 origin: OriginFor<T>,732 rmrk_collection_id: RmrkCollectionId,733 rmrk_nft_id: RmrkNftId,734 rmrk_resource_id: RmrkResourceId,735 ) -> DispatchResult {736 let sender = ensure_signed(origin)?;737 let cross_sender = T::CrossAccountId::from_sub(sender);738739 let collection_id = Self::unique_collection_id(rmrk_collection_id)740 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;741 let collection =742 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;743 collection.check_is_external()?;744745 let nft_id = rmrk_nft_id.into();746 let resource_id = rmrk_resource_id.into();747748 let budget = budget::Value::new(NESTING_BUDGET);749750 let nft_owner =751 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)752 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;753754 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);755756 let resource_collection_id: Option<CollectionId> =757 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)758 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;759760 let resource_collection_id =761 resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;762763 let is_pending: bool = Self::get_nft_property_decoded(764 resource_collection_id,765 resource_id,766 PendingResourceRemoval,767 )768 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;769770 ensure!(is_pending, <Error<T>>::ResourceNotPending);771772 let resource_collection = Self::get_typed_nft_collection(773 resource_collection_id,774 misc::CollectionType::Resource,775 )?;776777 let resource_data = <TokenData<T>>::get((resource_collection_id, resource_id))778 .ok_or(<Error<T>>::ResourceDoesntExist)?;779780 let resource_owner = resource_data.owner;781782 <PalletNft<T>>::burn(783 &resource_collection,784 &resource_owner,785 rmrk_resource_id.into(),786 )787 .map_err(Self::map_unique_err_to_proxy)?;788789 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {790 nft_id: rmrk_nft_id,791 resource_id: rmrk_resource_id,792 });793794 Ok(())795 }796797 /// set a custom value on an NFT798 #[transactional]799 #[pallet::weight(<SelfWeightOf<T>>::set_property())]800 pub fn set_property(801 origin: OriginFor<T>,802 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,803 maybe_nft_id: Option<RmrkNftId>,804 key: RmrkKeyString,805 value: RmrkValueString,806 ) -> DispatchResult {807 let sender = ensure_signed(origin)?;808 let sender = T::CrossAccountId::from_sub(sender);809810 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;811 let collection =812 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;813 collection.check_is_external()?;814815 let budget = budget::Value::new(NESTING_BUDGET);816817 match maybe_nft_id {818 Some(nft_id) => {819 let token_id: TokenId = nft_id.into();820821 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;822 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;823824 <PalletNft<T>>::set_scoped_token_property(825 collection_id,826 token_id,827 PropertyScope::Rmrk,828 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,829 )?;830 }831 None => {832 let collection = Self::get_typed_nft_collection(833 collection_id,834 misc::CollectionType::Regular,835 )?;836837 Self::check_collection_owner(&collection, &sender)?;838839 <PalletCommon<T>>::set_scoped_collection_property(840 collection_id,841 PropertyScope::Rmrk,842 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,843 )?;844 }845 }846847 Self::deposit_event(Event::PropertySet {848 collection_id: rmrk_collection_id,849 maybe_nft_id,850 key,851 value,852 });853854 Ok(())855 }856857 /// set a different order of resource priority858 #[transactional]859 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]860 pub fn set_priority(861 origin: OriginFor<T>,862 rmrk_collection_id: RmrkCollectionId,863 rmrk_nft_id: RmrkNftId,864 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,865 ) -> DispatchResult {866 let sender = ensure_signed(origin)?;867 let sender = T::CrossAccountId::from_sub(sender);868869 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;870 let nft_id = rmrk_nft_id.into();871872 let collection =873 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;874 collection.check_is_external()?;875876 let budget = budget::Value::new(NESTING_BUDGET);877878 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;879 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;880881 <PalletNft<T>>::set_scoped_token_property(882 collection_id,883 nft_id,884 PropertyScope::Rmrk,885 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,886 )?;887888 Self::deposit_event(Event::<T>::PrioritySet {889 collection_id: rmrk_collection_id,890 nft_id: rmrk_nft_id,891 });892893 Ok(())894 }895896 /// Create basic resource897 #[transactional]898 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]899 pub fn add_basic_resource(900 origin: OriginFor<T>,901 rmrk_collection_id: RmrkCollectionId,902 nft_id: RmrkNftId,903 resource: RmrkBasicResource,904 ) -> DispatchResult {905 let sender = ensure_signed(origin.clone())?;906907 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;908 let collection =909 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;910 collection.check_is_external()?;911912 let resource_id = Self::resource_add(913 sender,914 collection_id,915 nft_id.into(),916 [917 Self::rmrk_property(TokenType, &NftType::Resource)?,918 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,919 Self::rmrk_property(Src, &resource.src)?,920 Self::rmrk_property(Metadata, &resource.metadata)?,921 Self::rmrk_property(License, &resource.license)?,922 Self::rmrk_property(Thumb, &resource.thumb)?,923 ]924 .into_iter(),925 )?;926927 Self::deposit_event(Event::ResourceAdded {928 nft_id,929 resource_id,930 });931 Ok(())932 }933934 /// Create composable resource935 #[transactional]936 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]937 pub fn add_composable_resource(938 origin: OriginFor<T>,939 rmrk_collection_id: RmrkCollectionId,940 nft_id: RmrkNftId,941 resource: RmrkComposableResource,942 ) -> DispatchResult {943 let sender = ensure_signed(origin.clone())?;944945 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;946 let collection =947 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;948 collection.check_is_external()?;949950 let resource_id = Self::resource_add(951 sender,952 collection_id,953 nft_id.into(),954 [955 Self::rmrk_property(TokenType, &NftType::Resource)?,956 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,957 Self::rmrk_property(Parts, &resource.parts)?,958 Self::rmrk_property(Base, &resource.base)?,959 Self::rmrk_property(Src, &resource.src)?,960 Self::rmrk_property(Metadata, &resource.metadata)?,961 Self::rmrk_property(License, &resource.license)?,962 Self::rmrk_property(Thumb, &resource.thumb)?,963 ]964 .into_iter(),965 )?;966967 Self::deposit_event(Event::ResourceAdded {968 nft_id,969 resource_id,970 });971 Ok(())972 }973974 /// Create slot resource975 #[transactional]976 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]977 pub fn add_slot_resource(978 origin: OriginFor<T>,979 rmrk_collection_id: RmrkCollectionId,980 nft_id: RmrkNftId,981 resource: RmrkSlotResource,982 ) -> DispatchResult {983 let sender = ensure_signed(origin.clone())?;984985 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;986 let collection =987 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;988 collection.check_is_external()?;989990 let resource_id = Self::resource_add(991 sender,992 collection_id,993 nft_id.into(),994 [995 Self::rmrk_property(TokenType, &NftType::Resource)?,996 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,997 Self::rmrk_property(Base, &resource.base)?,998 Self::rmrk_property(Src, &resource.src)?,999 Self::rmrk_property(Metadata, &resource.metadata)?,1000 Self::rmrk_property(Slot, &resource.slot)?,1001 Self::rmrk_property(License, &resource.license)?,1002 Self::rmrk_property(Thumb, &resource.thumb)?,1003 ]1004 .into_iter(),1005 )?;10061007 Self::deposit_event(Event::ResourceAdded {1008 nft_id,1009 resource_id,1010 });1011 Ok(())1012 }10131014 /// remove resource1015 #[transactional]1016 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1017 pub fn remove_resource(1018 origin: OriginFor<T>,1019 rmrk_collection_id: RmrkCollectionId,1020 nft_id: RmrkNftId,1021 resource_id: RmrkResourceId,1022 ) -> DispatchResult {1023 let sender = ensure_signed(origin.clone())?;10241025 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1026 let collection =1027 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1028 collection.check_is_external()?;10291030 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10311032 Self::deposit_event(Event::ResourceRemoval {1033 nft_id,1034 resource_id,1035 });1036 Ok(())1037 }1038 }1039}10401041impl<T: Config> Pallet<T> {1042 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1043 let key = rmrk_key.to_key::<T>()?;10441045 let scoped_key = PropertyScope::Rmrk1046 .apply(key)1047 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10481049 Ok(scoped_key)1050 }10511052 // todo think about renaming these1053 pub fn rmrk_property<E: Encode>(1054 rmrk_key: RmrkProperty,1055 value: &E,1056 ) -> Result<Property, DispatchError> {1057 let key = rmrk_key.to_key::<T>()?;10581059 let value = value1060 .encode()1061 .try_into()1062 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10631064 let property = Property { key, value };10651066 Ok(property)1067 }10681069 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1070 vec.decode()1071 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1072 }10731074 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1075 where1076 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1077 {1078 vec.rebind()1079 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1080 }10811082 fn init_collection(1083 sender: T::CrossAccountId,1084 data: CreateCollectionData<T::AccountId>,1085 properties: impl Iterator<Item = Property>,1086 ) -> Result<CollectionId, DispatchError> {1087 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10881089 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1090 return Err(<Error<T>>::NoAvailableCollectionId.into());1091 }10921093 <PalletCommon<T>>::set_scoped_collection_properties(1094 collection_id?,1095 PropertyScope::Rmrk,1096 properties,1097 )?;10981099 collection_id1100 }11011102 pub fn create_nft(1103 sender: &T::CrossAccountId,1104 owner: &T::CrossAccountId,1105 collection: &NonfungibleHandle<T>,1106 properties: impl Iterator<Item = Property>,1107 ) -> Result<TokenId, DispatchError> {1108 let data = CreateNftExData {1109 properties: BoundedVec::default(),1110 owner: owner.clone(),1111 };11121113 let budget = budget::Value::new(NESTING_BUDGET);11141115 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;11161117 let nft_id = <PalletNft<T>>::current_token_id(collection.id);11181119 <PalletNft<T>>::set_scoped_token_properties(1120 collection.id,1121 nft_id,1122 PropertyScope::Rmrk,1123 properties,1124 )?;11251126 Ok(nft_id)1127 }11281129 fn destroy_nft(1130 sender: T::CrossAccountId,1131 collection_id: CollectionId,1132 token_id: TokenId,1133 max_burns: u32,1134 error_if_not_owned: Error<T>,1135 ) -> DispatchResultWithPostInfo {1136 let collection =1137 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11381139 let token_data =1140 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11411142 let from = token_data.owner;11431144 let owner_check_budget = budget::Value::new(NESTING_BUDGET);11451146 ensure!(1147 <PalletStructure<T>>::check_indirectly_owned(1148 sender.clone(),1149 collection_id,1150 token_id,1151 None,1152 &owner_check_budget1153 )?,1154 error_if_not_owned,1155 );11561157 let burns_budget = budget::Value::new(max_burns);1158 let breadth_budget = budget::Value::new(max_burns);11591160 <PalletNft<T>>::burn_recursively(1161 &collection,1162 &from,1163 token_id,1164 &burns_budget,1165 &breadth_budget,1166 )1167 }11681169 fn resource_add(1170 sender: T::AccountId,1171 collection_id: CollectionId,1172 token_id: TokenId,1173 resource_properties: impl Iterator<Item = Property>,1174 ) -> Result<RmrkResourceId, DispatchError> {1175 let collection =1176 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1177 ensure!(collection.owner == sender, Error::<T>::NoPermission);11781179 let sender = T::CrossAccountId::from_sub(sender);1180 let budget = budget::Value::new(NESTING_BUDGET);11811182 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)1183 .map_err(Self::map_unique_err_to_proxy)?;11841185 let pending = sender != nft_owner;11861187 let resource_collection_id: Option<CollectionId> =1188 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;11891190 let resource_collection_id = match resource_collection_id {1191 Some(id) => id,1192 None => {1193 let resource_collection_id = Self::init_collection(1194 sender.clone(),1195 CreateCollectionData {1196 ..Default::default()1197 },1198 [Self::rmrk_property(1199 CollectionType,1200 &misc::CollectionType::Resource,1201 )?]1202 .into_iter(),1203 )?;12041205 <PalletNft<T>>::set_scoped_token_property(1206 collection_id,1207 token_id,1208 PropertyScope::Rmrk,1209 Self::rmrk_property(ResourceCollection, &Some(resource_collection_id))?,1210 )?;12111212 resource_collection_id1213 }1214 };12151216 let resource_collection =1217 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;12181219 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them12201221 let resource_id = Self::create_nft(1222 &sender,1223 &nft_owner,1224 &resource_collection,1225 resource_properties.chain(1226 [1227 Self::rmrk_property(PendingResourceAccept, &pending)?,1228 Self::rmrk_property(PendingResourceRemoval, &false)?,1229 ]1230 .into_iter(),1231 ),1232 )1233 .map_err(|err| match err {1234 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1235 err => Self::map_unique_err_to_proxy(err),1236 })?;12371238 Ok(resource_id.0)1239 }12401241 fn resource_remove(1242 sender: T::AccountId,1243 collection_id: CollectionId,1244 nft_id: TokenId,1245 resource_id: TokenId,1246 ) -> DispatchResult {1247 let collection =1248 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1249 ensure!(collection.owner == sender, Error::<T>::NoPermission);12501251 let resource_collection_id: Option<CollectionId> =1252 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;12531254 let resource_collection_id =1255 resource_collection_id.ok_or(Error::<T>::ResourceDoesntExist)?;12561257 let resource_collection =1258 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1259 ensure!(1260 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1261 Error::<T>::ResourceDoesntExist1262 );12631264 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1265 let topmost_owner =1266 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12671268 let sender = T::CrossAccountId::from_sub(sender);1269 if topmost_owner == sender {1270 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1271 .map_err(Self::map_unique_err_to_proxy)?;1272 } else {1273 <PalletNft<T>>::set_scoped_token_property(1274 resource_collection_id,1275 resource_id,1276 PropertyScope::Rmrk,1277 Self::rmrk_property(PendingResourceRemoval, &true)?,1278 )?;1279 }12801281 Ok(())1282 }12831284 fn change_collection_owner(1285 collection_id: CollectionId,1286 collection_type: misc::CollectionType,1287 sender: T::AccountId,1288 new_owner: T::AccountId,1289 ) -> DispatchResult {1290 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1291 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;12921293 let mut collection = collection.into_inner();12941295 collection.owner = new_owner;1296 collection.save()1297 }12981299 fn check_collection_owner(1300 collection: &NonfungibleHandle<T>,1301 account: &T::CrossAccountId,1302 ) -> DispatchResult {1303 collection1304 .check_is_owner(account)1305 .map_err(Self::map_unique_err_to_proxy)1306 }13071308 pub fn last_collection_idx() -> RmrkCollectionId {1309 <CollectionIndex<T>>::get()1310 }13111312 pub fn unique_collection_id(1313 rmrk_collection_id: RmrkCollectionId,1314 ) -> Result<CollectionId, DispatchError> {1315 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1316 .map_err(|_| <Error<T>>::CollectionUnknown.into())1317 }13181319 pub fn rmrk_collection_id(1320 unique_collection_id: CollectionId,1321 ) -> Result<RmrkCollectionId, DispatchError> {1322 Self::get_collection_property_decoded(1323 unique_collection_id,1324 RmrkInternalCollectionId1325 )1326 }13271328 pub fn get_nft_collection(1329 collection_id: CollectionId,1330 ) -> Result<NonfungibleHandle<T>, DispatchError> {1331 let collection = <CollectionHandle<T>>::try_get(collection_id)1332 .map_err(|_| <Error<T>>::CollectionUnknown)?;13331334 match collection.mode {1335 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1336 _ => Err(<Error<T>>::CollectionUnknown.into()),1337 }1338 }13391340 pub fn collection_exists(collection_id: CollectionId) -> bool {1341 <CollectionHandle<T>>::try_get(collection_id).is_ok()1342 }13431344 pub fn get_collection_property(1345 collection_id: CollectionId,1346 key: RmrkProperty,1347 ) -> Result<PropertyValue, DispatchError> {1348 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1349 .get(&Self::rmrk_property_key(key)?)1350 .ok_or(<Error<T>>::CollectionUnknown)?1351 .clone();13521353 Ok(collection_property)1354 }13551356 pub fn get_collection_property_decoded<V: Decode>(1357 collection_id: CollectionId,1358 key: RmrkProperty,1359 ) -> Result<V, DispatchError> {1360 Self::decode_property(Self::get_collection_property(collection_id, key)?)1361 }13621363 pub fn get_collection_type(1364 collection_id: CollectionId,1365 ) -> Result<misc::CollectionType, DispatchError> {1366 Self::get_collection_property_decoded(collection_id, CollectionType)1367 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1368 }13691370 pub fn ensure_collection_type(1371 collection_id: CollectionId,1372 collection_type: misc::CollectionType,1373 ) -> DispatchResult {1374 let actual_type = Self::get_collection_type(collection_id)?;1375 ensure!(1376 actual_type == collection_type,1377 <CommonError<T>>::NoPermission1378 );13791380 Ok(())1381 }13821383 pub fn get_typed_nft_collection(1384 collection_id: CollectionId,1385 collection_type: misc::CollectionType,1386 ) -> Result<NonfungibleHandle<T>, DispatchError> {1387 Self::ensure_collection_type(collection_id, collection_type)?;13881389 Self::get_nft_collection(collection_id)1390 }13911392 pub fn get_typed_nft_collection_mapped(1393 rmrk_collection_id: RmrkCollectionId,1394 collection_type: misc::CollectionType,1395 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1396 let unique_collection_id = match collection_type {1397 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1398 _ => rmrk_collection_id.into(),1399 };14001401 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;14021403 Ok((collection, unique_collection_id))1404 }14051406 pub fn get_nft_property(1407 collection_id: CollectionId,1408 nft_id: TokenId,1409 key: RmrkProperty,1410 ) -> Result<PropertyValue, DispatchError> {1411 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1412 .get(&Self::rmrk_property_key(key)?)1413 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1414 .clone();14151416 Ok(nft_property)1417 }14181419 pub fn get_nft_property_decoded<V: Decode>(1420 collection_id: CollectionId,1421 nft_id: TokenId,1422 key: RmrkProperty,1423 ) -> Result<V, DispatchError> {1424 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1425 }14261427 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1428 <TokenData<T>>::contains_key((collection_id, nft_id))1429 }14301431 pub fn get_nft_type(1432 collection_id: CollectionId,1433 token_id: TokenId,1434 ) -> Result<NftType, DispatchError> {1435 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1436 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1437 }14381439 pub fn ensure_nft_type(1440 collection_id: CollectionId,1441 token_id: TokenId,1442 nft_type: NftType,1443 ) -> DispatchResult {1444 let actual_type = Self::get_nft_type(collection_id, token_id)?;1445 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14461447 Ok(())1448 }14491450 pub fn ensure_nft_owner(1451 collection_id: CollectionId,1452 token_id: TokenId,1453 possible_owner: &T::CrossAccountId,1454 nesting_budget: &dyn budget::Budget,1455 ) -> DispatchResult {1456 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1457 possible_owner.clone(),1458 collection_id,1459 token_id,1460 None,1461 nesting_budget,1462 )1463 .map_err(Self::map_unique_err_to_proxy)?;14641465 ensure!(is_owned, <Error<T>>::NoPermission);14661467 Ok(())1468 }14691470 pub fn filter_user_properties<Key, Value, R, Mapper>(1471 collection_id: CollectionId,1472 token_id: Option<TokenId>,1473 filter_keys: Option<Vec<RmrkPropertyKey>>,1474 mapper: Mapper,1475 ) -> Result<Vec<R>, DispatchError>1476 where1477 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1478 Value: Decode + Default,1479 Mapper: Fn(Key, Value) -> R,1480 {1481 filter_keys1482 .map(|keys| {1483 let properties = keys1484 .into_iter()1485 .filter_map(|key| {1486 let key: Key = key.try_into().ok()?;14871488 let value = match token_id {1489 Some(token_id) => Self::get_nft_property_decoded(1490 collection_id,1491 token_id,1492 UserProperty(key.as_ref()),1493 ),1494 None => Self::get_collection_property_decoded(1495 collection_id,1496 UserProperty(key.as_ref()),1497 ),1498 }1499 .ok()?;15001501 Some(mapper(key, value))1502 })1503 .collect();15041505 Ok(properties)1506 })1507 .unwrap_or_else(|| {1508 let properties =1509 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15101511 Ok(properties)1512 })1513 }15141515 pub fn iterate_user_properties<Key, Value, R, Mapper>(1516 collection_id: CollectionId,1517 token_id: Option<TokenId>,1518 mapper: Mapper,1519 ) -> Result<impl Iterator<Item = R>, DispatchError>1520 where1521 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1522 Value: Decode + Default,1523 Mapper: Fn(Key, Value) -> R,1524 {1525 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15261527 let properties = match token_id {1528 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1529 None => <PalletCommon<T>>::collection_properties(collection_id),1530 };15311532 let properties = properties.into_iter().filter_map(move |(key, value)| {1533 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15341535 let key: Key = key.to_vec().try_into().ok()?;1536 let value: Value = value.decode().ok()?;15371538 Some(mapper(key, value))1539 });15401541 Ok(properties)1542 }15431544 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1545 map_unique_err_to_proxy! {1546 match err {1547 CommonError::NoPermission => NoPermission,1548 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1549 CommonError::PublicMintingNotAllowed => NoPermission,1550 CommonError::TokenNotFound => NoAvailableNftId,1551 CommonError::ApprovedValueTooLow => NoPermission,1552 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1553 StructureError::TokenNotFound => NoAvailableNftId,1554 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1555 }1556 }1557 }1558}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 UnableToDecodeRmrkData,158159 /* RMRK compatible events */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 /// Create a collection177 #[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 /// destroy collection242 #[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 /// Change the issuer of a collection269 ///270 /// Parameters:271 /// - `origin`: sender of the transaction272 /// - `collection_id`: collection id of the nft to change issuer of273 /// - `new_issuer`: Collection's new issuer274 #[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 /// lock collection305 #[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 /// Mints an NFT in the specified collection337 /// Sets metadata and the royalty attribute338 ///339 /// Parameters:340 /// - `collection_id`: The class of the asset to be minted.341 /// - `nft_id`: The nft value of the asset to be minted.342 /// - `recipient`: Receiver of the royalty343 /// - `royalty`: Permillage reward from each trade for the Recipient344 /// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash345 /// - `transferable`: Ability to transfer this NFT346 #[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 ) -> DispatchResult {357 let sender = ensure_signed(origin)?;358 let sender = T::CrossAccountId::from_sub(sender);359 let cross_owner = T::CrossAccountId::from_sub(owner.clone());360361 let collection = Self::get_typed_nft_collection(362 Self::unique_collection_id(collection_id)?,363 misc::CollectionType::Regular,364 )?;365 collection.check_is_external()?;366367 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {368 recipient: recipient.unwrap_or_else(|| owner.clone()),369 amount,370 });371372 let nft_id = Self::create_nft(373 &sender,374 &cross_owner,375 &collection,376 [377 Self::rmrk_property(TokenType, &NftType::Regular)?,378 Self::rmrk_property(Transferable, &transferable)?,379 Self::rmrk_property(PendingNftAccept, &false)?,380 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,381 Self::rmrk_property(Metadata, &metadata)?,382 Self::rmrk_property(Equipped, &false)?,383 Self::rmrk_property(ResourceCollection, &None::<CollectionId>)?,384 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,385 ]386 .into_iter(),387 )388 .map_err(|err| match err {389 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),390 err => Self::map_unique_err_to_proxy(err),391 })?;392393 Self::deposit_event(Event::NftMinted {394 owner,395 collection_id,396 nft_id: nft_id.0,397 });398399 Ok(())400 }401402 /// burn nft403 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]404 #[transactional]405 pub fn burn_nft(406 origin: OriginFor<T>,407 collection_id: RmrkCollectionId,408 nft_id: RmrkNftId,409 max_burns: u32,410 ) -> DispatchResult {411 let sender = ensure_signed(origin)?;412 let cross_sender = T::CrossAccountId::from_sub(sender.clone());413414 let collection = Self::get_typed_nft_collection(415 Self::unique_collection_id(collection_id)?,416 misc::CollectionType::Regular,417 )?;418 collection.check_is_external()?;419420 Self::destroy_nft(421 cross_sender,422 Self::unique_collection_id(collection_id)?,423 nft_id.into(),424 max_burns,425 <Error<T>>::NoPermission,426 )427 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;428429 Self::deposit_event(Event::NFTBurned {430 owner: sender,431 nft_id,432 });433434 Ok(())435 }436437 /// Transfers a NFT from an Account or NFT A to another Account or NFT B438 ///439 /// Parameters:440 /// - `origin`: sender of the transaction441 /// - `rmrk_collection_id`: collection id of the nft to be transferred442 /// - `rmrk_nft_id`: nft id of the nft to be transferred443 /// - `new_owner`: new owner of the nft which can be either an account or a NFT444 #[transactional]445 #[pallet::weight(<SelfWeightOf<T>>::send())]446 pub fn send(447 origin: OriginFor<T>,448 rmrk_collection_id: RmrkCollectionId,449 rmrk_nft_id: RmrkNftId,450 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,451 ) -> DispatchResult {452 let sender = ensure_signed(origin.clone())?;453 let cross_sender = T::CrossAccountId::from_sub(sender.clone());454455 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;456 let nft_id = rmrk_nft_id.into();457458 let collection =459 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;460 collection.check_is_external()?;461462 let token_data =463 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;464465 let from = token_data.owner;466467 ensure!(468 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,469 <Error<T>>::NonTransferable470 );471472 ensure!(473 !Self::get_nft_property_decoded(474 collection_id,475 nft_id,476 RmrkProperty::PendingNftAccept477 )?,478 <Error<T>>::NoPermission479 );480481 let target_owner;482 let approval_required;483484 match new_owner {485 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {486 target_owner = T::CrossAccountId::from_sub(account_id.clone());487 approval_required = false;488 }489 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(490 target_collection_id,491 target_nft_id,492 ) => {493 let target_collection_id = Self::unique_collection_id(target_collection_id)?;494495 let target_nft_budget = budget::Value::new(NESTING_BUDGET);496497 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(498 target_collection_id,499 target_nft_id.into(),500 Some((collection_id, nft_id)),501 &target_nft_budget,502 )503 .map_err(Self::map_unique_err_to_proxy)?;504505 approval_required = cross_sender != target_nft_owner;506507 if approval_required {508 target_owner = target_nft_owner;509510 <PalletNft<T>>::set_scoped_token_property(511 collection.id,512 nft_id,513 PropertyScope::Rmrk,514 Self::rmrk_property(PendingNftAccept, &approval_required)?,515 )?;516 } else {517 target_owner = T::CrossTokenAddressMapping::token_to_address(518 target_collection_id,519 target_nft_id.into(),520 );521 }522 }523 }524525 let src_nft_budget = budget::Value::new(NESTING_BUDGET);526527 <PalletNft<T>>::transfer_from(528 &collection,529 &cross_sender,530 &from,531 &target_owner,532 nft_id,533 &src_nft_budget,534 )535 .map_err(Self::map_unique_err_to_proxy)?;536537 Self::deposit_event(Event::NFTSent {538 sender,539 recipient: new_owner,540 collection_id: rmrk_collection_id,541 nft_id: rmrk_nft_id,542 approval_required,543 });544545 Ok(())546 }547548 /// Accepts an NFT sent from another account to self or owned NFT549 ///550 /// Parameters:551 /// - `origin`: sender of the transaction552 /// - `rmrk_collection_id`: collection id of the nft to be accepted553 /// - `rmrk_nft_id`: nft id of the nft to be accepted554 /// - `new_owner`: either origin's account ID or origin-owned NFT, whichever the NFT was555 /// sent to556 #[transactional]557 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]558 pub fn accept_nft(559 origin: OriginFor<T>,560 rmrk_collection_id: RmrkCollectionId,561 rmrk_nft_id: RmrkNftId,562 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,563 ) -> DispatchResult {564 let sender = ensure_signed(origin.clone())?;565 let cross_sender = T::CrossAccountId::from_sub(sender.clone());566567 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;568 let nft_id = rmrk_nft_id.into();569570 let collection =571 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;572 collection.check_is_external()?;573574 let new_cross_owner = match new_owner {575 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {576 T::CrossAccountId::from_sub(account_id.clone())577 }578 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(579 target_collection_id,580 target_nft_id,581 ) => {582 let target_collection_id = Self::unique_collection_id(target_collection_id)?;583584 T::CrossTokenAddressMapping::token_to_address(585 target_collection_id,586 TokenId(target_nft_id),587 )588 }589 };590591 let budget = budget::Value::new(NESTING_BUDGET);592593 <PalletNft<T>>::transfer(594 &collection,595 &cross_sender,596 &new_cross_owner,597 nft_id,598 &budget,599 )600 .map_err(|err| {601 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {602 <Error<T>>::CannotAcceptNonOwnedNft.into()603 } else {604 Self::map_unique_err_to_proxy(err)605 }606 })?;607608 <PalletNft<T>>::set_scoped_token_property(609 collection.id,610 nft_id,611 PropertyScope::Rmrk,612 Self::rmrk_property(PendingNftAccept, &false)?,613 )?;614615 Self::deposit_event(Event::NFTAccepted {616 sender,617 recipient: new_owner,618 collection_id: rmrk_collection_id,619 nft_id: rmrk_nft_id,620 });621622 Ok(())623 }624625 /// Rejects an NFT sent from another account to self or owned NFT626 ///627 /// Parameters:628 /// - `origin`: sender of the transaction629 /// - `rmrk_collection_id`: collection id of the nft to be accepted630 /// - `rmrk_nft_id`: nft id of the nft to be accepted631 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]632 #[transactional]633 pub fn reject_nft(634 origin: OriginFor<T>,635 rmrk_collection_id: RmrkCollectionId,636 rmrk_nft_id: RmrkNftId,637 ) -> DispatchResult {638 let sender = ensure_signed(origin)?;639 let cross_sender = T::CrossAccountId::from_sub(sender.clone());640641 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;642 let nft_id = rmrk_nft_id.into();643644 let collection =645 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;646 collection.check_is_external()?;647648 Self::destroy_nft(649 cross_sender,650 collection_id,651 nft_id,652 NESTING_BUDGET,653 <Error<T>>::CannotRejectNonOwnedNft,654 )655 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;656657 Self::deposit_event(Event::NFTRejected {658 sender,659 collection_id: rmrk_collection_id,660 nft_id: rmrk_nft_id,661 });662663 Ok(())664 }665666 /// accept the addition of a new resource to an existing NFT667 #[transactional]668 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]669 pub fn accept_resource(670 origin: OriginFor<T>,671 rmrk_collection_id: RmrkCollectionId,672 rmrk_nft_id: RmrkNftId,673 rmrk_resource_id: RmrkResourceId,674 ) -> DispatchResult {675 let sender = ensure_signed(origin)?;676 let cross_sender = T::CrossAccountId::from_sub(sender);677678 let collection_id = Self::unique_collection_id(rmrk_collection_id)679 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;680 let collection =681 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;682 collection.check_is_external()?;683684 let nft_id = rmrk_nft_id.into();685 let resource_id = rmrk_resource_id.into();686687 let budget = budget::Value::new(NESTING_BUDGET);688689 let nft_owner =690 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)691 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;692693 let resource_collection_id: Option<CollectionId> =694 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)695 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;696697 let resource_collection_id =698 resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;699700 let is_pending: bool = Self::get_nft_property_decoded(701 resource_collection_id,702 resource_id,703 PendingResourceAccept,704 )705 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;706707 ensure!(is_pending, <Error<T>>::ResourceNotPending);708709 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);710711 <PalletNft<T>>::set_scoped_token_property(712 resource_collection_id,713 rmrk_resource_id.into(),714 PropertyScope::Rmrk,715 Self::rmrk_property(PendingResourceAccept, &false)?,716 )?;717718 Self::deposit_event(Event::<T>::ResourceAccepted {719 nft_id: rmrk_nft_id,720 resource_id: rmrk_resource_id,721 });722723 Ok(())724 }725726 /// accept the removal of a resource of an existing NFT727 #[transactional]728 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]729 pub fn accept_resource_removal(730 origin: OriginFor<T>,731 rmrk_collection_id: RmrkCollectionId,732 rmrk_nft_id: RmrkNftId,733 rmrk_resource_id: RmrkResourceId,734 ) -> DispatchResult {735 let sender = ensure_signed(origin)?;736 let cross_sender = T::CrossAccountId::from_sub(sender);737738 let collection_id = Self::unique_collection_id(rmrk_collection_id)739 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;740 let collection =741 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;742 collection.check_is_external()?;743744 let nft_id = rmrk_nft_id.into();745 let resource_id = rmrk_resource_id.into();746747 let budget = budget::Value::new(NESTING_BUDGET);748749 let nft_owner =750 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)751 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;752753 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);754755 let resource_collection_id: Option<CollectionId> =756 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)757 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;758759 let resource_collection_id =760 resource_collection_id.ok_or(<Error<T>>::ResourceDoesntExist)?;761762 let is_pending: bool = Self::get_nft_property_decoded(763 resource_collection_id,764 resource_id,765 PendingResourceRemoval,766 )767 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;768769 ensure!(is_pending, <Error<T>>::ResourceNotPending);770771 let resource_collection = Self::get_typed_nft_collection(772 resource_collection_id,773 misc::CollectionType::Resource,774 )?;775776 let resource_data = <TokenData<T>>::get((resource_collection_id, resource_id))777 .ok_or(<Error<T>>::ResourceDoesntExist)?;778779 let resource_owner = resource_data.owner;780781 <PalletNft<T>>::burn(782 &resource_collection,783 &resource_owner,784 rmrk_resource_id.into(),785 )786 .map_err(Self::map_unique_err_to_proxy)?;787788 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {789 nft_id: rmrk_nft_id,790 resource_id: rmrk_resource_id,791 });792793 Ok(())794 }795796 /// set a custom value on an NFT797 #[transactional]798 #[pallet::weight(<SelfWeightOf<T>>::set_property())]799 pub fn set_property(800 origin: OriginFor<T>,801 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,802 maybe_nft_id: Option<RmrkNftId>,803 key: RmrkKeyString,804 value: RmrkValueString,805 ) -> DispatchResult {806 let sender = ensure_signed(origin)?;807 let sender = T::CrossAccountId::from_sub(sender);808809 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;810 let collection =811 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;812 collection.check_is_external()?;813814 let budget = budget::Value::new(NESTING_BUDGET);815816 match maybe_nft_id {817 Some(nft_id) => {818 let token_id: TokenId = nft_id.into();819820 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;821 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;822823 <PalletNft<T>>::set_scoped_token_property(824 collection_id,825 token_id,826 PropertyScope::Rmrk,827 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,828 )?;829 }830 None => {831 let collection = Self::get_typed_nft_collection(832 collection_id,833 misc::CollectionType::Regular,834 )?;835836 Self::check_collection_owner(&collection, &sender)?;837838 <PalletCommon<T>>::set_scoped_collection_property(839 collection_id,840 PropertyScope::Rmrk,841 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,842 )?;843 }844 }845846 Self::deposit_event(Event::PropertySet {847 collection_id: rmrk_collection_id,848 maybe_nft_id,849 key,850 value,851 });852853 Ok(())854 }855856 /// set a different order of resource priority857 #[transactional]858 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]859 pub fn set_priority(860 origin: OriginFor<T>,861 rmrk_collection_id: RmrkCollectionId,862 rmrk_nft_id: RmrkNftId,863 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,864 ) -> DispatchResult {865 let sender = ensure_signed(origin)?;866 let sender = T::CrossAccountId::from_sub(sender);867868 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;869 let nft_id = rmrk_nft_id.into();870871 let collection =872 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;873 collection.check_is_external()?;874875 let budget = budget::Value::new(NESTING_BUDGET);876877 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;878 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;879880 <PalletNft<T>>::set_scoped_token_property(881 collection_id,882 nft_id,883 PropertyScope::Rmrk,884 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,885 )?;886887 Self::deposit_event(Event::<T>::PrioritySet {888 collection_id: rmrk_collection_id,889 nft_id: rmrk_nft_id,890 });891892 Ok(())893 }894895 /// Create basic resource896 #[transactional]897 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]898 pub fn add_basic_resource(899 origin: OriginFor<T>,900 rmrk_collection_id: RmrkCollectionId,901 nft_id: RmrkNftId,902 resource: RmrkBasicResource,903 ) -> DispatchResult {904 let sender = ensure_signed(origin.clone())?;905906 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;907 let collection =908 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;909 collection.check_is_external()?;910911 let resource_id = Self::resource_add(912 sender,913 collection_id,914 nft_id.into(),915 [916 Self::rmrk_property(TokenType, &NftType::Resource)?,917 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,918 Self::rmrk_property(Src, &resource.src)?,919 Self::rmrk_property(Metadata, &resource.metadata)?,920 Self::rmrk_property(License, &resource.license)?,921 Self::rmrk_property(Thumb, &resource.thumb)?,922 ]923 .into_iter(),924 )?;925926 Self::deposit_event(Event::ResourceAdded {927 nft_id,928 resource_id,929 });930 Ok(())931 }932933 /// Create composable resource934 #[transactional]935 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]936 pub fn add_composable_resource(937 origin: OriginFor<T>,938 rmrk_collection_id: RmrkCollectionId,939 nft_id: RmrkNftId,940 resource: RmrkComposableResource,941 ) -> DispatchResult {942 let sender = ensure_signed(origin.clone())?;943944 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;945 let collection =946 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;947 collection.check_is_external()?;948949 let resource_id = Self::resource_add(950 sender,951 collection_id,952 nft_id.into(),953 [954 Self::rmrk_property(TokenType, &NftType::Resource)?,955 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,956 Self::rmrk_property(Parts, &resource.parts)?,957 Self::rmrk_property(Base, &resource.base)?,958 Self::rmrk_property(Src, &resource.src)?,959 Self::rmrk_property(Metadata, &resource.metadata)?,960 Self::rmrk_property(License, &resource.license)?,961 Self::rmrk_property(Thumb, &resource.thumb)?,962 ]963 .into_iter(),964 )?;965966 Self::deposit_event(Event::ResourceAdded {967 nft_id,968 resource_id,969 });970 Ok(())971 }972973 /// Create slot resource974 #[transactional]975 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]976 pub fn add_slot_resource(977 origin: OriginFor<T>,978 rmrk_collection_id: RmrkCollectionId,979 nft_id: RmrkNftId,980 resource: RmrkSlotResource,981 ) -> DispatchResult {982 let sender = ensure_signed(origin.clone())?;983984 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;985 let collection =986 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;987 collection.check_is_external()?;988989 let resource_id = Self::resource_add(990 sender,991 collection_id,992 nft_id.into(),993 [994 Self::rmrk_property(TokenType, &NftType::Resource)?,995 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,996 Self::rmrk_property(Base, &resource.base)?,997 Self::rmrk_property(Src, &resource.src)?,998 Self::rmrk_property(Metadata, &resource.metadata)?,999 Self::rmrk_property(Slot, &resource.slot)?,1000 Self::rmrk_property(License, &resource.license)?,1001 Self::rmrk_property(Thumb, &resource.thumb)?,1002 ]1003 .into_iter(),1004 )?;10051006 Self::deposit_event(Event::ResourceAdded {1007 nft_id,1008 resource_id,1009 });1010 Ok(())1011 }10121013 /// remove resource1014 #[transactional]1015 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1016 pub fn remove_resource(1017 origin: OriginFor<T>,1018 rmrk_collection_id: RmrkCollectionId,1019 nft_id: RmrkNftId,1020 resource_id: RmrkResourceId,1021 ) -> DispatchResult {1022 let sender = ensure_signed(origin.clone())?;10231024 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1025 let collection =1026 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1027 collection.check_is_external()?;10281029 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;10301031 Self::deposit_event(Event::ResourceRemoval {1032 nft_id,1033 resource_id,1034 });1035 Ok(())1036 }1037 }1038}10391040impl<T: Config> Pallet<T> {1041 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1042 let key = rmrk_key.to_key::<T>()?;10431044 let scoped_key = PropertyScope::Rmrk1045 .apply(key)1046 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10471048 Ok(scoped_key)1049 }10501051 // todo think about renaming these1052 pub fn rmrk_property<E: Encode>(1053 rmrk_key: RmrkProperty,1054 value: &E,1055 ) -> Result<Property, DispatchError> {1056 let key = rmrk_key.to_key::<T>()?;10571058 let value = value1059 .encode()1060 .try_into()1061 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;10621063 let property = Property { key, value };10641065 Ok(property)1066 }10671068 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {1069 vec.decode()1070 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1071 }10721073 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1074 where1075 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1076 {1077 vec.rebind()1078 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1079 }10801081 fn init_collection(1082 sender: T::CrossAccountId,1083 data: CreateCollectionData<T::AccountId>,1084 properties: impl Iterator<Item = Property>,1085 ) -> Result<CollectionId, DispatchError> {1086 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);10871088 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1089 return Err(<Error<T>>::NoAvailableCollectionId.into());1090 }10911092 <PalletCommon<T>>::set_scoped_collection_properties(1093 collection_id?,1094 PropertyScope::Rmrk,1095 properties,1096 )?;10971098 collection_id1099 }11001101 pub fn create_nft(1102 sender: &T::CrossAccountId,1103 owner: &T::CrossAccountId,1104 collection: &NonfungibleHandle<T>,1105 properties: impl Iterator<Item = Property>,1106 ) -> Result<TokenId, DispatchError> {1107 let data = CreateNftExData {1108 properties: BoundedVec::default(),1109 owner: owner.clone(),1110 };11111112 let budget = budget::Value::new(NESTING_BUDGET);11131114 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;11151116 let nft_id = <PalletNft<T>>::current_token_id(collection.id);11171118 <PalletNft<T>>::set_scoped_token_properties(1119 collection.id,1120 nft_id,1121 PropertyScope::Rmrk,1122 properties,1123 )?;11241125 Ok(nft_id)1126 }11271128 fn destroy_nft(1129 sender: T::CrossAccountId,1130 collection_id: CollectionId,1131 token_id: TokenId,1132 max_burns: u32,1133 error_if_not_owned: Error<T>,1134 ) -> DispatchResultWithPostInfo {1135 let collection =1136 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11371138 let token_data =1139 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11401141 let from = token_data.owner;11421143 let owner_check_budget = budget::Value::new(NESTING_BUDGET);11441145 ensure!(1146 <PalletStructure<T>>::check_indirectly_owned(1147 sender.clone(),1148 collection_id,1149 token_id,1150 None,1151 &owner_check_budget1152 )?,1153 error_if_not_owned,1154 );11551156 let burns_budget = budget::Value::new(max_burns);1157 let breadth_budget = budget::Value::new(max_burns);11581159 <PalletNft<T>>::burn_recursively(1160 &collection,1161 &from,1162 token_id,1163 &burns_budget,1164 &breadth_budget,1165 )1166 }11671168 fn resource_add(1169 sender: T::AccountId,1170 collection_id: CollectionId,1171 token_id: TokenId,1172 resource_properties: impl Iterator<Item = Property>,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, token_id, &budget)1182 .map_err(Self::map_unique_err_to_proxy)?;11831184 let pending = sender != nft_owner;11851186 let resource_collection_id: Option<CollectionId> =1187 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;11881189 let resource_collection_id = match resource_collection_id {1190 Some(id) => id,1191 None => {1192 let resource_collection_id = Self::init_collection(1193 sender.clone(),1194 CreateCollectionData {1195 ..Default::default()1196 },1197 [Self::rmrk_property(1198 CollectionType,1199 &misc::CollectionType::Resource,1200 )?]1201 .into_iter(),1202 )?;12031204 <PalletNft<T>>::set_scoped_token_property(1205 collection_id,1206 token_id,1207 PropertyScope::Rmrk,1208 Self::rmrk_property(ResourceCollection, &Some(resource_collection_id))?,1209 )?;12101211 resource_collection_id1212 }1213 };12141215 let resource_collection =1216 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;12171218 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them12191220 let resource_id = Self::create_nft(1221 &sender,1222 &nft_owner,1223 &resource_collection,1224 resource_properties.chain(1225 [1226 Self::rmrk_property(PendingResourceAccept, &pending)?,1227 Self::rmrk_property(PendingResourceRemoval, &false)?,1228 ]1229 .into_iter(),1230 ),1231 )1232 .map_err(|err| match err {1233 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),1234 err => Self::map_unique_err_to_proxy(err),1235 })?;12361237 Ok(resource_id.0)1238 }12391240 fn resource_remove(1241 sender: T::AccountId,1242 collection_id: CollectionId,1243 nft_id: TokenId,1244 resource_id: TokenId,1245 ) -> DispatchResult {1246 let collection =1247 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1248 ensure!(collection.owner == sender, Error::<T>::NoPermission);12491250 let resource_collection_id: Option<CollectionId> =1251 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;12521253 let resource_collection_id =1254 resource_collection_id.ok_or(Error::<T>::ResourceDoesntExist)?;12551256 let resource_collection =1257 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;1258 ensure!(1259 <PalletNft<T>>::token_exists(&resource_collection, resource_id),1260 Error::<T>::ResourceDoesntExist1261 );12621263 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1264 let topmost_owner =1265 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;12661267 let sender = T::CrossAccountId::from_sub(sender);1268 if topmost_owner == sender {1269 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1270 .map_err(Self::map_unique_err_to_proxy)?;1271 } else {1272 <PalletNft<T>>::set_scoped_token_property(1273 resource_collection_id,1274 resource_id,1275 PropertyScope::Rmrk,1276 Self::rmrk_property(PendingResourceRemoval, &true)?,1277 )?;1278 }12791280 Ok(())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(1322 unique_collection_id,1323 RmrkInternalCollectionId1324 )1325 }13261327 pub fn get_nft_collection(1328 collection_id: CollectionId,1329 ) -> Result<NonfungibleHandle<T>, DispatchError> {1330 let collection = <CollectionHandle<T>>::try_get(collection_id)1331 .map_err(|_| <Error<T>>::CollectionUnknown)?;13321333 match collection.mode {1334 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1335 _ => Err(<Error<T>>::CollectionUnknown.into()),1336 }1337 }13381339 pub fn collection_exists(collection_id: CollectionId) -> bool {1340 <CollectionHandle<T>>::try_get(collection_id).is_ok()1341 }13421343 pub fn get_collection_property(1344 collection_id: CollectionId,1345 key: RmrkProperty,1346 ) -> Result<PropertyValue, DispatchError> {1347 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1348 .get(&Self::rmrk_property_key(key)?)1349 .ok_or(<Error<T>>::CollectionUnknown)?1350 .clone();13511352 Ok(collection_property)1353 }13541355 pub fn get_collection_property_decoded<V: Decode>(1356 collection_id: CollectionId,1357 key: RmrkProperty,1358 ) -> Result<V, DispatchError> {1359 Self::decode_property(Self::get_collection_property(collection_id, key)?)1360 }13611362 pub fn get_collection_type(1363 collection_id: CollectionId,1364 ) -> Result<misc::CollectionType, DispatchError> {1365 Self::get_collection_property_decoded(collection_id, CollectionType)1366 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1367 }13681369 pub fn ensure_collection_type(1370 collection_id: CollectionId,1371 collection_type: misc::CollectionType,1372 ) -> DispatchResult {1373 let actual_type = Self::get_collection_type(collection_id)?;1374 ensure!(1375 actual_type == collection_type,1376 <CommonError<T>>::NoPermission1377 );13781379 Ok(())1380 }13811382 pub fn get_typed_nft_collection(1383 collection_id: CollectionId,1384 collection_type: misc::CollectionType,1385 ) -> Result<NonfungibleHandle<T>, DispatchError> {1386 Self::ensure_collection_type(collection_id, collection_type)?;13871388 Self::get_nft_collection(collection_id)1389 }13901391 pub fn get_typed_nft_collection_mapped(1392 rmrk_collection_id: RmrkCollectionId,1393 collection_type: misc::CollectionType,1394 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1395 let unique_collection_id = match collection_type {1396 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1397 _ => rmrk_collection_id.into(),1398 };13991400 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;14011402 Ok((collection, unique_collection_id))1403 }14041405 pub fn get_nft_property(1406 collection_id: CollectionId,1407 nft_id: TokenId,1408 key: RmrkProperty,1409 ) -> Result<PropertyValue, DispatchError> {1410 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1411 .get(&Self::rmrk_property_key(key)?)1412 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1413 .clone();14141415 Ok(nft_property)1416 }14171418 pub fn get_nft_property_decoded<V: Decode>(1419 collection_id: CollectionId,1420 nft_id: TokenId,1421 key: RmrkProperty,1422 ) -> Result<V, DispatchError> {1423 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1424 }14251426 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1427 <TokenData<T>>::contains_key((collection_id, nft_id))1428 }14291430 pub fn get_nft_type(1431 collection_id: CollectionId,1432 token_id: TokenId,1433 ) -> Result<NftType, DispatchError> {1434 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1435 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1436 }14371438 pub fn ensure_nft_type(1439 collection_id: CollectionId,1440 token_id: TokenId,1441 nft_type: NftType,1442 ) -> DispatchResult {1443 let actual_type = Self::get_nft_type(collection_id, token_id)?;1444 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);14451446 Ok(())1447 }14481449 pub fn ensure_nft_owner(1450 collection_id: CollectionId,1451 token_id: TokenId,1452 possible_owner: &T::CrossAccountId,1453 nesting_budget: &dyn budget::Budget,1454 ) -> DispatchResult {1455 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1456 possible_owner.clone(),1457 collection_id,1458 token_id,1459 None,1460 nesting_budget,1461 )1462 .map_err(Self::map_unique_err_to_proxy)?;14631464 ensure!(is_owned, <Error<T>>::NoPermission);14651466 Ok(())1467 }14681469 pub fn filter_user_properties<Key, Value, R, Mapper>(1470 collection_id: CollectionId,1471 token_id: Option<TokenId>,1472 filter_keys: Option<Vec<RmrkPropertyKey>>,1473 mapper: Mapper,1474 ) -> Result<Vec<R>, DispatchError>1475 where1476 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1477 Value: Decode + Default,1478 Mapper: Fn(Key, Value) -> R,1479 {1480 filter_keys1481 .map(|keys| {1482 let properties = keys1483 .into_iter()1484 .filter_map(|key| {1485 let key: Key = key.try_into().ok()?;14861487 let value = match token_id {1488 Some(token_id) => Self::get_nft_property_decoded(1489 collection_id,1490 token_id,1491 UserProperty(key.as_ref()),1492 ),1493 None => Self::get_collection_property_decoded(1494 collection_id,1495 UserProperty(key.as_ref()),1496 ),1497 }1498 .ok()?;14991500 Some(mapper(key, value))1501 })1502 .collect();15031504 Ok(properties)1505 })1506 .unwrap_or_else(|| {1507 let properties =1508 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();15091510 Ok(properties)1511 })1512 }15131514 pub fn iterate_user_properties<Key, Value, R, Mapper>(1515 collection_id: CollectionId,1516 token_id: Option<TokenId>,1517 mapper: Mapper,1518 ) -> Result<impl Iterator<Item = R>, DispatchError>1519 where1520 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1521 Value: Decode + Default,1522 Mapper: Fn(Key, Value) -> R,1523 {1524 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;15251526 let properties = match token_id {1527 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1528 None => <PalletCommon<T>>::collection_properties(collection_id),1529 };15301531 let properties = properties.into_iter().filter_map(move |(key, value)| {1532 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;15331534 let key: Key = key.to_vec().try_into().ok()?;1535 let value: Value = value.decode().ok()?;15361537 Some(mapper(key, value))1538 });15391540 Ok(properties)1541 }15421543 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1544 map_unique_err_to_proxy! {1545 match err {1546 CommonError::NoPermission => NoPermission,1547 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1548 CommonError::PublicMintingNotAllowed => NoPermission,1549 CommonError::TokenNotFound => NoAvailableNftId,1550 CommonError::ApprovedValueTooLow => NoPermission,1551 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1552 StructureError::TokenNotFound => NoAvailableNftId,1553 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1554 }1555 }1556 }1557}