123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111#![cfg_attr(not(feature = "std"), no_std)]112113use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};114use frame_system::{pallet_prelude::*, ensure_signed};115use sp_runtime::{DispatchError, Permill, traits::StaticLookup};116use sp_std::{117 vec::Vec,118 collections::{btree_set::BTreeSet, btree_map::BTreeMap},119};120use up_data_structs::{*, mapping::TokenAddressMapping};121use pallet_common::{122 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,123};124use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};125use pallet_structure::{Pallet as PalletStructure, Error as StructureError};126use pallet_evm::account::CrossAccountId;127use core::convert::AsRef;128129pub use pallet::*;130131#[cfg(feature = "runtime-benchmarks")]132pub mod benchmarking;133pub mod misc;134pub mod property;135pub mod rpc;136pub mod weights;137138pub type SelfWeightOf<T> = <T as Config>::WeightInfo;139140use weights::WeightInfo;141use misc::*;142pub use property::*;143144use RmrkProperty::*;145146147pub const NESTING_BUDGET: u32 = 5;148149type PendingTarget = (CollectionId, TokenId);150type PendingChild = (RmrkCollectionId, RmrkNftId);151type PendingChildrenSet = BTreeSet<PendingChild>;152153type BasesMap = BTreeMap<RmrkBaseId, u32>;154155#[frame_support::pallet]156pub mod pallet {157 use super::*;158 use pallet_evm::account;159160 #[pallet::config]161 pub trait Config:162 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config163 {164 165 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;166 167 168 type WeightInfo: WeightInfo;169 }170171 172 #[pallet::storage]173 #[pallet::getter(fn collection_index)]174 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;175176 177 #[pallet::storage]178 pub type UniqueCollectionId<T: Config> =179 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;180181 #[pallet::pallet]182 #[pallet::generate_store(pub(super) trait Store)]183 pub struct Pallet<T>(_);184185 #[pallet::event]186 #[pallet::generate_deposit(pub(super) fn deposit_event)]187 pub enum Event<T: Config> {188 CollectionCreated {189 issuer: T::AccountId,190 collection_id: RmrkCollectionId,191 },192 CollectionDestroyed {193 issuer: T::AccountId,194 collection_id: RmrkCollectionId,195 },196 IssuerChanged {197 old_issuer: T::AccountId,198 new_issuer: T::AccountId,199 collection_id: RmrkCollectionId,200 },201 CollectionLocked {202 issuer: T::AccountId,203 collection_id: RmrkCollectionId,204 },205 NftMinted {206 owner: T::AccountId,207 collection_id: RmrkCollectionId,208 nft_id: RmrkNftId,209 },210 NFTBurned {211 owner: T::AccountId,212 nft_id: RmrkNftId,213 },214 NFTSent {215 sender: T::AccountId,216 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,217 collection_id: RmrkCollectionId,218 nft_id: RmrkNftId,219 approval_required: bool,220 },221 NFTAccepted {222 sender: T::AccountId,223 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,224 collection_id: RmrkCollectionId,225 nft_id: RmrkNftId,226 },227 NFTRejected {228 sender: T::AccountId,229 collection_id: RmrkCollectionId,230 nft_id: RmrkNftId,231 },232 PropertySet {233 collection_id: RmrkCollectionId,234 maybe_nft_id: Option<RmrkNftId>,235 key: RmrkKeyString,236 value: RmrkValueString,237 },238 ResourceAdded {239 nft_id: RmrkNftId,240 resource_id: RmrkResourceId,241 },242 ResourceRemoval {243 nft_id: RmrkNftId,244 resource_id: RmrkResourceId,245 },246 ResourceAccepted {247 nft_id: RmrkNftId,248 resource_id: RmrkResourceId,249 },250 ResourceRemovalAccepted {251 nft_id: RmrkNftId,252 resource_id: RmrkResourceId,253 },254 PrioritySet {255 collection_id: RmrkCollectionId,256 nft_id: RmrkNftId,257 },258 }259260 #[pallet::error]261 pub enum Error<T> {262 263 264 CorruptedCollectionType,265 266 267 RmrkPropertyKeyIsTooLong,268 269 RmrkPropertyValueIsTooLong,270 271 RmrkPropertyIsNotFound,272 273 274 UnableToDecodeRmrkData,275276 277 278 CollectionNotEmpty,279 280 NoAvailableCollectionId,281 282 NoAvailableNftId,283 284 CollectionUnknown,285 286 NoPermission,287 288 NonTransferable,289 290 CollectionFullOrLocked,291 292 ResourceDoesntExist,293 294 295 CannotSendToDescendentOrSelf,296 297 CannotAcceptNonOwnedNft,298 299 CannotRejectNonOwnedNft,300 301 CannotRejectNonPendingNft,302 303 ResourceNotPending,304 305 NoAvailableResourceId,306 }307308 #[pallet::call]309 impl<T: Config> Pallet<T> {310 311312 313 314 315 316 317 318 319 320 321 322 #[transactional]323 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]324 pub fn create_collection(325 origin: OriginFor<T>,326 metadata: RmrkString,327 max: Option<u32>,328 symbol: RmrkCollectionSymbol,329 ) -> DispatchResult {330 let sender = ensure_signed(origin)?;331332 let limits = CollectionLimits {333 owner_can_transfer: Some(false),334 token_limit: max,335 ..Default::default()336 };337338 let data = CreateCollectionData {339 limits: Some(limits),340 token_prefix: symbol341 .into_inner()342 .try_into()343 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,344 permissions: Some(CollectionPermissions {345 nesting: Some(NestingPermissions {346 token_owner: true,347 collection_admin: false,348 restricted: None,349 #[cfg(feature = "runtime-benchmarks")]350 permissive: false,351 }),352 ..Default::default()353 }),354 ..Default::default()355 };356357 let unique_collection_id = Self::init_collection(358 T::CrossAccountId::from_sub(sender.clone()),359 data,360 [361 Self::encode_rmrk_property(Metadata, &metadata)?,362 Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,363 ]364 .into_iter(),365 )?;366 let rmrk_collection_id = <CollectionIndex<T>>::get();367368 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);369370 <PalletCommon<T>>::set_scoped_collection_property(371 unique_collection_id,372 RMRK_SCOPE,373 Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,374 )?;375376 <CollectionIndex<T>>::mutate(|n| *n += 1);377378 Self::deposit_event(Event::CollectionCreated {379 issuer: sender,380 collection_id: rmrk_collection_id,381 });382383 Ok(())384 }385386 387 388 389 390 391 392 393 394 395 #[transactional]396 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]397 pub fn destroy_collection(398 origin: OriginFor<T>,399 collection_id: RmrkCollectionId,400 ) -> DispatchResult {401 let sender = ensure_signed(origin)?;402 let cross_sender = T::CrossAccountId::from_sub(sender.clone());403404 let collection = Self::get_typed_nft_collection(405 Self::unique_collection_id(collection_id)?,406 misc::CollectionType::Regular,407 )?;408 collection.check_is_external()?;409410 <PalletNft<T>>::destroy_collection(collection, &cross_sender)411 .map_err(Self::map_unique_err_to_proxy)?;412413 Self::deposit_event(Event::CollectionDestroyed {414 issuer: sender,415 collection_id,416 });417418 Ok(())419 }420421 422 423 424 425 426 427 428 429 #[transactional]430 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]431 pub fn change_collection_issuer(432 origin: OriginFor<T>,433 collection_id: RmrkCollectionId,434 new_issuer: <T::Lookup as StaticLookup>::Source,435 ) -> DispatchResult {436 let sender = ensure_signed(origin)?;437438 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;439 collection.check_is_external()?;440441 let new_issuer = T::Lookup::lookup(new_issuer)?;442443 Self::change_collection_owner(444 Self::unique_collection_id(collection_id)?,445 misc::CollectionType::Regular,446 sender.clone(),447 new_issuer.clone(),448 )?;449450 Self::deposit_event(Event::IssuerChanged {451 old_issuer: sender,452 new_issuer,453 collection_id,454 });455456 Ok(())457 }458459 460 461 462 463 464 465 466 #[transactional]467 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]468 pub fn lock_collection(469 origin: OriginFor<T>,470 collection_id: RmrkCollectionId,471 ) -> DispatchResult {472 let sender = ensure_signed(origin)?;473 let cross_sender = T::CrossAccountId::from_sub(sender.clone());474475 let collection = Self::get_typed_nft_collection(476 Self::unique_collection_id(collection_id)?,477 misc::CollectionType::Regular,478 )?;479 collection.check_is_external()?;480481 Self::check_collection_owner(&collection, &cross_sender)?;482483 let token_count = collection.total_supply();484485 let mut collection = collection.into_inner();486 collection.limits.token_limit = Some(token_count);487 collection.save()?;488489 Self::deposit_event(Event::CollectionLocked {490 issuer: sender,491 collection_id,492 });493494 Ok(())495 }496497 498 499 500 501 502 503 504 505 506 507 508 509 510 #[transactional]511 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]512 pub fn mint_nft(513 origin: OriginFor<T>,514 owner: Option<T::AccountId>,515 collection_id: RmrkCollectionId,516 recipient: Option<T::AccountId>,517 royalty_amount: Option<Permill>,518 metadata: RmrkString,519 transferable: bool,520 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,521 ) -> DispatchResult {522 let sender = ensure_signed(origin)?;523 let cross_sender = T::CrossAccountId::from_sub(sender.clone());524525 let owner = owner.unwrap_or(sender.clone());526 let cross_owner = T::CrossAccountId::from_sub(owner.clone());527528 let collection = Self::get_typed_nft_collection(529 Self::unique_collection_id(collection_id)?,530 misc::CollectionType::Regular,531 )?;532 collection.check_is_external()?;533534 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {535 recipient: recipient.unwrap_or_else(|| owner.clone()),536 amount,537 });538539 let nft_id = Self::create_nft(540 &cross_sender,541 &cross_owner,542 &collection,543 [544 Self::encode_rmrk_property(TokenType, &NftType::Regular)?,545 Self::encode_rmrk_property(Transferable, &transferable)?,546 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,547 Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,548 Self::encode_rmrk_property(Metadata, &metadata)?,549 Self::encode_rmrk_property(Equipped, &false)?,550 Self::encode_rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,551 Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,552 Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,553 Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,554 ]555 .into_iter(),556 )557 .map_err(|err| match err {558 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),559 err => Self::map_unique_err_to_proxy(err),560 })?;561562 if let Some(resources) = resources {563 for resource in resources {564 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;565 }566 }567568 Self::deposit_event(Event::NftMinted {569 owner,570 collection_id,571 nft_id: nft_id.0,572 });573574 Ok(())575 }576577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 #[transactional]593 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]594 pub fn burn_nft(595 origin: OriginFor<T>,596 collection_id: RmrkCollectionId,597 nft_id: RmrkNftId,598 max_burns: u32,599 ) -> DispatchResult {600 let sender = ensure_signed(origin)?;601 let cross_sender = T::CrossAccountId::from_sub(sender.clone());602603 let collection = Self::get_typed_nft_collection(604 Self::unique_collection_id(collection_id)?,605 misc::CollectionType::Regular,606 )?;607 collection.check_is_external()?;608609 Self::destroy_nft(610 cross_sender,611 Self::unique_collection_id(collection_id)?,612 nft_id.into(),613 max_burns,614 <Error<T>>::NoPermission,615 )616 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;617618 Self::deposit_event(Event::NFTBurned {619 owner: sender,620 nft_id,621 });622623 Ok(())624 }625626 627 628 629 630 631 632 633 634 635 636 637 638 639 #[transactional]640 #[pallet::weight(<SelfWeightOf<T>>::send())]641 pub fn send(642 origin: OriginFor<T>,643 rmrk_collection_id: RmrkCollectionId,644 rmrk_nft_id: RmrkNftId,645 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,646 ) -> DispatchResult {647 let sender = ensure_signed(origin.clone())?;648 let cross_sender = T::CrossAccountId::from_sub(sender.clone());649650 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;651 let nft_id = rmrk_nft_id.into();652653 let collection =654 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;655 collection.check_is_external()?;656657 let token_data =658 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;659660 let from = token_data.owner;661662 ensure!(663 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,664 <Error<T>>::NonTransferable665 );666667 ensure!(668 Self::get_nft_property_decoded::<Option<PendingTarget>>(669 collection_id,670 nft_id,671 RmrkProperty::PendingNftAccept672 )?673 .is_none(),674 <Error<T>>::NoPermission675 );676677 let target_owner;678 let approval_required;679680 match new_owner {681 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {682 target_owner = T::CrossAccountId::from_sub(account_id.clone());683 approval_required = false;684 }685 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(686 target_collection_id,687 target_nft_id,688 ) => {689 let target_collection_id = Self::unique_collection_id(target_collection_id)?;690691 let target_nft_budget = budget::Value::new(NESTING_BUDGET);692693 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(694 target_collection_id,695 target_nft_id.into(),696 Some((collection_id, nft_id)),697 &target_nft_budget,698 )699 .map_err(Self::map_unique_err_to_proxy)?;700701 approval_required = cross_sender != target_nft_owner;702703 if approval_required {704 target_owner = target_nft_owner;705706 <PalletNft<T>>::set_scoped_token_property(707 collection.id,708 nft_id,709 RMRK_SCOPE,710 Self::encode_rmrk_property::<Option<PendingTarget>>(711 PendingNftAccept,712 &Some((target_collection_id, target_nft_id.into())),713 )?,714 )?;715716 Self::insert_pending_child(717 (target_collection_id, target_nft_id.into()),718 (rmrk_collection_id, rmrk_nft_id),719 )?;720 } else {721 target_owner = T::CrossTokenAddressMapping::token_to_address(722 target_collection_id,723 target_nft_id.into(),724 );725 }726 }727 }728729 let src_nft_budget = budget::Value::new(NESTING_BUDGET);730731 <PalletNft<T>>::transfer_from(732 &collection,733 &cross_sender,734 &from,735 &target_owner,736 nft_id,737 &src_nft_budget,738 )739 .map_err(Self::map_unique_err_to_proxy)?;740741 Self::deposit_event(Event::NFTSent {742 sender,743 recipient: new_owner,744 collection_id: rmrk_collection_id,745 nft_id: rmrk_nft_id,746 approval_required,747 });748749 Ok(())750 }751752 753 754 755 756 757 758 759 760 761 762 763 764 #[transactional]765 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]766 pub fn accept_nft(767 origin: OriginFor<T>,768 rmrk_collection_id: RmrkCollectionId,769 rmrk_nft_id: RmrkNftId,770 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,771 ) -> DispatchResult {772 let sender = ensure_signed(origin.clone())?;773 let cross_sender = T::CrossAccountId::from_sub(sender.clone());774775 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;776 let nft_id = rmrk_nft_id.into();777778 let collection =779 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;780 collection.check_is_external()?;781782 let new_cross_owner = match new_owner {783 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {784 T::CrossAccountId::from_sub(account_id.clone())785 }786 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(787 target_collection_id,788 target_nft_id,789 ) => {790 let target_collection_id = Self::unique_collection_id(target_collection_id)?;791792 T::CrossTokenAddressMapping::token_to_address(793 target_collection_id,794 TokenId(target_nft_id),795 )796 }797 };798799 let budget = budget::Value::new(NESTING_BUDGET);800801 <PalletNft<T>>::transfer(802 &collection,803 &cross_sender,804 &new_cross_owner,805 nft_id,806 &budget,807 )808 .map_err(|err| {809 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {810 <Error<T>>::CannotAcceptNonOwnedNft.into()811 } else {812 Self::map_unique_err_to_proxy(err)813 }814 })?;815816 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(817 collection_id,818 nft_id,819 RmrkProperty::PendingNftAccept,820 )?;821822 if let Some(pending_target) = pending_target {823 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;824825 <PalletNft<T>>::set_scoped_token_property(826 collection.id,827 nft_id,828 RMRK_SCOPE,829 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,830 )?;831 }832833 Self::deposit_event(Event::NFTAccepted {834 sender,835 recipient: new_owner,836 collection_id: rmrk_collection_id,837 nft_id: rmrk_nft_id,838 });839840 Ok(())841 }842843 844 845 846 847 848 849 850 851 852 853 854 #[transactional]855 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]856 pub fn reject_nft(857 origin: OriginFor<T>,858 rmrk_collection_id: RmrkCollectionId,859 rmrk_nft_id: RmrkNftId,860 ) -> DispatchResult {861 let sender = ensure_signed(origin)?;862 let cross_sender = T::CrossAccountId::from_sub(sender.clone());863864 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;865 let nft_id = rmrk_nft_id.into();866867 let collection =868 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;869 collection.check_is_external()?;870871 ensure!(872 <TokenData<T>>::get((collection_id, nft_id)).is_some(),873 <Error<T>>::NoAvailableNftId874 );875876 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(877 collection_id,878 nft_id,879 RmrkProperty::PendingNftAccept,880 )?;881882 match pending_target {883 Some(pending_target) => {884 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?885 }886 None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),887 }888889 Self::destroy_nft(890 cross_sender,891 collection_id,892 nft_id,893 NESTING_BUDGET,894 <Error<T>>::CannotRejectNonOwnedNft,895 )896 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;897898 Self::deposit_event(Event::NFTRejected {899 sender,900 collection_id: rmrk_collection_id,901 nft_id: rmrk_nft_id,902 });903904 Ok(())905 }906907 908 909 910 911 912 913 914 915 916 917 918 919 920 #[transactional]921 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]922 pub fn accept_resource(923 origin: OriginFor<T>,924 rmrk_collection_id: RmrkCollectionId,925 rmrk_nft_id: RmrkNftId,926 resource_id: RmrkResourceId,927 ) -> DispatchResult {928 let sender = ensure_signed(origin)?;929 let cross_sender = T::CrossAccountId::from_sub(sender);930931 let collection_id = Self::unique_collection_id(rmrk_collection_id)932 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;933 let collection =934 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;935 collection.check_is_external()?;936937 let nft_id = rmrk_nft_id.into();938939 let budget = budget::Value::new(NESTING_BUDGET);940941 let nft_owner =942 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)943 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;944945 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {946 ensure!(res.pending, <Error<T>>::ResourceNotPending);947 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);948949 res.pending = false;950951 Ok(())952 })?;953954 Self::deposit_event(Event::<T>::ResourceAccepted {955 nft_id: rmrk_nft_id,956 resource_id,957 });958959 Ok(())960 }961962 963 964 965 966 967 968 969 970 971 972 973 974 #[transactional]975 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]976 pub fn accept_resource_removal(977 origin: OriginFor<T>,978 rmrk_collection_id: RmrkCollectionId,979 rmrk_nft_id: RmrkNftId,980 resource_id: RmrkResourceId,981 ) -> DispatchResult {982 let sender = ensure_signed(origin)?;983 let cross_sender = T::CrossAccountId::from_sub(sender);984985 let collection_id = Self::unique_collection_id(rmrk_collection_id)986 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;987 let collection =988 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;989 collection.check_is_external()?;990991 let nft_id = rmrk_nft_id.into();992993 let budget = budget::Value::new(NESTING_BUDGET);994995 let nft_owner =996 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)997 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;998999 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10001001 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10021003 let resource_info = <PalletNft<T>>::token_aux_property((1004 collection_id,1005 nft_id,1006 RMRK_SCOPE,1007 resource_id_key.clone(),1008 ))1009 .ok_or(<Error<T>>::ResourceDoesntExist)?;10101011 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10121013 ensure!(1014 resource_info.pending_removal,1015 <Error<T>>::ResourceNotPending1016 );10171018 <PalletNft<T>>::remove_token_aux_property(1019 collection_id,1020 nft_id,1021 RMRK_SCOPE,1022 resource_id_key,1023 );10241025 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1026 let base_id = resource.base;10271028 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1029 }10301031 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1032 nft_id: rmrk_nft_id,1033 resource_id,1034 });10351036 Ok(())1037 }10381039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 #[transactional]1056 #[pallet::weight(<SelfWeightOf<T>>::set_property())]1057 pub fn set_property(1058 origin: OriginFor<T>,1059 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,1060 maybe_nft_id: Option<RmrkNftId>,1061 key: RmrkKeyString,1062 value: RmrkValueString,1063 ) -> DispatchResult {1064 let sender = ensure_signed(origin)?;1065 let sender = T::CrossAccountId::from_sub(sender);10661067 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1068 let collection =1069 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1070 collection.check_is_external()?;10711072 let budget = budget::Value::new(NESTING_BUDGET);10731074 match maybe_nft_id {1075 Some(nft_id) => {1076 let token_id: TokenId = nft_id.into();10771078 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1079 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;10801081 <PalletNft<T>>::set_scoped_token_property(1082 collection_id,1083 token_id,1084 RMRK_SCOPE,1085 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1086 )?;1087 }1088 None => {1089 let collection = Self::get_typed_nft_collection(1090 collection_id,1091 misc::CollectionType::Regular,1092 )?;10931094 Self::check_collection_owner(&collection, &sender)?;10951096 <PalletCommon<T>>::set_scoped_collection_property(1097 collection_id,1098 RMRK_SCOPE,1099 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1100 )?;1101 }1102 }11031104 Self::deposit_event(Event::PropertySet {1105 collection_id: rmrk_collection_id,1106 maybe_nft_id,1107 key,1108 value,1109 });11101111 Ok(())1112 }11131114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 #[transactional]1129 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]1130 pub fn set_priority(1131 origin: OriginFor<T>,1132 rmrk_collection_id: RmrkCollectionId,1133 rmrk_nft_id: RmrkNftId,1134 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1135 ) -> DispatchResult {1136 let sender = ensure_signed(origin)?;1137 let sender = T::CrossAccountId::from_sub(sender);11381139 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1140 let nft_id = rmrk_nft_id.into();11411142 let collection =1143 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1144 collection.check_is_external()?;11451146 let budget = budget::Value::new(NESTING_BUDGET);11471148 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1149 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;11501151 <PalletNft<T>>::set_scoped_token_property(1152 collection_id,1153 nft_id,1154 RMRK_SCOPE,1155 Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1156 )?;11571158 Self::deposit_event(Event::<T>::PrioritySet {1159 collection_id: rmrk_collection_id,1160 nft_id: rmrk_nft_id,1161 });11621163 Ok(())1164 }11651166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 #[transactional]1184 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1185 pub fn add_basic_resource(1186 origin: OriginFor<T>,1187 rmrk_collection_id: RmrkCollectionId,1188 nft_id: RmrkNftId,1189 resource: RmrkBasicResource,1190 ) -> DispatchResult {1191 let sender = ensure_signed(origin.clone())?;11921193 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1194 let collection =1195 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1196 collection.check_is_external()?;11971198 let resource_id = Self::resource_add(1199 sender,1200 collection_id,1201 nft_id.into(),1202 RmrkResourceTypes::Basic(resource),1203 )?;12041205 Self::deposit_event(Event::ResourceAdded {1206 nft_id,1207 resource_id,1208 });1209 Ok(())1210 }12111212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 #[transactional]1230 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1231 pub fn add_composable_resource(1232 origin: OriginFor<T>,1233 rmrk_collection_id: RmrkCollectionId,1234 nft_id: RmrkNftId,1235 resource: RmrkComposableResource,1236 ) -> DispatchResult {1237 let sender = ensure_signed(origin.clone())?;12381239 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1240 let collection =1241 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1242 collection.check_is_external()?;12431244 let base_id = resource.base;12451246 let resource_id = Self::resource_add(1247 sender,1248 collection_id,1249 nft_id.into(),1250 RmrkResourceTypes::Composable(resource),1251 )?;12521253 <PalletNft<T>>::try_mutate_token_aux_property(1254 collection_id,1255 nft_id.into(),1256 RMRK_SCOPE,1257 Self::get_scoped_property_key(AssociatedBases)?,1258 |value| -> DispatchResult {1259 let mut bases: BasesMap = match value {1260 Some(value) => Self::decode_property_value(value)?,1261 None => BasesMap::new(),1262 };12631264 *bases.entry(base_id).or_insert(0) += 1;12651266 *value = Some(Self::encode_property_value(&bases)?);1267 Ok(())1268 },1269 )?;12701271 Self::deposit_event(Event::ResourceAdded {1272 nft_id,1273 resource_id,1274 });1275 Ok(())1276 }12771278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 #[transactional]1296 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1297 pub fn add_slot_resource(1298 origin: OriginFor<T>,1299 rmrk_collection_id: RmrkCollectionId,1300 nft_id: RmrkNftId,1301 resource: RmrkSlotResource,1302 ) -> DispatchResult {1303 let sender = ensure_signed(origin.clone())?;13041305 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1306 let collection =1307 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1308 collection.check_is_external()?;13091310 let resource_id = Self::resource_add(1311 sender,1312 collection_id,1313 nft_id.into(),1314 RmrkResourceTypes::Slot(resource),1315 )?;13161317 Self::deposit_event(Event::ResourceAdded {1318 nft_id,1319 resource_id,1320 });1321 Ok(())1322 }13231324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 #[transactional]1337 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1338 pub fn remove_resource(1339 origin: OriginFor<T>,1340 rmrk_collection_id: RmrkCollectionId,1341 nft_id: RmrkNftId,1342 resource_id: RmrkResourceId,1343 ) -> DispatchResult {1344 let sender = ensure_signed(origin.clone())?;13451346 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1347 let collection =1348 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1349 collection.check_is_external()?;13501351 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13521353 Self::deposit_event(Event::ResourceRemoval {1354 nft_id,1355 resource_id,1356 });1357 Ok(())1358 }1359 }1360}13611362impl<T: Config> Pallet<T> {1363 1364 pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1365 let key = rmrk_key.to_key::<T>()?;13661367 let scoped_key = RMRK_SCOPE1368 .apply(key)1369 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;13701371 Ok(scoped_key)1372 }13731374 1375 1376 pub fn encode_rmrk_property<E: Encode>(1377 rmrk_key: RmrkProperty,1378 value: &E,1379 ) -> Result<Property, DispatchError> {1380 let key = rmrk_key.to_key::<T>()?;13811382 let value = Self::encode_property_value(value)?;13831384 let property = Property { key, value };13851386 Ok(property)1387 }13881389 1390 pub fn encode_property_value<E: Encode, S: Get<u32>>(1391 value: &E,1392 ) -> Result<BoundedBytes<S>, DispatchError> {1393 let value = value1394 .encode()1395 .try_into()1396 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;13971398 Ok(value)1399 }14001401 1402 pub fn decode_property_value<D: Decode, S: Get<u32>>(1403 vec: &BoundedBytes<S>,1404 ) -> Result<D, DispatchError> {1405 vec.decode()1406 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1407 }14081409 1410 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1411 where1412 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1413 {1414 vec.rebind()1415 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1416 }14171418 1419 1420 1421 fn init_collection(1422 sender: T::CrossAccountId,1423 data: CreateCollectionData<T::AccountId>,1424 properties: impl Iterator<Item = Property>,1425 ) -> Result<CollectionId, DispatchError> {1426 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);14271428 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1429 return Err(<Error<T>>::NoAvailableCollectionId.into());1430 }14311432 <PalletCommon<T>>::set_scoped_collection_properties(1433 collection_id?,1434 RMRK_SCOPE,1435 properties,1436 )?;14371438 collection_id1439 }14401441 1442 1443 1444 pub fn create_nft(1445 sender: &T::CrossAccountId,1446 owner: &T::CrossAccountId,1447 collection: &NonfungibleHandle<T>,1448 properties: impl Iterator<Item = Property>,1449 ) -> Result<TokenId, DispatchError> {1450 let data = CreateNftExData {1451 properties: BoundedVec::default(),1452 owner: owner.clone(),1453 };14541455 let budget = budget::Value::new(NESTING_BUDGET);14561457 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;14581459 let nft_id = <PalletNft<T>>::current_token_id(collection.id);14601461 <PalletNft<T>>::set_scoped_token_properties(1462 collection.id,1463 nft_id,1464 RMRK_SCOPE,1465 properties,1466 )?;14671468 Ok(nft_id)1469 }14701471 1472 1473 1474 fn destroy_nft(1475 sender: T::CrossAccountId,1476 collection_id: CollectionId,1477 token_id: TokenId,1478 max_burns: u32,1479 error_if_not_owned: Error<T>,1480 ) -> DispatchResultWithPostInfo {1481 let collection =1482 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;14831484 let token_data =1485 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;14861487 let from = token_data.owner;14881489 let owner_check_budget = budget::Value::new(NESTING_BUDGET);14901491 ensure!(1492 <PalletStructure<T>>::check_indirectly_owned(1493 sender.clone(),1494 collection_id,1495 token_id,1496 None,1497 &owner_check_budget1498 )?,1499 error_if_not_owned,1500 );15011502 let burns_budget = budget::Value::new(max_burns);1503 let breadth_budget = budget::Value::new(max_burns);15041505 <PalletNft<T>>::burn_recursively(1506 &collection,1507 &from,1508 token_id,1509 &burns_budget,1510 &breadth_budget,1511 )1512 }15131514 1515 fn insert_pending_child(1516 target: (CollectionId, TokenId),1517 child: (RmrkCollectionId, RmrkNftId),1518 ) -> DispatchResult {1519 Self::mutate_pending_children(target, |pending_children| {1520 pending_children.insert(child);1521 })1522 }15231524 1525 fn remove_pending_child(1526 target: (CollectionId, TokenId),1527 child: (RmrkCollectionId, RmrkNftId),1528 ) -> DispatchResult {1529 Self::mutate_pending_children(target, |pending_children| {1530 pending_children.remove(&child);1531 })1532 }15331534 1535 1536 fn mutate_pending_children(1537 (target_collection_id, target_nft_id): (CollectionId, TokenId),1538 f: impl FnOnce(&mut PendingChildrenSet),1539 ) -> DispatchResult {1540 <PalletNft<T>>::try_mutate_token_aux_property(1541 target_collection_id,1542 target_nft_id,1543 RMRK_SCOPE,1544 Self::get_scoped_property_key(PendingChildren)?,1545 |pending_children| -> DispatchResult {1546 let mut map = match pending_children {1547 Some(map) => Self::decode_property_value(map)?,1548 None => PendingChildrenSet::new(),1549 };15501551 f(&mut map);15521553 *pending_children = Some(Self::encode_property_value(&map)?);15541555 Ok(())1556 },1557 )1558 }15591560 1561 1562 fn iterate_pending_children(1563 collection_id: CollectionId,1564 nft_id: TokenId,1565 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1566 let property = <PalletNft<T>>::token_aux_property((1567 collection_id,1568 nft_id,1569 RMRK_SCOPE,1570 Self::get_scoped_property_key(PendingChildren)?,1571 ));15721573 let pending_children = match property {1574 Some(map) => Self::decode_property_value(&map)?,1575 None => PendingChildrenSet::new(),1576 };15771578 Ok(pending_children.into_iter())1579 }15801581 1582 1583 fn acquire_next_resource_id(1584 collection_id: CollectionId,1585 nft_id: TokenId,1586 ) -> Result<RmrkResourceId, DispatchError> {1587 let resource_id: RmrkResourceId =1588 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;15891590 let next_id = resource_id1591 .checked_add(1)1592 .ok_or(<Error<T>>::NoAvailableResourceId)?;15931594 <PalletNft<T>>::set_scoped_token_property(1595 collection_id,1596 nft_id,1597 RMRK_SCOPE,1598 Self::encode_rmrk_property(NextResourceId, &next_id)?,1599 )?;16001601 Ok(resource_id)1602 }16031604 1605 1606 fn resource_add(1607 sender: T::AccountId,1608 collection_id: CollectionId,1609 nft_id: TokenId,1610 resource: RmrkResourceTypes,1611 ) -> Result<RmrkResourceId, DispatchError> {1612 let collection =1613 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1614 ensure!(collection.owner == sender, Error::<T>::NoPermission);16151616 let sender = T::CrossAccountId::from_sub(sender);1617 let budget = budget::Value::new(NESTING_BUDGET);16181619 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1620 .map_err(Self::map_unique_err_to_proxy)?;16211622 let pending = sender != nft_owner;16231624 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16251626 let resource_info = RmrkResourceInfo {1627 id,1628 resource,1629 pending,1630 pending_removal: false,1631 };16321633 <PalletNft<T>>::try_mutate_token_aux_property(1634 collection_id,1635 nft_id,1636 RMRK_SCOPE,1637 Self::get_scoped_property_key(ResourceId(id))?,1638 |value| -> DispatchResult {1639 *value = Some(Self::encode_property_value(&resource_info)?);16401641 Ok(())1642 },1643 )?;16441645 Ok(id)1646 }16471648 1649 1650 fn resource_remove(1651 sender: T::AccountId,1652 collection_id: CollectionId,1653 nft_id: TokenId,1654 resource_id: RmrkResourceId,1655 ) -> DispatchResult {1656 let collection =1657 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1658 ensure!(collection.owner == sender, Error::<T>::NoPermission);16591660 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16611662 let resource = <PalletNft<T>>::token_aux_property((1663 collection_id,1664 nft_id,1665 RMRK_SCOPE,1666 resource_id_key.clone(),1667 ))1668 .ok_or(<Error<T>>::ResourceDoesntExist)?;16691670 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;16711672 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1673 let topmost_owner =1674 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;16751676 let sender = T::CrossAccountId::from_sub(sender);1677 if topmost_owner == sender {1678 <PalletNft<T>>::remove_token_aux_property(1679 collection_id,1680 nft_id,1681 RMRK_SCOPE,1682 Self::get_scoped_property_key(ResourceId(resource_id))?,1683 );16841685 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1686 let base_id = resource.base;16871688 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1689 }1690 } else {1691 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1692 res.pending_removal = true;16931694 Ok(())1695 })?;1696 }16971698 Ok(())1699 }17001701 1702 1703 fn remove_associated_base_id(1704 collection_id: CollectionId,1705 nft_id: TokenId,1706 base_id: RmrkBaseId,1707 ) -> DispatchResult {1708 <PalletNft<T>>::try_mutate_token_aux_property(1709 collection_id,1710 nft_id,1711 RMRK_SCOPE,1712 Self::get_scoped_property_key(AssociatedBases)?,1713 |value| -> DispatchResult {1714 let mut bases: BasesMap = match value {1715 Some(value) => Self::decode_property_value(value)?,1716 None => BasesMap::new(),1717 };17181719 let remaining = bases.get(&base_id);17201721 if let Some(remaining) = remaining {1722 if let Some(0) | None = remaining.checked_sub(1) {1723 bases.remove(&base_id);1724 }1725 }17261727 *value = Some(Self::encode_property_value(&bases)?);1728 Ok(())1729 },1730 )1731 }17321733 1734 fn try_mutate_resource_info(1735 collection_id: CollectionId,1736 nft_id: TokenId,1737 resource_id: RmrkResourceId,1738 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1739 ) -> DispatchResult {1740 <PalletNft<T>>::try_mutate_token_aux_property(1741 collection_id,1742 nft_id,1743 RMRK_SCOPE,1744 Self::get_scoped_property_key(ResourceId(resource_id))?,1745 |value| match value {1746 Some(value) => {1747 let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17481749 f(&mut resource_info)?;17501751 *value = Self::encode_property_value(&resource_info)?;17521753 Ok(())1754 }1755 None => Err(<Error<T>>::ResourceDoesntExist.into()),1756 },1757 )1758 }17591760 1761 fn change_collection_owner(1762 collection_id: CollectionId,1763 collection_type: misc::CollectionType,1764 sender: T::AccountId,1765 new_owner: T::AccountId,1766 ) -> DispatchResult {1767 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1768 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;17691770 let mut collection = collection.into_inner();17711772 collection.owner = new_owner;1773 collection.save()1774 }17751776 1777 pub fn check_collection_owner(1778 collection: &NonfungibleHandle<T>,1779 account: &T::CrossAccountId,1780 ) -> DispatchResult {1781 collection1782 .check_is_owner(account)1783 .map_err(Self::map_unique_err_to_proxy)1784 }17851786 1787 pub fn last_collection_idx() -> RmrkCollectionId {1788 <CollectionIndex<T>>::get()1789 }17901791 1792 pub fn unique_collection_id(1793 rmrk_collection_id: RmrkCollectionId,1794 ) -> Result<CollectionId, DispatchError> {1795 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1796 .map_err(|_| <Error<T>>::CollectionUnknown.into())1797 }17981799 1800 pub fn rmrk_collection_id(1801 unique_collection_id: CollectionId,1802 ) -> Result<RmrkCollectionId, DispatchError> {1803 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1804 }18051806 1807 pub fn get_nft_collection(1808 collection_id: CollectionId,1809 ) -> Result<NonfungibleHandle<T>, DispatchError> {1810 let collection = <CollectionHandle<T>>::try_get(collection_id)1811 .map_err(|_| <Error<T>>::CollectionUnknown)?;18121813 match collection.mode {1814 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1815 _ => Err(<Error<T>>::CollectionUnknown.into()),1816 }1817 }18181819 1820 pub fn collection_exists(collection_id: CollectionId) -> bool {1821 <CollectionHandle<T>>::try_get(collection_id).is_ok()1822 }18231824 1825 pub fn get_collection_property(1826 collection_id: CollectionId,1827 key: RmrkProperty,1828 ) -> Result<PropertyValue, DispatchError> {1829 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1830 .get(&Self::get_scoped_property_key(key)?)1831 .ok_or(<Error<T>>::CollectionUnknown)?1832 .clone();18331834 Ok(collection_property)1835 }18361837 1838 pub fn get_collection_property_decoded<V: Decode>(1839 collection_id: CollectionId,1840 key: RmrkProperty,1841 ) -> Result<V, DispatchError> {1842 Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1843 }18441845 1846 1847 1848 pub fn get_collection_type(1849 collection_id: CollectionId,1850 ) -> Result<misc::CollectionType, DispatchError> {1851 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1852 if err != <Error<T>>::CollectionUnknown.into() {1853 <Error<T>>::CorruptedCollectionType.into()1854 } else {1855 err1856 }1857 })1858 }18591860 1861 1862 pub fn ensure_collection_type(1863 collection_id: CollectionId,1864 collection_type: misc::CollectionType,1865 ) -> DispatchResult {1866 let actual_type = Self::get_collection_type(collection_id)?;1867 ensure!(1868 actual_type == collection_type,1869 <CommonError<T>>::NoPermission1870 );18711872 Ok(())1873 }18741875 1876 pub fn get_typed_nft_collection(1877 collection_id: CollectionId,1878 collection_type: misc::CollectionType,1879 ) -> Result<NonfungibleHandle<T>, DispatchError> {1880 Self::ensure_collection_type(collection_id, collection_type)?;18811882 Self::get_nft_collection(collection_id)1883 }18841885 1886 1887 pub fn get_typed_nft_collection_mapped(1888 rmrk_collection_id: RmrkCollectionId,1889 collection_type: misc::CollectionType,1890 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1891 let unique_collection_id = match collection_type {1892 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1893 _ => rmrk_collection_id.into(),1894 };18951896 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;18971898 Ok((collection, unique_collection_id))1899 }19001901 1902 pub fn get_nft_property(1903 collection_id: CollectionId,1904 nft_id: TokenId,1905 key: RmrkProperty,1906 ) -> Result<PropertyValue, DispatchError> {1907 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1908 .get(&Self::get_scoped_property_key(key)?)1909 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1910 .clone();19111912 Ok(nft_property)1913 }19141915 1916 pub fn get_nft_property_decoded<V: Decode>(1917 collection_id: CollectionId,1918 nft_id: TokenId,1919 key: RmrkProperty,1920 ) -> Result<V, DispatchError> {1921 Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1922 }19231924 1925 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1926 <TokenData<T>>::contains_key((collection_id, nft_id))1927 }19281929 1930 1931 1932 pub fn get_nft_type(1933 collection_id: CollectionId,1934 token_id: TokenId,1935 ) -> Result<NftType, DispatchError> {1936 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1937 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1938 }19391940 1941 pub fn ensure_nft_type(1942 collection_id: CollectionId,1943 token_id: TokenId,1944 nft_type: NftType,1945 ) -> DispatchResult {1946 let actual_type = Self::get_nft_type(collection_id, token_id)?;1947 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19481949 Ok(())1950 }19511952 1953 1954 pub fn ensure_nft_owner(1955 collection_id: CollectionId,1956 token_id: TokenId,1957 possible_owner: &T::CrossAccountId,1958 nesting_budget: &dyn budget::Budget,1959 ) -> DispatchResult {1960 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1961 possible_owner.clone(),1962 collection_id,1963 token_id,1964 None,1965 nesting_budget,1966 )1967 .map_err(Self::map_unique_err_to_proxy)?;19681969 ensure!(is_owned, <Error<T>>::NoPermission);19701971 Ok(())1972 }19731974 1975 1976 pub fn filter_user_properties<Key, Value, R, Mapper>(1977 collection_id: CollectionId,1978 token_id: Option<TokenId>,1979 filter_keys: Option<Vec<RmrkPropertyKey>>,1980 mapper: Mapper,1981 ) -> Result<Vec<R>, DispatchError>1982 where1983 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1984 Value: Decode + Default,1985 Mapper: Fn(Key, Value) -> R,1986 {1987 filter_keys1988 .map(|keys| {1989 let properties = keys1990 .into_iter()1991 .filter_map(|key| {1992 let key: Key = key.try_into().ok()?;19931994 let value = match token_id {1995 Some(token_id) => Self::get_nft_property_decoded(1996 collection_id,1997 token_id,1998 UserProperty(key.as_ref()),1999 ),2000 None => Self::get_collection_property_decoded(2001 collection_id,2002 UserProperty(key.as_ref()),2003 ),2004 }2005 .ok()?;20062007 Some(mapper(key, value))2008 })2009 .collect();20102011 Ok(properties)2012 })2013 .unwrap_or_else(|| {2014 let properties =2015 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20162017 Ok(properties)2018 })2019 }20202021 2022 2023 pub fn iterate_user_properties<Key, Value, R, Mapper>(2024 collection_id: CollectionId,2025 token_id: Option<TokenId>,2026 mapper: Mapper,2027 ) -> Result<impl Iterator<Item = R>, DispatchError>2028 where2029 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2030 Value: Decode + Default,2031 Mapper: Fn(Key, Value) -> R,2032 {2033 let properties = match token_id {2034 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2035 None => <PalletCommon<T>>::collection_properties(collection_id),2036 };20372038 let properties = properties.into_iter().filter_map(move |(key, value)| {2039 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20402041 let key: Key = key.to_vec().try_into().ok()?;2042 let value: Value = value.decode().ok()?;20432044 Some(mapper(key, value))2045 });20462047 Ok(properties)2048 }20492050 2051 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2052 map_unique_err_to_proxy! {2053 match err {2054 CommonError::NoPermission => NoPermission,2055 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2056 CommonError::PublicMintingNotAllowed => NoPermission,2057 CommonError::TokenNotFound => NoAvailableNftId,2058 CommonError::ApprovedValueTooLow => NoPermission,2059 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2060 StructureError::TokenNotFound => NoAvailableNftId,2061 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2062 }2063 }2064 }2065}