123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146#![cfg_attr(not(feature = "std"), no_std)]147148use frame_support::{pallet_prelude::*, BoundedVec, dispatch::DispatchResult};149use frame_system::{pallet_prelude::*, ensure_signed};150use sp_runtime::{DispatchError, Permill, traits::StaticLookup};151use sp_std::{152 vec::Vec,153 collections::{btree_set::BTreeSet, btree_map::BTreeMap},154};155use up_data_structs::{*, mapping::TokenAddressMapping};156use pallet_common::{157 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,158};159use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};160use pallet_structure::{Pallet as PalletStructure, Error as StructureError};161use pallet_evm::account::CrossAccountId;162use core::convert::AsRef;163164pub use pallet::*;165166#[cfg(feature = "runtime-benchmarks")]167pub mod benchmarking;168pub mod misc;169pub mod property;170pub mod rpc;171pub mod weights;172173pub type SelfWeightOf<T> = <T as Config>::WeightInfo;174175use weights::WeightInfo;176use misc::*;177pub use property::*;178179use RmrkProperty::*;180181182pub const NESTING_BUDGET: u32 = 5;183184type PendingTarget = (CollectionId, TokenId);185type PendingChild = (RmrkCollectionId, RmrkNftId);186type PendingChildrenSet = BTreeSet<PendingChild>;187188type BasesMap = BTreeMap<RmrkBaseId, u32>;189190#[frame_support::pallet]191pub mod pallet {192 use super::*;193 use pallet_evm::account;194195 #[pallet::config]196 pub trait Config:197 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config198 {199 200 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;201202 203 type WeightInfo: WeightInfo;204 }205206 207 #[pallet::storage]208 #[pallet::getter(fn collection_index)]209 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;210211 212 #[pallet::storage]213 pub type UniqueCollectionId<T: Config> =214 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;215216 #[pallet::pallet]217 #[pallet::generate_store(pub(super) trait Store)]218 pub struct Pallet<T>(_);219220 #[pallet::event]221 #[pallet::generate_deposit(pub(super) fn deposit_event)]222 pub enum Event<T: Config> {223 CollectionCreated {224 issuer: T::AccountId,225 collection_id: RmrkCollectionId,226 },227 CollectionDestroyed {228 issuer: T::AccountId,229 collection_id: RmrkCollectionId,230 },231 IssuerChanged {232 old_issuer: T::AccountId,233 new_issuer: T::AccountId,234 collection_id: RmrkCollectionId,235 },236 CollectionLocked {237 issuer: T::AccountId,238 collection_id: RmrkCollectionId,239 },240 NftMinted {241 owner: T::AccountId,242 collection_id: RmrkCollectionId,243 nft_id: RmrkNftId,244 },245 NFTBurned {246 owner: T::AccountId,247 nft_id: RmrkNftId,248 },249 NFTSent {250 sender: T::AccountId,251 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,252 collection_id: RmrkCollectionId,253 nft_id: RmrkNftId,254 approval_required: bool,255 },256 NFTAccepted {257 sender: T::AccountId,258 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,259 collection_id: RmrkCollectionId,260 nft_id: RmrkNftId,261 },262 NFTRejected {263 sender: T::AccountId,264 collection_id: RmrkCollectionId,265 nft_id: RmrkNftId,266 },267 PropertySet {268 collection_id: RmrkCollectionId,269 maybe_nft_id: Option<RmrkNftId>,270 key: RmrkKeyString,271 value: RmrkValueString,272 },273 ResourceAdded {274 nft_id: RmrkNftId,275 resource_id: RmrkResourceId,276 },277 ResourceRemoval {278 nft_id: RmrkNftId,279 resource_id: RmrkResourceId,280 },281 ResourceAccepted {282 nft_id: RmrkNftId,283 resource_id: RmrkResourceId,284 },285 ResourceRemovalAccepted {286 nft_id: RmrkNftId,287 resource_id: RmrkResourceId,288 },289 PrioritySet {290 collection_id: RmrkCollectionId,291 nft_id: RmrkNftId,292 },293 }294295 #[pallet::error]296 pub enum Error<T> {297 298 299 CorruptedCollectionType,300 301 302 RmrkPropertyKeyIsTooLong,303 304 RmrkPropertyValueIsTooLong,305 306 RmrkPropertyIsNotFound,307 308 309 UnableToDecodeRmrkData,310311 312 313 CollectionNotEmpty,314 315 NoAvailableCollectionId,316 317 NoAvailableNftId,318 319 CollectionUnknown,320 321 NoPermission,322 323 NonTransferable,324 325 CollectionFullOrLocked,326 327 ResourceDoesntExist,328 329 330 CannotSendToDescendentOrSelf,331 332 CannotAcceptNonOwnedNft,333 334 CannotRejectNonOwnedNft,335 336 CannotRejectNonPendingNft,337 338 ResourceNotPending,339 340 NoAvailableResourceId,341 }342343 #[pallet::call]344 impl<T: Config> Pallet<T> {345 346347 348 349 350 351 352 353 354 355 356 357 358 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]359 pub fn create_collection(360 origin: OriginFor<T>,361 metadata: RmrkString,362 max: Option<u32>,363 symbol: RmrkCollectionSymbol,364 ) -> DispatchResult {365 let sender = ensure_signed(origin)?;366367 let limits = CollectionLimits {368 owner_can_transfer: Some(false),369 token_limit: max,370 ..Default::default()371 };372373 let data = CreateCollectionData {374 limits: Some(limits),375 token_prefix: symbol376 .into_inner()377 .try_into()378 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,379 permissions: Some(CollectionPermissions {380 nesting: Some(NestingPermissions {381 token_owner: true,382 collection_admin: false,383 restricted: None,384 #[cfg(feature = "runtime-benchmarks")]385 permissive: false,386 }),387 ..Default::default()388 }),389 ..Default::default()390 };391392 let unique_collection_id = Self::init_collection(393 T::CrossAccountId::from_sub(sender.clone()),394 data,395 [396 Self::encode_rmrk_property(Metadata, &metadata)?,397 Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,398 ]399 .into_iter(),400 )?;401 let rmrk_collection_id = <CollectionIndex<T>>::get();402403 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);404405 <PalletCommon<T>>::set_scoped_collection_property(406 unique_collection_id,407 RMRK_SCOPE,408 Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,409 )?;410411 <CollectionIndex<T>>::mutate(|n| *n += 1);412413 Self::deposit_event(Event::CollectionCreated {414 issuer: sender,415 collection_id: rmrk_collection_id,416 });417418 Ok(())419 }420421 422 423 424 425 426 427 428 429 430 431 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]432 pub fn destroy_collection(433 origin: OriginFor<T>,434 collection_id: RmrkCollectionId,435 ) -> DispatchResult {436 let sender = ensure_signed(origin)?;437 let cross_sender = T::CrossAccountId::from_sub(sender.clone());438439 let collection = Self::get_typed_nft_collection(440 Self::unique_collection_id(collection_id)?,441 misc::CollectionType::Regular,442 )?;443 collection.check_is_external()?;444445 <PalletNft<T>>::destroy_collection(collection, &cross_sender)446 .map_err(Self::map_unique_err_to_proxy)?;447448 Self::deposit_event(Event::CollectionDestroyed {449 issuer: sender,450 collection_id,451 });452453 Ok(())454 }455456 457 458 459 460 461 462 463 464 465 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]466 pub fn change_collection_issuer(467 origin: OriginFor<T>,468 collection_id: RmrkCollectionId,469 new_issuer: <T::Lookup as StaticLookup>::Source,470 ) -> DispatchResult {471 let sender = ensure_signed(origin)?;472473 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;474 collection.check_is_external()?;475476 let new_issuer = T::Lookup::lookup(new_issuer)?;477478 Self::change_collection_owner(479 Self::unique_collection_id(collection_id)?,480 misc::CollectionType::Regular,481 sender.clone(),482 new_issuer.clone(),483 )?;484485 Self::deposit_event(Event::IssuerChanged {486 old_issuer: sender,487 new_issuer,488 collection_id,489 });490491 Ok(())492 }493494 495 496 497 498 499 500 501 502 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]503 pub fn lock_collection(504 origin: OriginFor<T>,505 collection_id: RmrkCollectionId,506 ) -> DispatchResult {507 let sender = ensure_signed(origin)?;508 let cross_sender = T::CrossAccountId::from_sub(sender.clone());509510 let collection = Self::get_typed_nft_collection(511 Self::unique_collection_id(collection_id)?,512 misc::CollectionType::Regular,513 )?;514 collection.check_is_external()?;515516 Self::check_collection_owner(&collection, &cross_sender)?;517518 let token_count = collection.total_supply();519520 let mut collection = collection.into_inner();521 collection.limits.token_limit = Some(token_count);522 collection.save()?;523524 Self::deposit_event(Event::CollectionLocked {525 issuer: sender,526 collection_id,527 });528529 Ok(())530 }531532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]547 pub fn mint_nft(548 origin: OriginFor<T>,549 owner: Option<T::AccountId>,550 collection_id: RmrkCollectionId,551 recipient: Option<T::AccountId>,552 royalty_amount: Option<Permill>,553 metadata: RmrkString,554 transferable: bool,555 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,556 ) -> DispatchResult {557 let sender = ensure_signed(origin)?;558 let cross_sender = T::CrossAccountId::from_sub(sender.clone());559560 let owner = owner.unwrap_or(sender.clone());561 let cross_owner = T::CrossAccountId::from_sub(owner.clone());562563 let collection = Self::get_typed_nft_collection(564 Self::unique_collection_id(collection_id)?,565 misc::CollectionType::Regular,566 )?;567 collection.check_is_external()?;568569 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {570 recipient: recipient.unwrap_or_else(|| owner.clone()),571 amount,572 });573574 let nft_id = Self::create_nft(575 &cross_sender,576 &cross_owner,577 &collection,578 [579 Self::encode_rmrk_property(TokenType, &NftType::Regular)?,580 Self::encode_rmrk_property(Transferable, &transferable)?,581 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,582 Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,583 Self::encode_rmrk_property(Metadata, &metadata)?,584 Self::encode_rmrk_property(Equipped, &false)?,585 Self::encode_rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,586 Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,587 Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,588 Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,589 ]590 .into_iter(),591 )592 .map_err(|err| match err {593 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),594 err => Self::map_unique_err_to_proxy(err),595 })?;596597 if let Some(resources) = resources {598 for resource in resources {599 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;600 }601 }602603 Self::deposit_event(Event::NftMinted {604 owner,605 collection_id,606 nft_id: nft_id.0,607 });608609 Ok(())610 }611612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]630 pub fn burn_nft(631 origin: OriginFor<T>,632 collection_id: RmrkCollectionId,633 nft_id: RmrkNftId,634 max_burns: u32,635 ) -> DispatchResult {636 let sender = ensure_signed(origin)?;637 let cross_sender = T::CrossAccountId::from_sub(sender.clone());638639 let collection = Self::get_typed_nft_collection(640 Self::unique_collection_id(collection_id)?,641 misc::CollectionType::Regular,642 )?;643 collection.check_is_external()?;644645 Self::destroy_nft(646 cross_sender,647 Self::unique_collection_id(collection_id)?,648 nft_id.into(),649 max_burns,650 <Error<T>>::NoPermission,651 )652 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;653654 Self::deposit_event(Event::NFTBurned {655 owner: sender,656 nft_id,657 });658659 Ok(())660 }661662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 #[pallet::weight(<SelfWeightOf<T>>::send())]677 pub fn send(678 origin: OriginFor<T>,679 rmrk_collection_id: RmrkCollectionId,680 rmrk_nft_id: RmrkNftId,681 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,682 ) -> DispatchResult {683 let sender = ensure_signed(origin.clone())?;684 let cross_sender = T::CrossAccountId::from_sub(sender.clone());685686 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;687 let nft_id = rmrk_nft_id.into();688689 let collection =690 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;691 collection.check_is_external()?;692693 let token_data =694 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;695696 let from = token_data.owner;697698 ensure!(699 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,700 <Error<T>>::NonTransferable701 );702703 ensure!(704 Self::get_nft_property_decoded::<Option<PendingTarget>>(705 collection_id,706 nft_id,707 RmrkProperty::PendingNftAccept708 )?709 .is_none(),710 <Error<T>>::NoPermission711 );712713 let target_owner;714 let approval_required;715716 match new_owner {717 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {718 target_owner = T::CrossAccountId::from_sub(account_id.clone());719 approval_required = false;720 }721 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(722 target_collection_id,723 target_nft_id,724 ) => {725 let target_collection_id = Self::unique_collection_id(target_collection_id)?;726727 let target_nft_budget = budget::Value::new(NESTING_BUDGET);728729 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(730 target_collection_id,731 target_nft_id.into(),732 Some((collection_id, nft_id)),733 &target_nft_budget,734 )735 .map_err(Self::map_unique_err_to_proxy)?;736737 approval_required = cross_sender != target_nft_owner;738739 if approval_required {740 target_owner = target_nft_owner;741742 <PalletNft<T>>::set_scoped_token_property(743 collection.id,744 nft_id,745 RMRK_SCOPE,746 Self::encode_rmrk_property::<Option<PendingTarget>>(747 PendingNftAccept,748 &Some((target_collection_id, target_nft_id.into())),749 )?,750 )?;751752 Self::insert_pending_child(753 (target_collection_id, target_nft_id.into()),754 (rmrk_collection_id, rmrk_nft_id),755 )?;756 } else {757 target_owner = T::CrossTokenAddressMapping::token_to_address(758 target_collection_id,759 target_nft_id.into(),760 );761 }762 }763 }764765 let src_nft_budget = budget::Value::new(NESTING_BUDGET);766767 <PalletNft<T>>::transfer_from(768 &collection,769 &cross_sender,770 &from,771 &target_owner,772 nft_id,773 &src_nft_budget,774 )775 .map_err(Self::map_unique_err_to_proxy)?;776777 Self::deposit_event(Event::NFTSent {778 sender,779 recipient: new_owner,780 collection_id: rmrk_collection_id,781 nft_id: rmrk_nft_id,782 approval_required,783 });784785 Ok(())786 }787788 789 790 791 792 793 794 795 796 797 798 799 800 801 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]802 pub fn accept_nft(803 origin: OriginFor<T>,804 rmrk_collection_id: RmrkCollectionId,805 rmrk_nft_id: RmrkNftId,806 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,807 ) -> DispatchResult {808 let sender = ensure_signed(origin.clone())?;809 let cross_sender = T::CrossAccountId::from_sub(sender.clone());810811 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;812 let nft_id = rmrk_nft_id.into();813814 let collection =815 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;816 collection.check_is_external()?;817818 let new_cross_owner = match new_owner {819 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {820 T::CrossAccountId::from_sub(account_id.clone())821 }822 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(823 target_collection_id,824 target_nft_id,825 ) => {826 let target_collection_id = Self::unique_collection_id(target_collection_id)?;827828 T::CrossTokenAddressMapping::token_to_address(829 target_collection_id,830 TokenId(target_nft_id),831 )832 }833 };834835 let budget = budget::Value::new(NESTING_BUDGET);836837 <PalletNft<T>>::transfer(838 &collection,839 &cross_sender,840 &new_cross_owner,841 nft_id,842 &budget,843 )844 .map_err(|err| {845 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {846 <Error<T>>::CannotAcceptNonOwnedNft.into()847 } else {848 Self::map_unique_err_to_proxy(err)849 }850 })?;851852 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(853 collection_id,854 nft_id,855 RmrkProperty::PendingNftAccept,856 )?;857858 if let Some(pending_target) = pending_target {859 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;860861 <PalletNft<T>>::set_scoped_token_property(862 collection.id,863 nft_id,864 RMRK_SCOPE,865 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,866 )?;867 }868869 Self::deposit_event(Event::NFTAccepted {870 sender,871 recipient: new_owner,872 collection_id: rmrk_collection_id,873 nft_id: rmrk_nft_id,874 });875876 Ok(())877 }878879 880 881 882 883 884 885 886 887 888 889 890 891 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]892 pub fn reject_nft(893 origin: OriginFor<T>,894 rmrk_collection_id: RmrkCollectionId,895 rmrk_nft_id: RmrkNftId,896 ) -> DispatchResult {897 let sender = ensure_signed(origin)?;898 let cross_sender = T::CrossAccountId::from_sub(sender.clone());899900 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;901 let nft_id = rmrk_nft_id.into();902903 let collection =904 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;905 collection.check_is_external()?;906907 ensure!(908 <TokenData<T>>::get((collection_id, nft_id)).is_some(),909 <Error<T>>::NoAvailableNftId910 );911912 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(913 collection_id,914 nft_id,915 RmrkProperty::PendingNftAccept,916 )?;917918 match pending_target {919 Some(pending_target) => {920 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?921 }922 None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),923 }924925 Self::destroy_nft(926 cross_sender,927 collection_id,928 nft_id,929 NESTING_BUDGET,930 <Error<T>>::CannotRejectNonOwnedNft,931 )932 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;933934 Self::deposit_event(Event::NFTRejected {935 sender,936 collection_id: rmrk_collection_id,937 nft_id: rmrk_nft_id,938 });939940 Ok(())941 }942943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]959 pub fn accept_resource(960 origin: OriginFor<T>,961 rmrk_collection_id: RmrkCollectionId,962 rmrk_nft_id: RmrkNftId,963 resource_id: RmrkResourceId,964 ) -> DispatchResult {965 let sender = ensure_signed(origin)?;966 let cross_sender = T::CrossAccountId::from_sub(sender);967968 let collection_id = Self::unique_collection_id(rmrk_collection_id)969 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;970 let collection =971 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;972 collection.check_is_external()?;973974 let nft_id = rmrk_nft_id.into();975976 let budget = budget::Value::new(NESTING_BUDGET);977978 let nft_owner =979 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)980 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;981982 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {983 ensure!(res.pending, <Error<T>>::ResourceNotPending);984 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);985986 res.pending = false;987988 Ok(())989 })?;990991 Self::deposit_event(Event::<T>::ResourceAccepted {992 nft_id: rmrk_nft_id,993 resource_id,994 });995996 Ok(())997 }998999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]1013 pub fn accept_resource_removal(1014 origin: OriginFor<T>,1015 rmrk_collection_id: RmrkCollectionId,1016 rmrk_nft_id: RmrkNftId,1017 resource_id: RmrkResourceId,1018 ) -> DispatchResult {1019 let sender = ensure_signed(origin)?;1020 let cross_sender = T::CrossAccountId::from_sub(sender);10211022 let collection_id = Self::unique_collection_id(rmrk_collection_id)1023 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;1024 let collection =1025 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1026 collection.check_is_external()?;10271028 let nft_id = rmrk_nft_id.into();10291030 let budget = budget::Value::new(NESTING_BUDGET);10311032 let nft_owner =1033 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1034 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;10351036 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10371038 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10391040 let resource_info = <PalletNft<T>>::token_aux_property((1041 collection_id,1042 nft_id,1043 RMRK_SCOPE,1044 resource_id_key.clone(),1045 ))1046 .ok_or(<Error<T>>::ResourceDoesntExist)?;10471048 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10491050 ensure!(1051 resource_info.pending_removal,1052 <Error<T>>::ResourceNotPending1053 );10541055 <PalletNft<T>>::remove_token_aux_property(1056 collection_id,1057 nft_id,1058 RMRK_SCOPE,1059 resource_id_key,1060 );10611062 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1063 let base_id = resource.base;10641065 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1066 }10671068 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1069 nft_id: rmrk_nft_id,1070 resource_id,1071 });10721073 Ok(())1074 }10751076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 #[pallet::weight(<SelfWeightOf<T>>::set_property())]1094 pub fn set_property(1095 origin: OriginFor<T>,1096 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,1097 maybe_nft_id: Option<RmrkNftId>,1098 key: RmrkKeyString,1099 value: RmrkValueString,1100 ) -> DispatchResult {1101 let sender = ensure_signed(origin)?;1102 let sender = T::CrossAccountId::from_sub(sender);11031104 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1105 let collection =1106 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1107 collection.check_is_external()?;11081109 let budget = budget::Value::new(NESTING_BUDGET);11101111 match maybe_nft_id {1112 Some(nft_id) => {1113 let token_id: TokenId = nft_id.into();11141115 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1116 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;11171118 <PalletNft<T>>::set_scoped_token_property(1119 collection_id,1120 token_id,1121 RMRK_SCOPE,1122 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1123 )?;1124 }1125 None => {1126 let collection = Self::get_typed_nft_collection(1127 collection_id,1128 misc::CollectionType::Regular,1129 )?;11301131 Self::check_collection_owner(&collection, &sender)?;11321133 <PalletCommon<T>>::set_scoped_collection_property(1134 collection_id,1135 RMRK_SCOPE,1136 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1137 )?;1138 }1139 }11401141 Self::deposit_event(Event::PropertySet {1142 collection_id: rmrk_collection_id,1143 maybe_nft_id,1144 key,1145 value,1146 });11471148 Ok(())1149 }11501151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]1167 pub fn set_priority(1168 origin: OriginFor<T>,1169 rmrk_collection_id: RmrkCollectionId,1170 rmrk_nft_id: RmrkNftId,1171 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1172 ) -> DispatchResult {1173 let sender = ensure_signed(origin)?;1174 let sender = T::CrossAccountId::from_sub(sender);11751176 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1177 let nft_id = rmrk_nft_id.into();11781179 let collection =1180 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1181 collection.check_is_external()?;11821183 let budget = budget::Value::new(NESTING_BUDGET);11841185 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1186 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;11871188 <PalletNft<T>>::set_scoped_token_property(1189 collection_id,1190 nft_id,1191 RMRK_SCOPE,1192 Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1193 )?;11941195 Self::deposit_event(Event::<T>::PrioritySet {1196 collection_id: rmrk_collection_id,1197 nft_id: rmrk_nft_id,1198 });11991200 Ok(())1201 }12021203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1218 pub fn add_basic_resource(1219 origin: OriginFor<T>,1220 rmrk_collection_id: RmrkCollectionId,1221 nft_id: RmrkNftId,1222 resource: RmrkBasicResource,1223 ) -> DispatchResult {1224 let sender = ensure_signed(origin.clone())?;12251226 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1227 let collection =1228 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1229 collection.check_is_external()?;12301231 let resource_id = Self::resource_add(1232 sender,1233 collection_id,1234 nft_id.into(),1235 RmrkResourceTypes::Basic(resource),1236 )?;12371238 Self::deposit_event(Event::ResourceAdded {1239 nft_id,1240 resource_id,1241 });1242 Ok(())1243 }12441245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1260 pub fn add_composable_resource(1261 origin: OriginFor<T>,1262 rmrk_collection_id: RmrkCollectionId,1263 nft_id: RmrkNftId,1264 resource: RmrkComposableResource,1265 ) -> DispatchResult {1266 let sender = ensure_signed(origin.clone())?;12671268 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1269 let collection =1270 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1271 collection.check_is_external()?;12721273 let base_id = resource.base;12741275 let resource_id = Self::resource_add(1276 sender,1277 collection_id,1278 nft_id.into(),1279 RmrkResourceTypes::Composable(resource),1280 )?;12811282 <PalletNft<T>>::try_mutate_token_aux_property(1283 collection_id,1284 nft_id.into(),1285 RMRK_SCOPE,1286 Self::get_scoped_property_key(AssociatedBases)?,1287 |value| -> DispatchResult {1288 let mut bases: BasesMap = match value {1289 Some(value) => Self::decode_property_value(value)?,1290 None => BasesMap::new(),1291 };12921293 *bases.entry(base_id).or_insert(0) += 1;12941295 *value = Some(Self::encode_property_value(&bases)?);1296 Ok(())1297 },1298 )?;12991300 Self::deposit_event(Event::ResourceAdded {1301 nft_id,1302 resource_id,1303 });1304 Ok(())1305 }13061307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1322 pub fn add_slot_resource(1323 origin: OriginFor<T>,1324 rmrk_collection_id: RmrkCollectionId,1325 nft_id: RmrkNftId,1326 resource: RmrkSlotResource,1327 ) -> DispatchResult {1328 let sender = ensure_signed(origin.clone())?;13291330 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1331 let collection =1332 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1333 collection.check_is_external()?;13341335 let resource_id = Self::resource_add(1336 sender,1337 collection_id,1338 nft_id.into(),1339 RmrkResourceTypes::Slot(resource),1340 )?;13411342 Self::deposit_event(Event::ResourceAdded {1343 nft_id,1344 resource_id,1345 });1346 Ok(())1347 }13481349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1363 pub fn remove_resource(1364 origin: OriginFor<T>,1365 rmrk_collection_id: RmrkCollectionId,1366 nft_id: RmrkNftId,1367 resource_id: RmrkResourceId,1368 ) -> DispatchResult {1369 let sender = ensure_signed(origin.clone())?;13701371 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1372 let collection =1373 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1374 collection.check_is_external()?;13751376 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13771378 Self::deposit_event(Event::ResourceRemoval {1379 nft_id,1380 resource_id,1381 });1382 Ok(())1383 }1384 }1385}13861387impl<T: Config> Pallet<T> {1388 1389 pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1390 let key = rmrk_key.to_key::<T>()?;13911392 let scoped_key = RMRK_SCOPE1393 .apply(key)1394 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;13951396 Ok(scoped_key)1397 }13981399 1400 1401 pub fn encode_rmrk_property<E: Encode>(1402 rmrk_key: RmrkProperty,1403 value: &E,1404 ) -> Result<Property, DispatchError> {1405 let key = rmrk_key.to_key::<T>()?;14061407 let value = Self::encode_property_value(value)?;14081409 let property = Property { key, value };14101411 Ok(property)1412 }14131414 1415 pub fn encode_property_value<E: Encode, S: Get<u32>>(1416 value: &E,1417 ) -> Result<BoundedBytes<S>, DispatchError> {1418 let value = value1419 .encode()1420 .try_into()1421 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;14221423 Ok(value)1424 }14251426 1427 pub fn decode_property_value<D: Decode, S: Get<u32>>(1428 vec: &BoundedBytes<S>,1429 ) -> Result<D, DispatchError> {1430 vec.decode()1431 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1432 }14331434 1435 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1436 where1437 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1438 {1439 vec.rebind()1440 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1441 }14421443 1444 1445 1446 fn init_collection(1447 sender: T::CrossAccountId,1448 data: CreateCollectionData<T::AccountId>,1449 properties: impl Iterator<Item = Property>,1450 ) -> Result<CollectionId, DispatchError> {1451 let collection_id = <PalletNft<T>>::init_collection(1452 sender.clone(),1453 sender,1454 data,1455 up_data_structs::CollectionFlags {1456 external: true,1457 ..Default::default()1458 },1459 );14601461 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1462 return Err(<Error<T>>::NoAvailableCollectionId.into());1463 }14641465 <PalletCommon<T>>::set_scoped_collection_properties(1466 collection_id?,1467 RMRK_SCOPE,1468 properties,1469 )?;14701471 collection_id1472 }14731474 1475 1476 1477 pub fn create_nft(1478 sender: &T::CrossAccountId,1479 owner: &T::CrossAccountId,1480 collection: &NonfungibleHandle<T>,1481 properties: impl Iterator<Item = Property>,1482 ) -> Result<TokenId, DispatchError> {1483 let data = CreateNftExData {1484 properties: BoundedVec::default(),1485 owner: owner.clone(),1486 };14871488 let budget = budget::Value::new(NESTING_BUDGET);14891490 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;14911492 let nft_id = <PalletNft<T>>::current_token_id(collection.id);14931494 <PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;14951496 Ok(nft_id)1497 }14981499 1500 1501 1502 fn destroy_nft(1503 sender: T::CrossAccountId,1504 collection_id: CollectionId,1505 token_id: TokenId,1506 max_burns: u32,1507 error_if_not_owned: Error<T>,1508 ) -> DispatchResultWithPostInfo {1509 let collection =1510 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;15111512 let token_data =1513 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;15141515 let from = token_data.owner;15161517 let owner_check_budget = budget::Value::new(NESTING_BUDGET);15181519 ensure!(1520 <PalletStructure<T>>::check_indirectly_owned(1521 sender.clone(),1522 collection_id,1523 token_id,1524 None,1525 &owner_check_budget1526 )?,1527 error_if_not_owned,1528 );15291530 let burns_budget = budget::Value::new(max_burns);1531 let breadth_budget = budget::Value::new(max_burns);15321533 <PalletNft<T>>::burn_recursively(1534 &collection,1535 &from,1536 token_id,1537 &burns_budget,1538 &breadth_budget,1539 )1540 }15411542 1543 fn insert_pending_child(1544 target: (CollectionId, TokenId),1545 child: (RmrkCollectionId, RmrkNftId),1546 ) -> DispatchResult {1547 Self::mutate_pending_children(target, |pending_children| {1548 pending_children.insert(child);1549 })1550 }15511552 1553 fn remove_pending_child(1554 target: (CollectionId, TokenId),1555 child: (RmrkCollectionId, RmrkNftId),1556 ) -> DispatchResult {1557 Self::mutate_pending_children(target, |pending_children| {1558 pending_children.remove(&child);1559 })1560 }15611562 1563 1564 fn mutate_pending_children(1565 (target_collection_id, target_nft_id): (CollectionId, TokenId),1566 f: impl FnOnce(&mut PendingChildrenSet),1567 ) -> DispatchResult {1568 <PalletNft<T>>::try_mutate_token_aux_property(1569 target_collection_id,1570 target_nft_id,1571 RMRK_SCOPE,1572 Self::get_scoped_property_key(PendingChildren)?,1573 |pending_children| -> DispatchResult {1574 let mut map = match pending_children {1575 Some(map) => Self::decode_property_value(map)?,1576 None => PendingChildrenSet::new(),1577 };15781579 f(&mut map);15801581 *pending_children = Some(Self::encode_property_value(&map)?);15821583 Ok(())1584 },1585 )1586 }15871588 1589 1590 fn iterate_pending_children(1591 collection_id: CollectionId,1592 nft_id: TokenId,1593 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1594 let property = <PalletNft<T>>::token_aux_property((1595 collection_id,1596 nft_id,1597 RMRK_SCOPE,1598 Self::get_scoped_property_key(PendingChildren)?,1599 ));16001601 let pending_children = match property {1602 Some(map) => Self::decode_property_value(&map)?,1603 None => PendingChildrenSet::new(),1604 };16051606 Ok(pending_children.into_iter())1607 }16081609 1610 1611 1612 1613 fn acquire_next_resource_id(1614 collection_id: CollectionId,1615 nft_id: TokenId,1616 ) -> Result<RmrkResourceId, DispatchError> {1617 let resource_id: RmrkResourceId =1618 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;16191620 let next_id = resource_id1621 .checked_add(1)1622 .ok_or(<Error<T>>::NoAvailableResourceId)?;16231624 <PalletNft<T>>::set_scoped_token_property(1625 collection_id,1626 nft_id,1627 RMRK_SCOPE,1628 Self::encode_rmrk_property(NextResourceId, &next_id)?,1629 )?;16301631 Ok(resource_id)1632 }16331634 1635 1636 fn resource_add(1637 sender: T::AccountId,1638 collection_id: CollectionId,1639 nft_id: TokenId,1640 resource: RmrkResourceTypes,1641 ) -> Result<RmrkResourceId, DispatchError> {1642 let collection =1643 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1644 ensure!(collection.owner == sender, Error::<T>::NoPermission);16451646 let sender = T::CrossAccountId::from_sub(sender);1647 let budget = budget::Value::new(NESTING_BUDGET);16481649 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1650 .map_err(Self::map_unique_err_to_proxy)?;16511652 let pending = sender != nft_owner;16531654 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16551656 let resource_info = RmrkResourceInfo {1657 id,1658 resource,1659 pending,1660 pending_removal: false,1661 };16621663 <PalletNft<T>>::try_mutate_token_aux_property(1664 collection_id,1665 nft_id,1666 RMRK_SCOPE,1667 Self::get_scoped_property_key(ResourceId(id))?,1668 |value| -> DispatchResult {1669 *value = Some(Self::encode_property_value(&resource_info)?);16701671 Ok(())1672 },1673 )?;16741675 Ok(id)1676 }16771678 1679 1680 fn resource_remove(1681 sender: T::AccountId,1682 collection_id: CollectionId,1683 nft_id: TokenId,1684 resource_id: RmrkResourceId,1685 ) -> DispatchResult {1686 let collection =1687 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1688 ensure!(collection.owner == sender, Error::<T>::NoPermission);16891690 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16911692 let resource = <PalletNft<T>>::token_aux_property((1693 collection_id,1694 nft_id,1695 RMRK_SCOPE,1696 resource_id_key.clone(),1697 ))1698 .ok_or(<Error<T>>::ResourceDoesntExist)?;16991700 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;17011702 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1703 let topmost_owner =1704 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;17051706 let sender = T::CrossAccountId::from_sub(sender);1707 if topmost_owner == sender {1708 <PalletNft<T>>::remove_token_aux_property(1709 collection_id,1710 nft_id,1711 RMRK_SCOPE,1712 Self::get_scoped_property_key(ResourceId(resource_id))?,1713 );17141715 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1716 let base_id = resource.base;17171718 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1719 }1720 } else {1721 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1722 res.pending_removal = true;17231724 Ok(())1725 })?;1726 }17271728 Ok(())1729 }17301731 1732 1733 fn remove_associated_base_id(1734 collection_id: CollectionId,1735 nft_id: TokenId,1736 base_id: RmrkBaseId,1737 ) -> DispatchResult {1738 <PalletNft<T>>::try_mutate_token_aux_property(1739 collection_id,1740 nft_id,1741 RMRK_SCOPE,1742 Self::get_scoped_property_key(AssociatedBases)?,1743 |value| -> DispatchResult {1744 let mut bases: BasesMap = match value {1745 Some(value) => Self::decode_property_value(value)?,1746 None => BasesMap::new(),1747 };17481749 let remaining = bases.get(&base_id);17501751 if let Some(remaining) = remaining {1752 if let Some(0) | None = remaining.checked_sub(1) {1753 bases.remove(&base_id);1754 }1755 }17561757 *value = Some(Self::encode_property_value(&bases)?);1758 Ok(())1759 },1760 )1761 }17621763 1764 fn try_mutate_resource_info(1765 collection_id: CollectionId,1766 nft_id: TokenId,1767 resource_id: RmrkResourceId,1768 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1769 ) -> DispatchResult {1770 <PalletNft<T>>::try_mutate_token_aux_property(1771 collection_id,1772 nft_id,1773 RMRK_SCOPE,1774 Self::get_scoped_property_key(ResourceId(resource_id))?,1775 |value| match value {1776 Some(value) => {1777 let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17781779 f(&mut resource_info)?;17801781 *value = Self::encode_property_value(&resource_info)?;17821783 Ok(())1784 }1785 None => Err(<Error<T>>::ResourceDoesntExist.into()),1786 },1787 )1788 }17891790 1791 fn change_collection_owner(1792 collection_id: CollectionId,1793 collection_type: misc::CollectionType,1794 sender: T::AccountId,1795 new_owner: T::AccountId,1796 ) -> DispatchResult {1797 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1798 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;17991800 let mut collection = collection.into_inner();18011802 collection.owner = new_owner;1803 collection.save()1804 }18051806 1807 pub fn check_collection_owner(1808 collection: &NonfungibleHandle<T>,1809 account: &T::CrossAccountId,1810 ) -> DispatchResult {1811 collection1812 .check_is_owner(account)1813 .map_err(Self::map_unique_err_to_proxy)1814 }18151816 1817 pub fn last_collection_idx() -> RmrkCollectionId {1818 <CollectionIndex<T>>::get()1819 }18201821 1822 pub fn unique_collection_id(1823 rmrk_collection_id: RmrkCollectionId,1824 ) -> Result<CollectionId, DispatchError> {1825 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1826 .map_err(|_| <Error<T>>::CollectionUnknown.into())1827 }18281829 1830 pub fn rmrk_collection_id(1831 unique_collection_id: CollectionId,1832 ) -> Result<RmrkCollectionId, DispatchError> {1833 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1834 }18351836 1837 pub fn get_nft_collection(1838 collection_id: CollectionId,1839 ) -> Result<NonfungibleHandle<T>, DispatchError> {1840 let collection = <CollectionHandle<T>>::try_get(collection_id)1841 .map_err(|_| <Error<T>>::CollectionUnknown)?;18421843 match collection.mode {1844 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1845 _ => Err(<Error<T>>::CollectionUnknown.into()),1846 }1847 }18481849 1850 pub fn collection_exists(collection_id: CollectionId) -> bool {1851 <CollectionHandle<T>>::try_get(collection_id).is_ok()1852 }18531854 1855 pub fn get_collection_property(1856 collection_id: CollectionId,1857 key: RmrkProperty,1858 ) -> Result<PropertyValue, DispatchError> {1859 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1860 .get(&Self::get_scoped_property_key(key)?)1861 .ok_or(<Error<T>>::CollectionUnknown)?1862 .clone();18631864 Ok(collection_property)1865 }18661867 1868 pub fn get_collection_property_decoded<V: Decode>(1869 collection_id: CollectionId,1870 key: RmrkProperty,1871 ) -> Result<V, DispatchError> {1872 Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1873 }18741875 1876 1877 1878 pub fn get_collection_type(1879 collection_id: CollectionId,1880 ) -> Result<misc::CollectionType, DispatchError> {1881 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1882 if err != <Error<T>>::CollectionUnknown.into() {1883 <Error<T>>::CorruptedCollectionType.into()1884 } else {1885 err1886 }1887 })1888 }18891890 1891 1892 pub fn ensure_collection_type(1893 collection_id: CollectionId,1894 collection_type: misc::CollectionType,1895 ) -> DispatchResult {1896 let actual_type = Self::get_collection_type(collection_id)?;1897 ensure!(1898 actual_type == collection_type,1899 <CommonError<T>>::NoPermission1900 );19011902 Ok(())1903 }19041905 1906 pub fn get_typed_nft_collection(1907 collection_id: CollectionId,1908 collection_type: misc::CollectionType,1909 ) -> Result<NonfungibleHandle<T>, DispatchError> {1910 Self::ensure_collection_type(collection_id, collection_type)?;19111912 Self::get_nft_collection(collection_id)1913 }19141915 1916 1917 pub fn get_typed_nft_collection_mapped(1918 rmrk_collection_id: RmrkCollectionId,1919 collection_type: misc::CollectionType,1920 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1921 let unique_collection_id = match collection_type {1922 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1923 _ => rmrk_collection_id.into(),1924 };19251926 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;19271928 Ok((collection, unique_collection_id))1929 }19301931 1932 pub fn get_nft_property(1933 collection_id: CollectionId,1934 nft_id: TokenId,1935 key: RmrkProperty,1936 ) -> Result<PropertyValue, DispatchError> {1937 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1938 .get(&Self::get_scoped_property_key(key)?)1939 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1940 .clone();19411942 Ok(nft_property)1943 }19441945 1946 pub fn get_nft_property_decoded<V: Decode>(1947 collection_id: CollectionId,1948 nft_id: TokenId,1949 key: RmrkProperty,1950 ) -> Result<V, DispatchError> {1951 Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1952 }19531954 1955 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1956 <TokenData<T>>::contains_key((collection_id, nft_id))1957 }19581959 1960 1961 1962 pub fn get_nft_type(1963 collection_id: CollectionId,1964 token_id: TokenId,1965 ) -> Result<NftType, DispatchError> {1966 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1967 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1968 }19691970 1971 pub fn ensure_nft_type(1972 collection_id: CollectionId,1973 token_id: TokenId,1974 nft_type: NftType,1975 ) -> DispatchResult {1976 let actual_type = Self::get_nft_type(collection_id, token_id)?;1977 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19781979 Ok(())1980 }19811982 1983 1984 pub fn ensure_nft_owner(1985 collection_id: CollectionId,1986 token_id: TokenId,1987 possible_owner: &T::CrossAccountId,1988 nesting_budget: &dyn budget::Budget,1989 ) -> DispatchResult {1990 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1991 possible_owner.clone(),1992 collection_id,1993 token_id,1994 None,1995 nesting_budget,1996 )1997 .map_err(Self::map_unique_err_to_proxy)?;19981999 ensure!(is_owned, <Error<T>>::NoPermission);20002001 Ok(())2002 }20032004 2005 2006 pub fn filter_user_properties<Key, Value, R, Mapper>(2007 collection_id: CollectionId,2008 token_id: Option<TokenId>,2009 filter_keys: Option<Vec<RmrkPropertyKey>>,2010 mapper: Mapper,2011 ) -> Result<Vec<R>, DispatchError>2012 where2013 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2014 Value: Decode + Default,2015 Mapper: Fn(Key, Value) -> R,2016 {2017 filter_keys2018 .map(|keys| {2019 let properties = keys2020 .into_iter()2021 .filter_map(|key| {2022 let key: Key = key.try_into().ok()?;20232024 let value = match token_id {2025 Some(token_id) => Self::get_nft_property_decoded(2026 collection_id,2027 token_id,2028 UserProperty(key.as_ref()),2029 ),2030 None => Self::get_collection_property_decoded(2031 collection_id,2032 UserProperty(key.as_ref()),2033 ),2034 }2035 .ok()?;20362037 Some(mapper(key, value))2038 })2039 .collect();20402041 Ok(properties)2042 })2043 .unwrap_or_else(|| {2044 let properties =2045 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20462047 Ok(properties)2048 })2049 }20502051 2052 2053 pub fn iterate_user_properties<Key, Value, R, Mapper>(2054 collection_id: CollectionId,2055 token_id: Option<TokenId>,2056 mapper: Mapper,2057 ) -> Result<impl Iterator<Item = R>, DispatchError>2058 where2059 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2060 Value: Decode + Default,2061 Mapper: Fn(Key, Value) -> R,2062 {2063 let properties = match token_id {2064 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2065 None => <PalletCommon<T>>::collection_properties(collection_id),2066 };20672068 let properties = properties.into_iter().filter_map(move |(key, value)| {2069 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20702071 let key: Key = key.to_vec().try_into().ok()?;2072 let value: Value = value.decode().ok()?;20732074 Some(mapper(key, value))2075 });20762077 Ok(properties)2078 }20792080 2081 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2082 map_unique_err_to_proxy! {2083 match err {2084 CommonError::NoPermission => NoPermission,2085 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2086 CommonError::PublicMintingNotAllowed => NoPermission,2087 CommonError::TokenNotFound => NoAvailableNftId,2088 CommonError::ApprovedValueTooLow => NoPermission,2089 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2090 StructureError::TokenNotFound => NoAvailableNftId,2091 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2092 }2093 }2094 }2095}