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 Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;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(sender, data, true);14521453 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1454 return Err(<Error<T>>::NoAvailableCollectionId.into());1455 }14561457 <PalletCommon<T>>::set_scoped_collection_properties(1458 collection_id?,1459 RMRK_SCOPE,1460 properties,1461 )?;14621463 collection_id1464 }14651466 1467 1468 1469 pub fn create_nft(1470 sender: &T::CrossAccountId,1471 owner: &T::CrossAccountId,1472 collection: &NonfungibleHandle<T>,1473 properties: impl Iterator<Item = Property>,1474 ) -> Result<TokenId, DispatchError> {1475 let data = CreateNftExData {1476 properties: BoundedVec::default(),1477 owner: owner.clone(),1478 };14791480 let budget = budget::Value::new(NESTING_BUDGET);14811482 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;14831484 let nft_id = <PalletNft<T>>::current_token_id(collection.id);14851486 <PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;14871488 Ok(nft_id)1489 }14901491 1492 1493 1494 fn destroy_nft(1495 sender: T::CrossAccountId,1496 collection_id: CollectionId,1497 token_id: TokenId,1498 max_burns: u32,1499 error_if_not_owned: Error<T>,1500 ) -> DispatchResultWithPostInfo {1501 let collection =1502 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;15031504 let token_data =1505 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;15061507 let from = token_data.owner;15081509 let owner_check_budget = budget::Value::new(NESTING_BUDGET);15101511 ensure!(1512 <PalletStructure<T>>::check_indirectly_owned(1513 sender.clone(),1514 collection_id,1515 token_id,1516 None,1517 &owner_check_budget1518 )?,1519 error_if_not_owned,1520 );15211522 let burns_budget = budget::Value::new(max_burns);1523 let breadth_budget = budget::Value::new(max_burns);15241525 <PalletNft<T>>::burn_recursively(1526 &collection,1527 &from,1528 token_id,1529 &burns_budget,1530 &breadth_budget,1531 )1532 }15331534 1535 fn insert_pending_child(1536 target: (CollectionId, TokenId),1537 child: (RmrkCollectionId, RmrkNftId),1538 ) -> DispatchResult {1539 Self::mutate_pending_children(target, |pending_children| {1540 pending_children.insert(child);1541 })1542 }15431544 1545 fn remove_pending_child(1546 target: (CollectionId, TokenId),1547 child: (RmrkCollectionId, RmrkNftId),1548 ) -> DispatchResult {1549 Self::mutate_pending_children(target, |pending_children| {1550 pending_children.remove(&child);1551 })1552 }15531554 1555 1556 fn mutate_pending_children(1557 (target_collection_id, target_nft_id): (CollectionId, TokenId),1558 f: impl FnOnce(&mut PendingChildrenSet),1559 ) -> DispatchResult {1560 <PalletNft<T>>::try_mutate_token_aux_property(1561 target_collection_id,1562 target_nft_id,1563 RMRK_SCOPE,1564 Self::get_scoped_property_key(PendingChildren)?,1565 |pending_children| -> DispatchResult {1566 let mut map = match pending_children {1567 Some(map) => Self::decode_property_value(map)?,1568 None => PendingChildrenSet::new(),1569 };15701571 f(&mut map);15721573 *pending_children = Some(Self::encode_property_value(&map)?);15741575 Ok(())1576 },1577 )1578 }15791580 1581 1582 fn iterate_pending_children(1583 collection_id: CollectionId,1584 nft_id: TokenId,1585 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1586 let property = <PalletNft<T>>::token_aux_property((1587 collection_id,1588 nft_id,1589 RMRK_SCOPE,1590 Self::get_scoped_property_key(PendingChildren)?,1591 ));15921593 let pending_children = match property {1594 Some(map) => Self::decode_property_value(&map)?,1595 None => PendingChildrenSet::new(),1596 };15971598 Ok(pending_children.into_iter())1599 }16001601 1602 1603 1604 1605 fn acquire_next_resource_id(1606 collection_id: CollectionId,1607 nft_id: TokenId,1608 ) -> Result<RmrkResourceId, DispatchError> {1609 let resource_id: RmrkResourceId =1610 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;16111612 let next_id = resource_id1613 .checked_add(1)1614 .ok_or(<Error<T>>::NoAvailableResourceId)?;16151616 <PalletNft<T>>::set_scoped_token_property(1617 collection_id,1618 nft_id,1619 RMRK_SCOPE,1620 Self::encode_rmrk_property(NextResourceId, &next_id)?,1621 )?;16221623 Ok(resource_id)1624 }16251626 1627 1628 fn resource_add(1629 sender: T::AccountId,1630 collection_id: CollectionId,1631 nft_id: TokenId,1632 resource: RmrkResourceTypes,1633 ) -> Result<RmrkResourceId, DispatchError> {1634 let collection =1635 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1636 ensure!(collection.owner == sender, Error::<T>::NoPermission);16371638 let sender = T::CrossAccountId::from_sub(sender);1639 let budget = budget::Value::new(NESTING_BUDGET);16401641 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1642 .map_err(Self::map_unique_err_to_proxy)?;16431644 let pending = sender != nft_owner;16451646 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16471648 let resource_info = RmrkResourceInfo {1649 id,1650 resource,1651 pending,1652 pending_removal: false,1653 };16541655 <PalletNft<T>>::try_mutate_token_aux_property(1656 collection_id,1657 nft_id,1658 RMRK_SCOPE,1659 Self::get_scoped_property_key(ResourceId(id))?,1660 |value| -> DispatchResult {1661 *value = Some(Self::encode_property_value(&resource_info)?);16621663 Ok(())1664 },1665 )?;16661667 Ok(id)1668 }16691670 1671 1672 fn resource_remove(1673 sender: T::AccountId,1674 collection_id: CollectionId,1675 nft_id: TokenId,1676 resource_id: RmrkResourceId,1677 ) -> DispatchResult {1678 let collection =1679 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1680 ensure!(collection.owner == sender, Error::<T>::NoPermission);16811682 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16831684 let resource = <PalletNft<T>>::token_aux_property((1685 collection_id,1686 nft_id,1687 RMRK_SCOPE,1688 resource_id_key.clone(),1689 ))1690 .ok_or(<Error<T>>::ResourceDoesntExist)?;16911692 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;16931694 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1695 let topmost_owner =1696 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;16971698 let sender = T::CrossAccountId::from_sub(sender);1699 if topmost_owner == sender {1700 <PalletNft<T>>::remove_token_aux_property(1701 collection_id,1702 nft_id,1703 RMRK_SCOPE,1704 Self::get_scoped_property_key(ResourceId(resource_id))?,1705 );17061707 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1708 let base_id = resource.base;17091710 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1711 }1712 } else {1713 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1714 res.pending_removal = true;17151716 Ok(())1717 })?;1718 }17191720 Ok(())1721 }17221723 1724 1725 fn remove_associated_base_id(1726 collection_id: CollectionId,1727 nft_id: TokenId,1728 base_id: RmrkBaseId,1729 ) -> DispatchResult {1730 <PalletNft<T>>::try_mutate_token_aux_property(1731 collection_id,1732 nft_id,1733 RMRK_SCOPE,1734 Self::get_scoped_property_key(AssociatedBases)?,1735 |value| -> DispatchResult {1736 let mut bases: BasesMap = match value {1737 Some(value) => Self::decode_property_value(value)?,1738 None => BasesMap::new(),1739 };17401741 let remaining = bases.get(&base_id);17421743 if let Some(remaining) = remaining {1744 if let Some(0) | None = remaining.checked_sub(1) {1745 bases.remove(&base_id);1746 }1747 }17481749 *value = Some(Self::encode_property_value(&bases)?);1750 Ok(())1751 },1752 )1753 }17541755 1756 fn try_mutate_resource_info(1757 collection_id: CollectionId,1758 nft_id: TokenId,1759 resource_id: RmrkResourceId,1760 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1761 ) -> DispatchResult {1762 <PalletNft<T>>::try_mutate_token_aux_property(1763 collection_id,1764 nft_id,1765 RMRK_SCOPE,1766 Self::get_scoped_property_key(ResourceId(resource_id))?,1767 |value| match value {1768 Some(value) => {1769 let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17701771 f(&mut resource_info)?;17721773 *value = Self::encode_property_value(&resource_info)?;17741775 Ok(())1776 }1777 None => Err(<Error<T>>::ResourceDoesntExist.into()),1778 },1779 )1780 }17811782 1783 fn change_collection_owner(1784 collection_id: CollectionId,1785 collection_type: misc::CollectionType,1786 sender: T::AccountId,1787 new_owner: T::AccountId,1788 ) -> DispatchResult {1789 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1790 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;17911792 let mut collection = collection.into_inner();17931794 collection.owner = new_owner;1795 collection.save()1796 }17971798 1799 pub fn check_collection_owner(1800 collection: &NonfungibleHandle<T>,1801 account: &T::CrossAccountId,1802 ) -> DispatchResult {1803 collection1804 .check_is_owner(account)1805 .map_err(Self::map_unique_err_to_proxy)1806 }18071808 1809 pub fn last_collection_idx() -> RmrkCollectionId {1810 <CollectionIndex<T>>::get()1811 }18121813 1814 pub fn unique_collection_id(1815 rmrk_collection_id: RmrkCollectionId,1816 ) -> Result<CollectionId, DispatchError> {1817 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1818 .map_err(|_| <Error<T>>::CollectionUnknown.into())1819 }18201821 1822 pub fn rmrk_collection_id(1823 unique_collection_id: CollectionId,1824 ) -> Result<RmrkCollectionId, DispatchError> {1825 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1826 }18271828 1829 pub fn get_nft_collection(1830 collection_id: CollectionId,1831 ) -> Result<NonfungibleHandle<T>, DispatchError> {1832 let collection = <CollectionHandle<T>>::try_get(collection_id)1833 .map_err(|_| <Error<T>>::CollectionUnknown)?;18341835 match collection.mode {1836 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1837 _ => Err(<Error<T>>::CollectionUnknown.into()),1838 }1839 }18401841 1842 pub fn collection_exists(collection_id: CollectionId) -> bool {1843 <CollectionHandle<T>>::try_get(collection_id).is_ok()1844 }18451846 1847 pub fn get_collection_property(1848 collection_id: CollectionId,1849 key: RmrkProperty,1850 ) -> Result<PropertyValue, DispatchError> {1851 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1852 .get(&Self::get_scoped_property_key(key)?)1853 .ok_or(<Error<T>>::CollectionUnknown)?1854 .clone();18551856 Ok(collection_property)1857 }18581859 1860 pub fn get_collection_property_decoded<V: Decode>(1861 collection_id: CollectionId,1862 key: RmrkProperty,1863 ) -> Result<V, DispatchError> {1864 Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1865 }18661867 1868 1869 1870 pub fn get_collection_type(1871 collection_id: CollectionId,1872 ) -> Result<misc::CollectionType, DispatchError> {1873 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1874 if err != <Error<T>>::CollectionUnknown.into() {1875 <Error<T>>::CorruptedCollectionType.into()1876 } else {1877 err1878 }1879 })1880 }18811882 1883 1884 pub fn ensure_collection_type(1885 collection_id: CollectionId,1886 collection_type: misc::CollectionType,1887 ) -> DispatchResult {1888 let actual_type = Self::get_collection_type(collection_id)?;1889 ensure!(1890 actual_type == collection_type,1891 <CommonError<T>>::NoPermission1892 );18931894 Ok(())1895 }18961897 1898 pub fn get_typed_nft_collection(1899 collection_id: CollectionId,1900 collection_type: misc::CollectionType,1901 ) -> Result<NonfungibleHandle<T>, DispatchError> {1902 Self::ensure_collection_type(collection_id, collection_type)?;19031904 Self::get_nft_collection(collection_id)1905 }19061907 1908 1909 pub fn get_typed_nft_collection_mapped(1910 rmrk_collection_id: RmrkCollectionId,1911 collection_type: misc::CollectionType,1912 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1913 let unique_collection_id = match collection_type {1914 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1915 _ => rmrk_collection_id.into(),1916 };19171918 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;19191920 Ok((collection, unique_collection_id))1921 }19221923 1924 pub fn get_nft_property(1925 collection_id: CollectionId,1926 nft_id: TokenId,1927 key: RmrkProperty,1928 ) -> Result<PropertyValue, DispatchError> {1929 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1930 .get(&Self::get_scoped_property_key(key)?)1931 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1932 .clone();19331934 Ok(nft_property)1935 }19361937 1938 pub fn get_nft_property_decoded<V: Decode>(1939 collection_id: CollectionId,1940 nft_id: TokenId,1941 key: RmrkProperty,1942 ) -> Result<V, DispatchError> {1943 Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1944 }19451946 1947 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1948 <TokenData<T>>::contains_key((collection_id, nft_id))1949 }19501951 1952 1953 1954 pub fn get_nft_type(1955 collection_id: CollectionId,1956 token_id: TokenId,1957 ) -> Result<NftType, DispatchError> {1958 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1959 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1960 }19611962 1963 pub fn ensure_nft_type(1964 collection_id: CollectionId,1965 token_id: TokenId,1966 nft_type: NftType,1967 ) -> DispatchResult {1968 let actual_type = Self::get_nft_type(collection_id, token_id)?;1969 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19701971 Ok(())1972 }19731974 1975 1976 pub fn ensure_nft_owner(1977 collection_id: CollectionId,1978 token_id: TokenId,1979 possible_owner: &T::CrossAccountId,1980 nesting_budget: &dyn budget::Budget,1981 ) -> DispatchResult {1982 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1983 possible_owner.clone(),1984 collection_id,1985 token_id,1986 None,1987 nesting_budget,1988 )1989 .map_err(Self::map_unique_err_to_proxy)?;19901991 ensure!(is_owned, <Error<T>>::NoPermission);19921993 Ok(())1994 }19951996 1997 1998 pub fn filter_user_properties<Key, Value, R, Mapper>(1999 collection_id: CollectionId,2000 token_id: Option<TokenId>,2001 filter_keys: Option<Vec<RmrkPropertyKey>>,2002 mapper: Mapper,2003 ) -> Result<Vec<R>, DispatchError>2004 where2005 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2006 Value: Decode + Default,2007 Mapper: Fn(Key, Value) -> R,2008 {2009 filter_keys2010 .map(|keys| {2011 let properties = keys2012 .into_iter()2013 .filter_map(|key| {2014 let key: Key = key.try_into().ok()?;20152016 let value = match token_id {2017 Some(token_id) => Self::get_nft_property_decoded(2018 collection_id,2019 token_id,2020 UserProperty(key.as_ref()),2021 ),2022 None => Self::get_collection_property_decoded(2023 collection_id,2024 UserProperty(key.as_ref()),2025 ),2026 }2027 .ok()?;20282029 Some(mapper(key, value))2030 })2031 .collect();20322033 Ok(properties)2034 })2035 .unwrap_or_else(|| {2036 let properties =2037 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20382039 Ok(properties)2040 })2041 }20422043 2044 2045 pub fn iterate_user_properties<Key, Value, R, Mapper>(2046 collection_id: CollectionId,2047 token_id: Option<TokenId>,2048 mapper: Mapper,2049 ) -> Result<impl Iterator<Item = R>, DispatchError>2050 where2051 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2052 Value: Decode + Default,2053 Mapper: Fn(Key, Value) -> R,2054 {2055 let properties = match token_id {2056 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2057 None => <PalletCommon<T>>::collection_properties(collection_id),2058 };20592060 let properties = properties.into_iter().filter_map(move |(key, value)| {2061 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20622063 let key: Key = key.to_vec().try_into().ok()?;2064 let value: Value = value.decode().ok()?;20652066 Some(mapper(key, value))2067 });20682069 Ok(properties)2070 }20712072 2073 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2074 map_unique_err_to_proxy! {2075 match err {2076 CommonError::NoPermission => NoPermission,2077 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2078 CommonError::PublicMintingNotAllowed => NoPermission,2079 CommonError::TokenNotFound => NoAvailableNftId,2080 CommonError::ApprovedValueTooLow => NoPermission,2081 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2082 StructureError::TokenNotFound => NoAvailableNftId,2083 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2084 }2085 }2086 }2087}