1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::{23 vec::Vec,24 collections::{btree_set::BTreeSet, btree_map::BTreeMap},25};26use up_data_structs::{*, mapping::TokenAddressMapping};27use pallet_common::{28 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,29};30use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};31use pallet_structure::{Pallet as PalletStructure, Error as StructureError};32use pallet_evm::account::CrossAccountId;33use core::convert::AsRef;3435pub use pallet::*;3637#[cfg(feature = "runtime-benchmarks")]38pub mod benchmarking;39pub mod misc;40pub mod property;41pub mod rpc;42pub mod weights;4344pub type SelfWeightOf<T> = <T as Config>::WeightInfo;4546use weights::WeightInfo;47use misc::*;48pub use property::*;4950use RmrkProperty::*;5152pub const NESTING_BUDGET: u32 = 5;5354type PendingTarget = (CollectionId, TokenId);55type PendingChild = (RmrkCollectionId, RmrkNftId);56type PendingChildrenSet = BTreeSet<PendingChild>;5758type BasesMap = BTreeMap<RmrkBaseId, u32>;5960#[frame_support::pallet]61pub mod pallet {62 use super::*;63 use pallet_evm::account;6465 #[pallet::config]66 pub trait Config:67 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config68 {69 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;70 type WeightInfo: WeightInfo;71 }7273 #[pallet::storage]74 #[pallet::getter(fn collection_index)]75 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;7677 #[pallet::storage]78 pub type UniqueCollectionId<T: Config> =79 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;8081 #[pallet::pallet]82 #[pallet::generate_store(pub(super) trait Store)]83 pub struct Pallet<T>(_);8485 #[pallet::event]86 #[pallet::generate_deposit(pub(super) fn deposit_event)]87 pub enum Event<T: Config> {88 CollectionCreated {89 issuer: T::AccountId,90 collection_id: RmrkCollectionId,91 },92 CollectionDestroyed {93 issuer: T::AccountId,94 collection_id: RmrkCollectionId,95 },96 IssuerChanged {97 old_issuer: T::AccountId,98 new_issuer: T::AccountId,99 collection_id: RmrkCollectionId,100 },101 CollectionLocked {102 issuer: T::AccountId,103 collection_id: RmrkCollectionId,104 },105 NftMinted {106 owner: T::AccountId,107 collection_id: RmrkCollectionId,108 nft_id: RmrkNftId,109 },110 NFTBurned {111 owner: T::AccountId,112 nft_id: RmrkNftId,113 },114 NFTSent {115 sender: T::AccountId,116 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,117 collection_id: RmrkCollectionId,118 nft_id: RmrkNftId,119 approval_required: bool,120 },121 NFTAccepted {122 sender: T::AccountId,123 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,124 collection_id: RmrkCollectionId,125 nft_id: RmrkNftId,126 },127 NFTRejected {128 sender: T::AccountId,129 collection_id: RmrkCollectionId,130 nft_id: RmrkNftId,131 },132 PropertySet {133 collection_id: RmrkCollectionId,134 maybe_nft_id: Option<RmrkNftId>,135 key: RmrkKeyString,136 value: RmrkValueString,137 },138 ResourceAdded {139 nft_id: RmrkNftId,140 resource_id: RmrkResourceId,141 },142 ResourceRemoval {143 nft_id: RmrkNftId,144 resource_id: RmrkResourceId,145 },146 ResourceAccepted {147 nft_id: RmrkNftId,148 resource_id: RmrkResourceId,149 },150 ResourceRemovalAccepted {151 nft_id: RmrkNftId,152 resource_id: RmrkResourceId,153 },154 PrioritySet {155 collection_id: RmrkCollectionId,156 nft_id: RmrkNftId,157 },158 }159160 #[pallet::error]161 pub enum Error<T> {162 163 CorruptedCollectionType,164 NftTypeEncodeError,165 RmrkPropertyKeyIsTooLong,166 RmrkPropertyValueIsTooLong,167 RmrkPropertyIsNotFound,168 UnableToDecodeRmrkData,169170 171 CollectionNotEmpty,172 NoAvailableCollectionId,173 NoAvailableNftId,174 CollectionUnknown,175 NoPermission,176 NonTransferable,177 CollectionFullOrLocked,178 ResourceDoesntExist,179 CannotSendToDescendentOrSelf,180 CannotAcceptNonOwnedNft,181 CannotRejectNonOwnedNft,182 CannotRejectNonPendingNft,183 ResourceNotPending,184 NoAvailableResourceId,185 }186187 #[pallet::call]188 impl<T: Config> Pallet<T> {189 190 #[transactional]191 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]192 pub fn create_collection(193 origin: OriginFor<T>,194 metadata: RmrkString,195 max: Option<u32>,196 symbol: RmrkCollectionSymbol,197 ) -> DispatchResult {198 let sender = ensure_signed(origin)?;199200 let limits = CollectionLimits {201 owner_can_transfer: Some(false),202 token_limit: max,203 ..Default::default()204 };205206 let data = CreateCollectionData {207 limits: Some(limits),208 token_prefix: symbol209 .into_inner()210 .try_into()211 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,212 permissions: Some(CollectionPermissions {213 nesting: Some(NestingPermissions {214 token_owner: true,215 collection_admin: false,216 restricted: None,217 #[cfg(feature = "runtime-benchmarks")]218 permissive: false,219 }),220 ..Default::default()221 }),222 ..Default::default()223 };224225 let unique_collection_id = Self::init_collection(226 T::CrossAccountId::from_sub(sender.clone()),227 data,228 [229 Self::rmrk_property(Metadata, &metadata)?,230 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,231 ]232 .into_iter(),233 )?;234 let rmrk_collection_id = <CollectionIndex<T>>::get();235236 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);237238 <PalletCommon<T>>::set_scoped_collection_property(239 unique_collection_id,240 PropertyScope::Rmrk,241 Self::rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,242 )?;243244 <CollectionIndex<T>>::mutate(|n| *n += 1);245246 Self::deposit_event(Event::CollectionCreated {247 issuer: sender,248 collection_id: rmrk_collection_id,249 });250251 Ok(())252 }253254 255 #[transactional]256 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]257 pub fn destroy_collection(258 origin: OriginFor<T>,259 collection_id: RmrkCollectionId,260 ) -> DispatchResult {261 let sender = ensure_signed(origin)?;262 let cross_sender = T::CrossAccountId::from_sub(sender.clone());263264 let collection = Self::get_typed_nft_collection(265 Self::unique_collection_id(collection_id)?,266 misc::CollectionType::Regular,267 )?;268 collection.check_is_external()?;269270 <PalletNft<T>>::destroy_collection(collection, &cross_sender)271 .map_err(Self::map_unique_err_to_proxy)?;272273 Self::deposit_event(Event::CollectionDestroyed {274 issuer: sender,275 collection_id,276 });277278 Ok(())279 }280281 282 283 284 285 286 287 #[transactional]288 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]289 pub fn change_collection_issuer(290 origin: OriginFor<T>,291 collection_id: RmrkCollectionId,292 new_issuer: <T::Lookup as StaticLookup>::Source,293 ) -> DispatchResult {294 let sender = ensure_signed(origin)?;295296 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;297 collection.check_is_external()?;298299 let new_issuer = T::Lookup::lookup(new_issuer)?;300301 Self::change_collection_owner(302 Self::unique_collection_id(collection_id)?,303 misc::CollectionType::Regular,304 sender.clone(),305 new_issuer.clone(),306 )?;307308 Self::deposit_event(Event::IssuerChanged {309 old_issuer: sender,310 new_issuer,311 collection_id,312 });313314 Ok(())315 }316317 318 #[transactional]319 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]320 pub fn lock_collection(321 origin: OriginFor<T>,322 collection_id: RmrkCollectionId,323 ) -> DispatchResult {324 let sender = ensure_signed(origin)?;325 let cross_sender = T::CrossAccountId::from_sub(sender.clone());326327 let collection = Self::get_typed_nft_collection(328 Self::unique_collection_id(collection_id)?,329 misc::CollectionType::Regular,330 )?;331 collection.check_is_external()?;332333 Self::check_collection_owner(&collection, &cross_sender)?;334335 let token_count = collection.total_supply();336337 let mut collection = collection.into_inner();338 collection.limits.token_limit = Some(token_count);339 collection.save()?;340341 Self::deposit_event(Event::CollectionLocked {342 issuer: sender,343 collection_id,344 });345346 Ok(())347 }348349 350 351 352 353 354 355 356 357 358 359 #[transactional]360 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]361 pub fn mint_nft(362 origin: OriginFor<T>,363 owner: Option<T::AccountId>,364 collection_id: RmrkCollectionId,365 recipient: Option<T::AccountId>,366 royalty_amount: Option<Permill>,367 metadata: RmrkString,368 transferable: bool,369 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,370 ) -> DispatchResult {371 let sender = ensure_signed(origin)?;372 let cross_sender = T::CrossAccountId::from_sub(sender.clone());373374 let owner = owner.unwrap_or(sender.clone());375 let cross_owner = T::CrossAccountId::from_sub(owner.clone());376377 let collection = Self::get_typed_nft_collection(378 Self::unique_collection_id(collection_id)?,379 misc::CollectionType::Regular,380 )?;381 collection.check_is_external()?;382383 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {384 recipient: recipient.unwrap_or_else(|| owner.clone()),385 amount,386 });387388 let nft_id = Self::create_nft(389 &cross_sender,390 &cross_owner,391 &collection,392 [393 Self::rmrk_property(TokenType, &NftType::Regular)?,394 Self::rmrk_property(Transferable, &transferable)?,395 Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,396 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,397 Self::rmrk_property(Metadata, &metadata)?,398 Self::rmrk_property(Equipped, &false)?,399 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,400 Self::rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,401 Self::rmrk_property(PendingChildren, &PendingChildrenSet::new())?,402 Self::rmrk_property(AssociatedBases, &BasesMap::new())?,403 ]404 .into_iter(),405 )406 .map_err(|err| match err {407 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),408 err => Self::map_unique_err_to_proxy(err),409 })?;410411 if let Some(resources) = resources {412 for resource in resources {413 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;414 }415 }416417 Self::deposit_event(Event::NftMinted {418 owner,419 collection_id,420 nft_id: nft_id.0,421 });422423 Ok(())424 }425426 427 #[transactional]428 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]429 pub fn burn_nft(430 origin: OriginFor<T>,431 collection_id: RmrkCollectionId,432 nft_id: RmrkNftId,433 max_burns: u32,434 ) -> DispatchResult {435 let sender = ensure_signed(origin)?;436 let cross_sender = T::CrossAccountId::from_sub(sender.clone());437438 let collection = Self::get_typed_nft_collection(439 Self::unique_collection_id(collection_id)?,440 misc::CollectionType::Regular,441 )?;442 collection.check_is_external()?;443444 Self::destroy_nft(445 cross_sender,446 Self::unique_collection_id(collection_id)?,447 nft_id.into(),448 max_burns,449 <Error<T>>::NoPermission,450 )451 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;452453 Self::deposit_event(Event::NFTBurned {454 owner: sender,455 nft_id,456 });457458 Ok(())459 }460461 462 463 464 465 466 467 468 #[transactional]469 #[pallet::weight(<SelfWeightOf<T>>::send())]470 pub fn send(471 origin: OriginFor<T>,472 rmrk_collection_id: RmrkCollectionId,473 rmrk_nft_id: RmrkNftId,474 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,475 ) -> DispatchResult {476 let sender = ensure_signed(origin.clone())?;477 let cross_sender = T::CrossAccountId::from_sub(sender.clone());478479 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;480 let nft_id = rmrk_nft_id.into();481482 let collection =483 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;484 collection.check_is_external()?;485486 let token_data =487 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;488489 let from = token_data.owner;490491 ensure!(492 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,493 <Error<T>>::NonTransferable494 );495496 ensure!(497 Self::get_nft_property_decoded::<Option<PendingTarget>>(498 collection_id,499 nft_id,500 RmrkProperty::PendingNftAccept501 )?502 .is_none(),503 <Error<T>>::NoPermission504 );505506 let target_owner;507 let approval_required;508509 match new_owner {510 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {511 target_owner = T::CrossAccountId::from_sub(account_id.clone());512 approval_required = false;513 }514 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(515 target_collection_id,516 target_nft_id,517 ) => {518 let target_collection_id = Self::unique_collection_id(target_collection_id)?;519520 let target_nft_budget = budget::Value::new(NESTING_BUDGET);521522 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(523 target_collection_id,524 target_nft_id.into(),525 Some((collection_id, nft_id)),526 &target_nft_budget,527 )528 .map_err(Self::map_unique_err_to_proxy)?;529530 approval_required = cross_sender != target_nft_owner;531532 if approval_required {533 target_owner = target_nft_owner;534535 <PalletNft<T>>::set_scoped_token_property(536 collection.id,537 nft_id,538 PropertyScope::Rmrk,539 Self::rmrk_property::<Option<PendingTarget>>(540 PendingNftAccept,541 &Some((target_collection_id, target_nft_id.into())),542 )?,543 )?;544545 Self::insert_pending_child(546 (target_collection_id, target_nft_id.into()),547 (rmrk_collection_id, rmrk_nft_id),548 )?;549 } else {550 target_owner = T::CrossTokenAddressMapping::token_to_address(551 target_collection_id,552 target_nft_id.into(),553 );554 }555 }556 }557558 let src_nft_budget = budget::Value::new(NESTING_BUDGET);559560 <PalletNft<T>>::transfer_from(561 &collection,562 &cross_sender,563 &from,564 &target_owner,565 nft_id,566 &src_nft_budget,567 )568 .map_err(Self::map_unique_err_to_proxy)?;569570 Self::deposit_event(Event::NFTSent {571 sender,572 recipient: new_owner,573 collection_id: rmrk_collection_id,574 nft_id: rmrk_nft_id,575 approval_required,576 });577578 Ok(())579 }580581 582 583 584 585 586 587 588 589 #[transactional]590 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]591 pub fn accept_nft(592 origin: OriginFor<T>,593 rmrk_collection_id: RmrkCollectionId,594 rmrk_nft_id: RmrkNftId,595 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,596 ) -> DispatchResult {597 let sender = ensure_signed(origin.clone())?;598 let cross_sender = T::CrossAccountId::from_sub(sender.clone());599600 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;601 let nft_id = rmrk_nft_id.into();602603 let collection =604 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;605 collection.check_is_external()?;606607 let new_cross_owner = match new_owner {608 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {609 T::CrossAccountId::from_sub(account_id.clone())610 }611 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(612 target_collection_id,613 target_nft_id,614 ) => {615 let target_collection_id = Self::unique_collection_id(target_collection_id)?;616617 T::CrossTokenAddressMapping::token_to_address(618 target_collection_id,619 TokenId(target_nft_id),620 )621 }622 };623624 let budget = budget::Value::new(NESTING_BUDGET);625626 <PalletNft<T>>::transfer(627 &collection,628 &cross_sender,629 &new_cross_owner,630 nft_id,631 &budget,632 )633 .map_err(|err| {634 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {635 <Error<T>>::CannotAcceptNonOwnedNft.into()636 } else {637 Self::map_unique_err_to_proxy(err)638 }639 })?;640641 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(642 collection_id,643 nft_id,644 RmrkProperty::PendingNftAccept,645 )?;646647 if let Some(pending_target) = pending_target {648 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;649650 <PalletNft<T>>::set_scoped_token_property(651 collection.id,652 nft_id,653 PropertyScope::Rmrk,654 Self::rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,655 )?;656 }657658 Self::deposit_event(Event::NFTAccepted {659 sender,660 recipient: new_owner,661 collection_id: rmrk_collection_id,662 nft_id: rmrk_nft_id,663 });664665 Ok(())666 }667668 669 670 671 672 673 674 #[transactional]675 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]676 pub fn reject_nft(677 origin: OriginFor<T>,678 rmrk_collection_id: RmrkCollectionId,679 rmrk_nft_id: RmrkNftId,680 ) -> DispatchResult {681 let sender = ensure_signed(origin)?;682 let cross_sender = T::CrossAccountId::from_sub(sender.clone());683684 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;685 let nft_id = rmrk_nft_id.into();686687 let collection =688 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;689 collection.check_is_external()?;690691 ensure!(692 <TokenData<T>>::get((collection_id, nft_id)).is_some(),693 <Error<T>>::NoAvailableNftId694 );695696 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(697 collection_id,698 nft_id,699 RmrkProperty::PendingNftAccept,700 )?;701702 match pending_target {703 Some(pending_target) => {704 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?705 }706 None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),707 }708709 Self::destroy_nft(710 cross_sender,711 collection_id,712 nft_id,713 NESTING_BUDGET,714 <Error<T>>::CannotRejectNonOwnedNft,715 )716 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;717718 Self::deposit_event(Event::NFTRejected {719 sender,720 collection_id: rmrk_collection_id,721 nft_id: rmrk_nft_id,722 });723724 Ok(())725 }726727 728 #[transactional]729 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]730 pub fn accept_resource(731 origin: OriginFor<T>,732 rmrk_collection_id: RmrkCollectionId,733 rmrk_nft_id: RmrkNftId,734 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();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 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {754 ensure!(res.pending, <Error<T>>::ResourceNotPending);755 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);756757 res.pending = false;758759 Ok(())760 })?;761762 Self::deposit_event(Event::<T>::ResourceAccepted {763 nft_id: rmrk_nft_id,764 resource_id,765 });766767 Ok(())768 }769770 771 #[transactional]772 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]773 pub fn accept_resource_removal(774 origin: OriginFor<T>,775 rmrk_collection_id: RmrkCollectionId,776 rmrk_nft_id: RmrkNftId,777 resource_id: RmrkResourceId,778 ) -> DispatchResult {779 let sender = ensure_signed(origin)?;780 let cross_sender = T::CrossAccountId::from_sub(sender);781782 let collection_id = Self::unique_collection_id(rmrk_collection_id)783 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;784 let collection =785 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;786 collection.check_is_external()?;787788 let nft_id = rmrk_nft_id.into();789790 let budget = budget::Value::new(NESTING_BUDGET);791792 let nft_owner =793 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)794 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;795796 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);797798 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;799800 let resource_info = <PalletNft<T>>::token_aux_property((801 collection_id,802 nft_id,803 PropertyScope::Rmrk,804 resource_id_key.clone(),805 ))806 .ok_or(<Error<T>>::ResourceDoesntExist)?;807808 let resource_info: RmrkResourceInfo = Self::decode_property(&resource_info)?;809810 ensure!(811 resource_info.pending_removal,812 <Error<T>>::ResourceNotPending813 );814815 <PalletNft<T>>::remove_token_aux_property(816 collection_id,817 nft_id,818 PropertyScope::Rmrk,819 resource_id_key,820 );821822 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {823 let base_id = resource.base;824825 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;826 }827828 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {829 nft_id: rmrk_nft_id,830 resource_id,831 });832833 Ok(())834 }835836 837 #[transactional]838 #[pallet::weight(<SelfWeightOf<T>>::set_property())]839 pub fn set_property(840 origin: OriginFor<T>,841 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,842 maybe_nft_id: Option<RmrkNftId>,843 key: RmrkKeyString,844 value: RmrkValueString,845 ) -> DispatchResult {846 let sender = ensure_signed(origin)?;847 let sender = T::CrossAccountId::from_sub(sender);848849 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;850 let collection =851 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;852 collection.check_is_external()?;853854 let budget = budget::Value::new(NESTING_BUDGET);855856 match maybe_nft_id {857 Some(nft_id) => {858 let token_id: TokenId = nft_id.into();859860 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;861 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;862863 <PalletNft<T>>::set_scoped_token_property(864 collection_id,865 token_id,866 PropertyScope::Rmrk,867 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,868 )?;869 }870 None => {871 let collection = Self::get_typed_nft_collection(872 collection_id,873 misc::CollectionType::Regular,874 )?;875876 Self::check_collection_owner(&collection, &sender)?;877878 <PalletCommon<T>>::set_scoped_collection_property(879 collection_id,880 PropertyScope::Rmrk,881 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,882 )?;883 }884 }885886 Self::deposit_event(Event::PropertySet {887 collection_id: rmrk_collection_id,888 maybe_nft_id,889 key,890 value,891 });892893 Ok(())894 }895896 897 #[transactional]898 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]899 pub fn set_priority(900 origin: OriginFor<T>,901 rmrk_collection_id: RmrkCollectionId,902 rmrk_nft_id: RmrkNftId,903 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,904 ) -> DispatchResult {905 let sender = ensure_signed(origin)?;906 let sender = T::CrossAccountId::from_sub(sender);907908 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;909 let nft_id = rmrk_nft_id.into();910911 let collection =912 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;913 collection.check_is_external()?;914915 let budget = budget::Value::new(NESTING_BUDGET);916917 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;918 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;919920 <PalletNft<T>>::set_scoped_token_property(921 collection_id,922 nft_id,923 PropertyScope::Rmrk,924 Self::rmrk_property(ResourcePriorities, &priorities.into_inner())?,925 )?;926927 Self::deposit_event(Event::<T>::PrioritySet {928 collection_id: rmrk_collection_id,929 nft_id: rmrk_nft_id,930 });931932 Ok(())933 }934935 936 #[transactional]937 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]938 pub fn add_basic_resource(939 origin: OriginFor<T>,940 rmrk_collection_id: RmrkCollectionId,941 nft_id: RmrkNftId,942 resource: RmrkBasicResource,943 ) -> DispatchResult {944 let sender = ensure_signed(origin.clone())?;945946 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;947 let collection =948 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;949 collection.check_is_external()?;950951 let resource_id = Self::resource_add(952 sender,953 collection_id,954 nft_id.into(),955 RmrkResourceTypes::Basic(resource),956 )?;957958 Self::deposit_event(Event::ResourceAdded {959 nft_id,960 resource_id,961 });962 Ok(())963 }964965 966 #[transactional]967 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]968 pub fn add_composable_resource(969 origin: OriginFor<T>,970 rmrk_collection_id: RmrkCollectionId,971 nft_id: RmrkNftId,972 resource: RmrkComposableResource,973 ) -> DispatchResult {974 let sender = ensure_signed(origin.clone())?;975976 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;977 let collection =978 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;979 collection.check_is_external()?;980981 let base_id = resource.base;982983 let resource_id = Self::resource_add(984 sender,985 collection_id,986 nft_id.into(),987 RmrkResourceTypes::Composable(resource),988 )?;989990 <PalletNft<T>>::try_mutate_token_aux_property(991 collection_id,992 nft_id.into(),993 PropertyScope::Rmrk,994 Self::rmrk_property_key(AssociatedBases)?,995 |value| -> DispatchResult {996 let mut bases: BasesMap = match value {997 Some(value) => Self::decode_property(value)?,998 None => BasesMap::new(),999 };10001001 *bases.entry(base_id).or_insert(0) += 1;10021003 *value = Some(Self::encode_property(&bases)?);1004 Ok(())1005 },1006 )?;10071008 Self::deposit_event(Event::ResourceAdded {1009 nft_id,1010 resource_id,1011 });1012 Ok(())1013 }10141015 1016 #[transactional]1017 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1018 pub fn add_slot_resource(1019 origin: OriginFor<T>,1020 rmrk_collection_id: RmrkCollectionId,1021 nft_id: RmrkNftId,1022 resource: RmrkSlotResource,1023 ) -> DispatchResult {1024 let sender = ensure_signed(origin.clone())?;10251026 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1027 let collection =1028 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1029 collection.check_is_external()?;10301031 let resource_id = Self::resource_add(1032 sender,1033 collection_id,1034 nft_id.into(),1035 RmrkResourceTypes::Slot(resource),1036 )?;10371038 Self::deposit_event(Event::ResourceAdded {1039 nft_id,1040 resource_id,1041 });1042 Ok(())1043 }10441045 1046 #[transactional]1047 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1048 pub fn remove_resource(1049 origin: OriginFor<T>,1050 rmrk_collection_id: RmrkCollectionId,1051 nft_id: RmrkNftId,1052 resource_id: RmrkResourceId,1053 ) -> DispatchResult {1054 let sender = ensure_signed(origin.clone())?;10551056 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1057 let collection =1058 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1059 collection.check_is_external()?;10601061 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;10621063 Self::deposit_event(Event::ResourceRemoval {1064 nft_id,1065 resource_id,1066 });1067 Ok(())1068 }1069 }1070}10711072impl<T: Config> Pallet<T> {1073 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1074 let key = rmrk_key.to_key::<T>()?;10751076 let scoped_key = PropertyScope::Rmrk1077 .apply(key)1078 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;10791080 Ok(scoped_key)1081 }10821083 1084 pub fn rmrk_property<E: Encode>(1085 rmrk_key: RmrkProperty,1086 value: &E,1087 ) -> Result<Property, DispatchError> {1088 let key = rmrk_key.to_key::<T>()?;10891090 let value = Self::encode_property(value)?;10911092 let property = Property { key, value };10931094 Ok(property)1095 }10961097 pub fn encode_property<E: Encode, S: Get<u32>>(1098 value: &E,1099 ) -> Result<BoundedBytes<S>, DispatchError> {1100 let value = value1101 .encode()1102 .try_into()1103 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;11041105 Ok(value)1106 }11071108 pub fn decode_property<D: Decode, S: Get<u32>>(1109 vec: &BoundedBytes<S>,1110 ) -> Result<D, DispatchError> {1111 vec.decode()1112 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1113 }11141115 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1116 where1117 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1118 {1119 vec.rebind()1120 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1121 }11221123 fn init_collection(1124 sender: T::CrossAccountId,1125 data: CreateCollectionData<T::AccountId>,1126 properties: impl Iterator<Item = Property>,1127 ) -> Result<CollectionId, DispatchError> {1128 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);11291130 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1131 return Err(<Error<T>>::NoAvailableCollectionId.into());1132 }11331134 <PalletCommon<T>>::set_scoped_collection_properties(1135 collection_id?,1136 PropertyScope::Rmrk,1137 properties,1138 )?;11391140 collection_id1141 }11421143 pub fn create_nft(1144 sender: &T::CrossAccountId,1145 owner: &T::CrossAccountId,1146 collection: &NonfungibleHandle<T>,1147 properties: impl Iterator<Item = Property>,1148 ) -> Result<TokenId, DispatchError> {1149 let data = CreateNftExData {1150 properties: BoundedVec::default(),1151 owner: owner.clone(),1152 };11531154 let budget = budget::Value::new(NESTING_BUDGET);11551156 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;11571158 let nft_id = <PalletNft<T>>::current_token_id(collection.id);11591160 <PalletNft<T>>::set_scoped_token_properties(1161 collection.id,1162 nft_id,1163 PropertyScope::Rmrk,1164 properties,1165 )?;11661167 Ok(nft_id)1168 }11691170 fn destroy_nft(1171 sender: T::CrossAccountId,1172 collection_id: CollectionId,1173 token_id: TokenId,1174 max_burns: u32,1175 error_if_not_owned: Error<T>,1176 ) -> DispatchResultWithPostInfo {1177 let collection =1178 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;11791180 let token_data =1181 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;11821183 let from = token_data.owner;11841185 let owner_check_budget = budget::Value::new(NESTING_BUDGET);11861187 ensure!(1188 <PalletStructure<T>>::check_indirectly_owned(1189 sender.clone(),1190 collection_id,1191 token_id,1192 None,1193 &owner_check_budget1194 )?,1195 error_if_not_owned,1196 );11971198 let burns_budget = budget::Value::new(max_burns);1199 let breadth_budget = budget::Value::new(max_burns);12001201 <PalletNft<T>>::burn_recursively(1202 &collection,1203 &from,1204 token_id,1205 &burns_budget,1206 &breadth_budget,1207 )1208 }12091210 fn insert_pending_child(1211 target: (CollectionId, TokenId),1212 child: (RmrkCollectionId, RmrkNftId),1213 ) -> DispatchResult {1214 Self::mutate_pending_child(target, |pending_children| {1215 pending_children.insert(child);1216 })1217 }12181219 fn remove_pending_child(1220 target: (CollectionId, TokenId),1221 child: (RmrkCollectionId, RmrkNftId),1222 ) -> DispatchResult {1223 Self::mutate_pending_child(target, |pending_children| {1224 pending_children.remove(&child);1225 })1226 }12271228 fn mutate_pending_child(1229 (target_collection_id, target_nft_id): (CollectionId, TokenId),1230 f: impl FnOnce(&mut PendingChildrenSet),1231 ) -> DispatchResult {1232 <PalletNft<T>>::try_mutate_token_aux_property(1233 target_collection_id,1234 target_nft_id,1235 PropertyScope::Rmrk,1236 Self::rmrk_property_key(PendingChildren)?,1237 |pending_children| -> DispatchResult {1238 let mut map = match pending_children {1239 Some(map) => Self::decode_property(map)?,1240 None => PendingChildrenSet::new(),1241 };12421243 f(&mut map);12441245 *pending_children = Some(Self::encode_property(&map)?);12461247 Ok(())1248 },1249 )1250 }12511252 fn iterate_pending_children(1253 collection_id: CollectionId,1254 nft_id: TokenId,1255 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1256 let property = <PalletNft<T>>::token_aux_property((1257 collection_id,1258 nft_id,1259 PropertyScope::Rmrk,1260 Self::rmrk_property_key(PendingChildren)?,1261 ));12621263 let pending_children = match property {1264 Some(map) => Self::decode_property(&map)?,1265 None => PendingChildrenSet::new(),1266 };12671268 Ok(pending_children.into_iter())1269 }12701271 fn acquire_next_resource_id(1272 collection_id: CollectionId,1273 nft_id: TokenId,1274 ) -> Result<RmrkResourceId, DispatchError> {1275 let resource_id: RmrkResourceId =1276 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;12771278 let next_id = resource_id1279 .checked_add(1)1280 .ok_or(<Error<T>>::NoAvailableResourceId)?;12811282 <PalletNft<T>>::set_scoped_token_property(1283 collection_id,1284 nft_id,1285 PropertyScope::Rmrk,1286 Self::rmrk_property(NextResourceId, &next_id)?,1287 )?;12881289 Ok(resource_id)1290 }12911292 fn resource_add(1293 sender: T::AccountId,1294 collection_id: CollectionId,1295 nft_id: TokenId,1296 resource: RmrkResourceTypes,1297 ) -> Result<RmrkResourceId, DispatchError> {1298 let collection =1299 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1300 ensure!(collection.owner == sender, Error::<T>::NoPermission);13011302 let sender = T::CrossAccountId::from_sub(sender);1303 let budget = budget::Value::new(NESTING_BUDGET);13041305 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1306 .map_err(Self::map_unique_err_to_proxy)?;13071308 let pending = sender != nft_owner;13091310 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;13111312 let resource_info = RmrkResourceInfo {1313 id,1314 resource,1315 pending,1316 pending_removal: false,1317 };13181319 <PalletNft<T>>::try_mutate_token_aux_property(1320 collection_id,1321 nft_id,1322 PropertyScope::Rmrk,1323 Self::rmrk_property_key(ResourceId(id))?,1324 |value| -> DispatchResult {1325 *value = Some(Self::encode_property(&resource_info)?);13261327 Ok(())1328 },1329 )?;13301331 Ok(id)1332 }13331334 fn resource_remove(1335 sender: T::AccountId,1336 collection_id: CollectionId,1337 nft_id: TokenId,1338 resource_id: RmrkResourceId,1339 ) -> DispatchResult {1340 let collection =1341 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1342 ensure!(collection.owner == sender, Error::<T>::NoPermission);13431344 let resource_id_key = Self::rmrk_property_key(ResourceId(resource_id))?;1345 let scope = PropertyScope::Rmrk;13461347 let resource = <PalletNft<T>>::token_aux_property((1348 collection_id,1349 nft_id,1350 scope,1351 resource_id_key.clone(),1352 ))1353 .ok_or(<Error<T>>::ResourceDoesntExist)?;13541355 let resource_info: RmrkResourceInfo = Self::decode_property(&resource)?;13561357 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1358 let topmost_owner =1359 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;13601361 let sender = T::CrossAccountId::from_sub(sender);1362 if topmost_owner == sender {1363 <PalletNft<T>>::remove_token_aux_property(1364 collection_id,1365 nft_id,1366 PropertyScope::Rmrk,1367 Self::rmrk_property_key(ResourceId(resource_id))?,1368 );13691370 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1371 let base_id = resource.base;13721373 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1374 }1375 } else {1376 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1377 res.pending_removal = true;13781379 Ok(())1380 })?;1381 }13821383 Ok(())1384 }13851386 fn remove_associated_base_id(1387 collection_id: CollectionId,1388 nft_id: TokenId,1389 base_id: RmrkBaseId,1390 ) -> DispatchResult {1391 <PalletNft<T>>::try_mutate_token_aux_property(1392 collection_id,1393 nft_id,1394 PropertyScope::Rmrk,1395 Self::rmrk_property_key(AssociatedBases)?,1396 |value| -> DispatchResult {1397 let mut bases: BasesMap = match value {1398 Some(value) => Self::decode_property(value)?,1399 None => BasesMap::new(),1400 };14011402 let remaining = bases.get(&base_id);14031404 if let Some(remaining) = remaining {1405 if let Some(0) | None = remaining.checked_sub(1) {1406 bases.remove(&base_id);1407 }1408 }14091410 *value = Some(Self::encode_property(&bases)?);1411 Ok(())1412 },1413 )1414 }14151416 fn try_mutate_resource_info(1417 collection_id: CollectionId,1418 nft_id: TokenId,1419 resource_id: RmrkResourceId,1420 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1421 ) -> DispatchResult {1422 <PalletNft<T>>::try_mutate_token_aux_property(1423 collection_id,1424 nft_id,1425 PropertyScope::Rmrk,1426 Self::rmrk_property_key(ResourceId(resource_id))?,1427 |value| match value {1428 Some(value) => {1429 let mut resource_info: RmrkResourceInfo = Self::decode_property(value)?;14301431 f(&mut resource_info)?;14321433 *value = Self::encode_property(&resource_info)?;14341435 Ok(())1436 }1437 None => Err(<Error<T>>::ResourceDoesntExist.into()),1438 },1439 )1440 }14411442 fn change_collection_owner(1443 collection_id: CollectionId,1444 collection_type: misc::CollectionType,1445 sender: T::AccountId,1446 new_owner: T::AccountId,1447 ) -> DispatchResult {1448 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1449 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;14501451 let mut collection = collection.into_inner();14521453 collection.owner = new_owner;1454 collection.save()1455 }14561457 pub fn check_collection_owner(1458 collection: &NonfungibleHandle<T>,1459 account: &T::CrossAccountId,1460 ) -> DispatchResult {1461 collection1462 .check_is_owner(account)1463 .map_err(Self::map_unique_err_to_proxy)1464 }14651466 pub fn last_collection_idx() -> RmrkCollectionId {1467 <CollectionIndex<T>>::get()1468 }14691470 pub fn unique_collection_id(1471 rmrk_collection_id: RmrkCollectionId,1472 ) -> Result<CollectionId, DispatchError> {1473 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1474 .map_err(|_| <Error<T>>::CollectionUnknown.into())1475 }14761477 pub fn rmrk_collection_id(1478 unique_collection_id: CollectionId,1479 ) -> Result<RmrkCollectionId, DispatchError> {1480 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1481 }14821483 pub fn get_nft_collection(1484 collection_id: CollectionId,1485 ) -> Result<NonfungibleHandle<T>, DispatchError> {1486 let collection = <CollectionHandle<T>>::try_get(collection_id)1487 .map_err(|_| <Error<T>>::CollectionUnknown)?;14881489 match collection.mode {1490 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1491 _ => Err(<Error<T>>::CollectionUnknown.into()),1492 }1493 }14941495 pub fn collection_exists(collection_id: CollectionId) -> bool {1496 <CollectionHandle<T>>::try_get(collection_id).is_ok()1497 }14981499 pub fn get_collection_property(1500 collection_id: CollectionId,1501 key: RmrkProperty,1502 ) -> Result<PropertyValue, DispatchError> {1503 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1504 .get(&Self::rmrk_property_key(key)?)1505 .ok_or(<Error<T>>::CollectionUnknown)?1506 .clone();15071508 Ok(collection_property)1509 }15101511 pub fn get_collection_property_decoded<V: Decode>(1512 collection_id: CollectionId,1513 key: RmrkProperty,1514 ) -> Result<V, DispatchError> {1515 Self::decode_property(&Self::get_collection_property(collection_id, key)?)1516 }15171518 pub fn get_collection_type(1519 collection_id: CollectionId,1520 ) -> Result<misc::CollectionType, DispatchError> {1521 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1522 if err != <Error<T>>::CollectionUnknown.into() {1523 <Error<T>>::CorruptedCollectionType.into()1524 } else {1525 err1526 }1527 })1528 }15291530 pub fn ensure_collection_type(1531 collection_id: CollectionId,1532 collection_type: misc::CollectionType,1533 ) -> DispatchResult {1534 let actual_type = Self::get_collection_type(collection_id)?;1535 ensure!(1536 actual_type == collection_type,1537 <CommonError<T>>::NoPermission1538 );15391540 Ok(())1541 }15421543 pub fn get_typed_nft_collection(1544 collection_id: CollectionId,1545 collection_type: misc::CollectionType,1546 ) -> Result<NonfungibleHandle<T>, DispatchError> {1547 Self::ensure_collection_type(collection_id, collection_type)?;15481549 Self::get_nft_collection(collection_id)1550 }15511552 pub fn get_typed_nft_collection_mapped(1553 rmrk_collection_id: RmrkCollectionId,1554 collection_type: misc::CollectionType,1555 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1556 let unique_collection_id = match collection_type {1557 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1558 _ => rmrk_collection_id.into(),1559 };15601561 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;15621563 Ok((collection, unique_collection_id))1564 }15651566 pub fn get_nft_property(1567 collection_id: CollectionId,1568 nft_id: TokenId,1569 key: RmrkProperty,1570 ) -> Result<PropertyValue, DispatchError> {1571 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1572 .get(&Self::rmrk_property_key(key)?)1573 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1574 .clone();15751576 Ok(nft_property)1577 }15781579 pub fn get_nft_property_decoded<V: Decode>(1580 collection_id: CollectionId,1581 nft_id: TokenId,1582 key: RmrkProperty,1583 ) -> Result<V, DispatchError> {1584 Self::decode_property(&Self::get_nft_property(collection_id, nft_id, key)?)1585 }15861587 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1588 <TokenData<T>>::contains_key((collection_id, nft_id))1589 }15901591 pub fn get_nft_type(1592 collection_id: CollectionId,1593 token_id: TokenId,1594 ) -> Result<NftType, DispatchError> {1595 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1596 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1597 }15981599 pub fn ensure_nft_type(1600 collection_id: CollectionId,1601 token_id: TokenId,1602 nft_type: NftType,1603 ) -> DispatchResult {1604 let actual_type = Self::get_nft_type(collection_id, token_id)?;1605 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);16061607 Ok(())1608 }16091610 pub fn ensure_nft_owner(1611 collection_id: CollectionId,1612 token_id: TokenId,1613 possible_owner: &T::CrossAccountId,1614 nesting_budget: &dyn budget::Budget,1615 ) -> DispatchResult {1616 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1617 possible_owner.clone(),1618 collection_id,1619 token_id,1620 None,1621 nesting_budget,1622 )1623 .map_err(Self::map_unique_err_to_proxy)?;16241625 ensure!(is_owned, <Error<T>>::NoPermission);16261627 Ok(())1628 }16291630 pub fn filter_user_properties<Key, Value, R, Mapper>(1631 collection_id: CollectionId,1632 token_id: Option<TokenId>,1633 filter_keys: Option<Vec<RmrkPropertyKey>>,1634 mapper: Mapper,1635 ) -> Result<Vec<R>, DispatchError>1636 where1637 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1638 Value: Decode + Default,1639 Mapper: Fn(Key, Value) -> R,1640 {1641 filter_keys1642 .map(|keys| {1643 let properties = keys1644 .into_iter()1645 .filter_map(|key| {1646 let key: Key = key.try_into().ok()?;16471648 let value = match token_id {1649 Some(token_id) => Self::get_nft_property_decoded(1650 collection_id,1651 token_id,1652 UserProperty(key.as_ref()),1653 ),1654 None => Self::get_collection_property_decoded(1655 collection_id,1656 UserProperty(key.as_ref()),1657 ),1658 }1659 .ok()?;16601661 Some(mapper(key, value))1662 })1663 .collect();16641665 Ok(properties)1666 })1667 .unwrap_or_else(|| {1668 let properties =1669 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();16701671 Ok(properties)1672 })1673 }16741675 pub fn iterate_user_properties<Key, Value, R, Mapper>(1676 collection_id: CollectionId,1677 token_id: Option<TokenId>,1678 mapper: Mapper,1679 ) -> Result<impl Iterator<Item = R>, DispatchError>1680 where1681 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1682 Value: Decode + Default,1683 Mapper: Fn(Key, Value) -> R,1684 {1685 let properties = match token_id {1686 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1687 None => <PalletCommon<T>>::collection_properties(collection_id),1688 };16891690 let properties = properties.into_iter().filter_map(move |(key, value)| {1691 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;16921693 let key: Key = key.to_vec().try_into().ok()?;1694 let value: Value = value.decode().ok()?;16951696 Some(mapper(key, value))1697 });16981699 Ok(properties)1700 }17011702 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1703 map_unique_err_to_proxy! {1704 match err {1705 CommonError::NoPermission => NoPermission,1706 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1707 CommonError::PublicMintingNotAllowed => NoPermission,1708 CommonError::TokenNotFound => NoAvailableNftId,1709 CommonError::ApprovedValueTooLow => NoPermission,1710 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1711 StructureError::TokenNotFound => NoAvailableNftId,1712 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1713 }1714 }1715 }1716}