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>;166167 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(collection.id, nft_id, RMRK_SCOPE, properties)?;14621463 Ok(nft_id)1464 }14651466 1467 1468 1469 fn destroy_nft(1470 sender: T::CrossAccountId,1471 collection_id: CollectionId,1472 token_id: TokenId,1473 max_burns: u32,1474 error_if_not_owned: Error<T>,1475 ) -> DispatchResultWithPostInfo {1476 let collection =1477 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;14781479 let token_data =1480 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;14811482 let from = token_data.owner;14831484 let owner_check_budget = budget::Value::new(NESTING_BUDGET);14851486 ensure!(1487 <PalletStructure<T>>::check_indirectly_owned(1488 sender.clone(),1489 collection_id,1490 token_id,1491 None,1492 &owner_check_budget1493 )?,1494 error_if_not_owned,1495 );14961497 let burns_budget = budget::Value::new(max_burns);1498 let breadth_budget = budget::Value::new(max_burns);14991500 <PalletNft<T>>::burn_recursively(1501 &collection,1502 &from,1503 token_id,1504 &burns_budget,1505 &breadth_budget,1506 )1507 }15081509 1510 fn insert_pending_child(1511 target: (CollectionId, TokenId),1512 child: (RmrkCollectionId, RmrkNftId),1513 ) -> DispatchResult {1514 Self::mutate_pending_children(target, |pending_children| {1515 pending_children.insert(child);1516 })1517 }15181519 1520 fn remove_pending_child(1521 target: (CollectionId, TokenId),1522 child: (RmrkCollectionId, RmrkNftId),1523 ) -> DispatchResult {1524 Self::mutate_pending_children(target, |pending_children| {1525 pending_children.remove(&child);1526 })1527 }15281529 1530 1531 fn mutate_pending_children(1532 (target_collection_id, target_nft_id): (CollectionId, TokenId),1533 f: impl FnOnce(&mut PendingChildrenSet),1534 ) -> DispatchResult {1535 <PalletNft<T>>::try_mutate_token_aux_property(1536 target_collection_id,1537 target_nft_id,1538 RMRK_SCOPE,1539 Self::get_scoped_property_key(PendingChildren)?,1540 |pending_children| -> DispatchResult {1541 let mut map = match pending_children {1542 Some(map) => Self::decode_property_value(map)?,1543 None => PendingChildrenSet::new(),1544 };15451546 f(&mut map);15471548 *pending_children = Some(Self::encode_property_value(&map)?);15491550 Ok(())1551 },1552 )1553 }15541555 1556 1557 fn iterate_pending_children(1558 collection_id: CollectionId,1559 nft_id: TokenId,1560 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1561 let property = <PalletNft<T>>::token_aux_property((1562 collection_id,1563 nft_id,1564 RMRK_SCOPE,1565 Self::get_scoped_property_key(PendingChildren)?,1566 ));15671568 let pending_children = match property {1569 Some(map) => Self::decode_property_value(&map)?,1570 None => PendingChildrenSet::new(),1571 };15721573 Ok(pending_children.into_iter())1574 }15751576 1577 1578 fn acquire_next_resource_id(1579 collection_id: CollectionId,1580 nft_id: TokenId,1581 ) -> Result<RmrkResourceId, DispatchError> {1582 let resource_id: RmrkResourceId =1583 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;15841585 let next_id = resource_id1586 .checked_add(1)1587 .ok_or(<Error<T>>::NoAvailableResourceId)?;15881589 <PalletNft<T>>::set_scoped_token_property(1590 collection_id,1591 nft_id,1592 RMRK_SCOPE,1593 Self::encode_rmrk_property(NextResourceId, &next_id)?,1594 )?;15951596 Ok(resource_id)1597 }15981599 1600 1601 fn resource_add(1602 sender: T::AccountId,1603 collection_id: CollectionId,1604 nft_id: TokenId,1605 resource: RmrkResourceTypes,1606 ) -> Result<RmrkResourceId, DispatchError> {1607 let collection =1608 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1609 ensure!(collection.owner == sender, Error::<T>::NoPermission);16101611 let sender = T::CrossAccountId::from_sub(sender);1612 let budget = budget::Value::new(NESTING_BUDGET);16131614 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1615 .map_err(Self::map_unique_err_to_proxy)?;16161617 let pending = sender != nft_owner;16181619 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16201621 let resource_info = RmrkResourceInfo {1622 id,1623 resource,1624 pending,1625 pending_removal: false,1626 };16271628 <PalletNft<T>>::try_mutate_token_aux_property(1629 collection_id,1630 nft_id,1631 RMRK_SCOPE,1632 Self::get_scoped_property_key(ResourceId(id))?,1633 |value| -> DispatchResult {1634 *value = Some(Self::encode_property_value(&resource_info)?);16351636 Ok(())1637 },1638 )?;16391640 Ok(id)1641 }16421643 1644 1645 fn resource_remove(1646 sender: T::AccountId,1647 collection_id: CollectionId,1648 nft_id: TokenId,1649 resource_id: RmrkResourceId,1650 ) -> DispatchResult {1651 let collection =1652 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1653 ensure!(collection.owner == sender, Error::<T>::NoPermission);16541655 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16561657 let resource = <PalletNft<T>>::token_aux_property((1658 collection_id,1659 nft_id,1660 RMRK_SCOPE,1661 resource_id_key.clone(),1662 ))1663 .ok_or(<Error<T>>::ResourceDoesntExist)?;16641665 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;16661667 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1668 let topmost_owner =1669 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;16701671 let sender = T::CrossAccountId::from_sub(sender);1672 if topmost_owner == sender {1673 <PalletNft<T>>::remove_token_aux_property(1674 collection_id,1675 nft_id,1676 RMRK_SCOPE,1677 Self::get_scoped_property_key(ResourceId(resource_id))?,1678 );16791680 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1681 let base_id = resource.base;16821683 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1684 }1685 } else {1686 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1687 res.pending_removal = true;16881689 Ok(())1690 })?;1691 }16921693 Ok(())1694 }16951696 1697 1698 fn remove_associated_base_id(1699 collection_id: CollectionId,1700 nft_id: TokenId,1701 base_id: RmrkBaseId,1702 ) -> DispatchResult {1703 <PalletNft<T>>::try_mutate_token_aux_property(1704 collection_id,1705 nft_id,1706 RMRK_SCOPE,1707 Self::get_scoped_property_key(AssociatedBases)?,1708 |value| -> DispatchResult {1709 let mut bases: BasesMap = match value {1710 Some(value) => Self::decode_property_value(value)?,1711 None => BasesMap::new(),1712 };17131714 let remaining = bases.get(&base_id);17151716 if let Some(remaining) = remaining {1717 if let Some(0) | None = remaining.checked_sub(1) {1718 bases.remove(&base_id);1719 }1720 }17211722 *value = Some(Self::encode_property_value(&bases)?);1723 Ok(())1724 },1725 )1726 }17271728 1729 fn try_mutate_resource_info(1730 collection_id: CollectionId,1731 nft_id: TokenId,1732 resource_id: RmrkResourceId,1733 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1734 ) -> DispatchResult {1735 <PalletNft<T>>::try_mutate_token_aux_property(1736 collection_id,1737 nft_id,1738 RMRK_SCOPE,1739 Self::get_scoped_property_key(ResourceId(resource_id))?,1740 |value| match value {1741 Some(value) => {1742 let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17431744 f(&mut resource_info)?;17451746 *value = Self::encode_property_value(&resource_info)?;17471748 Ok(())1749 }1750 None => Err(<Error<T>>::ResourceDoesntExist.into()),1751 },1752 )1753 }17541755 1756 fn change_collection_owner(1757 collection_id: CollectionId,1758 collection_type: misc::CollectionType,1759 sender: T::AccountId,1760 new_owner: T::AccountId,1761 ) -> DispatchResult {1762 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1763 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;17641765 let mut collection = collection.into_inner();17661767 collection.owner = new_owner;1768 collection.save()1769 }17701771 1772 pub fn check_collection_owner(1773 collection: &NonfungibleHandle<T>,1774 account: &T::CrossAccountId,1775 ) -> DispatchResult {1776 collection1777 .check_is_owner(account)1778 .map_err(Self::map_unique_err_to_proxy)1779 }17801781 1782 pub fn last_collection_idx() -> RmrkCollectionId {1783 <CollectionIndex<T>>::get()1784 }17851786 1787 pub fn unique_collection_id(1788 rmrk_collection_id: RmrkCollectionId,1789 ) -> Result<CollectionId, DispatchError> {1790 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1791 .map_err(|_| <Error<T>>::CollectionUnknown.into())1792 }17931794 1795 pub fn rmrk_collection_id(1796 unique_collection_id: CollectionId,1797 ) -> Result<RmrkCollectionId, DispatchError> {1798 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1799 }18001801 1802 pub fn get_nft_collection(1803 collection_id: CollectionId,1804 ) -> Result<NonfungibleHandle<T>, DispatchError> {1805 let collection = <CollectionHandle<T>>::try_get(collection_id)1806 .map_err(|_| <Error<T>>::CollectionUnknown)?;18071808 match collection.mode {1809 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1810 _ => Err(<Error<T>>::CollectionUnknown.into()),1811 }1812 }18131814 1815 pub fn collection_exists(collection_id: CollectionId) -> bool {1816 <CollectionHandle<T>>::try_get(collection_id).is_ok()1817 }18181819 1820 pub fn get_collection_property(1821 collection_id: CollectionId,1822 key: RmrkProperty,1823 ) -> Result<PropertyValue, DispatchError> {1824 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1825 .get(&Self::get_scoped_property_key(key)?)1826 .ok_or(<Error<T>>::CollectionUnknown)?1827 .clone();18281829 Ok(collection_property)1830 }18311832 1833 pub fn get_collection_property_decoded<V: Decode>(1834 collection_id: CollectionId,1835 key: RmrkProperty,1836 ) -> Result<V, DispatchError> {1837 Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1838 }18391840 1841 1842 1843 pub fn get_collection_type(1844 collection_id: CollectionId,1845 ) -> Result<misc::CollectionType, DispatchError> {1846 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1847 if err != <Error<T>>::CollectionUnknown.into() {1848 <Error<T>>::CorruptedCollectionType.into()1849 } else {1850 err1851 }1852 })1853 }18541855 1856 1857 pub fn ensure_collection_type(1858 collection_id: CollectionId,1859 collection_type: misc::CollectionType,1860 ) -> DispatchResult {1861 let actual_type = Self::get_collection_type(collection_id)?;1862 ensure!(1863 actual_type == collection_type,1864 <CommonError<T>>::NoPermission1865 );18661867 Ok(())1868 }18691870 1871 pub fn get_typed_nft_collection(1872 collection_id: CollectionId,1873 collection_type: misc::CollectionType,1874 ) -> Result<NonfungibleHandle<T>, DispatchError> {1875 Self::ensure_collection_type(collection_id, collection_type)?;18761877 Self::get_nft_collection(collection_id)1878 }18791880 1881 1882 pub fn get_typed_nft_collection_mapped(1883 rmrk_collection_id: RmrkCollectionId,1884 collection_type: misc::CollectionType,1885 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1886 let unique_collection_id = match collection_type {1887 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1888 _ => rmrk_collection_id.into(),1889 };18901891 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;18921893 Ok((collection, unique_collection_id))1894 }18951896 1897 pub fn get_nft_property(1898 collection_id: CollectionId,1899 nft_id: TokenId,1900 key: RmrkProperty,1901 ) -> Result<PropertyValue, DispatchError> {1902 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1903 .get(&Self::get_scoped_property_key(key)?)1904 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1905 .clone();19061907 Ok(nft_property)1908 }19091910 1911 pub fn get_nft_property_decoded<V: Decode>(1912 collection_id: CollectionId,1913 nft_id: TokenId,1914 key: RmrkProperty,1915 ) -> Result<V, DispatchError> {1916 Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1917 }19181919 1920 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1921 <TokenData<T>>::contains_key((collection_id, nft_id))1922 }19231924 1925 1926 1927 pub fn get_nft_type(1928 collection_id: CollectionId,1929 token_id: TokenId,1930 ) -> Result<NftType, DispatchError> {1931 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1932 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1933 }19341935 1936 pub fn ensure_nft_type(1937 collection_id: CollectionId,1938 token_id: TokenId,1939 nft_type: NftType,1940 ) -> DispatchResult {1941 let actual_type = Self::get_nft_type(collection_id, token_id)?;1942 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19431944 Ok(())1945 }19461947 1948 1949 pub fn ensure_nft_owner(1950 collection_id: CollectionId,1951 token_id: TokenId,1952 possible_owner: &T::CrossAccountId,1953 nesting_budget: &dyn budget::Budget,1954 ) -> DispatchResult {1955 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1956 possible_owner.clone(),1957 collection_id,1958 token_id,1959 None,1960 nesting_budget,1961 )1962 .map_err(Self::map_unique_err_to_proxy)?;19631964 ensure!(is_owned, <Error<T>>::NoPermission);19651966 Ok(())1967 }19681969 1970 1971 pub fn filter_user_properties<Key, Value, R, Mapper>(1972 collection_id: CollectionId,1973 token_id: Option<TokenId>,1974 filter_keys: Option<Vec<RmrkPropertyKey>>,1975 mapper: Mapper,1976 ) -> Result<Vec<R>, DispatchError>1977 where1978 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1979 Value: Decode + Default,1980 Mapper: Fn(Key, Value) -> R,1981 {1982 filter_keys1983 .map(|keys| {1984 let properties = keys1985 .into_iter()1986 .filter_map(|key| {1987 let key: Key = key.try_into().ok()?;19881989 let value = match token_id {1990 Some(token_id) => Self::get_nft_property_decoded(1991 collection_id,1992 token_id,1993 UserProperty(key.as_ref()),1994 ),1995 None => Self::get_collection_property_decoded(1996 collection_id,1997 UserProperty(key.as_ref()),1998 ),1999 }2000 .ok()?;20012002 Some(mapper(key, value))2003 })2004 .collect();20052006 Ok(properties)2007 })2008 .unwrap_or_else(|| {2009 let properties =2010 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20112012 Ok(properties)2013 })2014 }20152016 2017 2018 pub fn iterate_user_properties<Key, Value, R, Mapper>(2019 collection_id: CollectionId,2020 token_id: Option<TokenId>,2021 mapper: Mapper,2022 ) -> Result<impl Iterator<Item = R>, DispatchError>2023 where2024 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2025 Value: Decode + Default,2026 Mapper: Fn(Key, Value) -> R,2027 {2028 let properties = match token_id {2029 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2030 None => <PalletCommon<T>>::collection_properties(collection_id),2031 };20322033 let properties = properties.into_iter().filter_map(move |(key, value)| {2034 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20352036 let key: Key = key.to_vec().try_into().ok()?;2037 let value: Value = value.decode().ok()?;20382039 Some(mapper(key, value))2040 });20412042 Ok(properties)2043 }20442045 2046 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2047 map_unique_err_to_proxy! {2048 match err {2049 CommonError::NoPermission => NoPermission,2050 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2051 CommonError::PublicMintingNotAllowed => NoPermission,2052 CommonError::TokenNotFound => NoAvailableNftId,2053 CommonError::ApprovedValueTooLow => NoPermission,2054 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2055 StructureError::TokenNotFound => NoAvailableNftId,2056 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2057 }2058 }2059 }2060}