123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146#![cfg_attr(not(feature = "std"), no_std)]147148use frame_support::{pallet_prelude::*, transactional, 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 #[transactional]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 #[transactional]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 #[transactional]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 #[transactional]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 #[transactional]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 #[transactional]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 #[transactional]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 #[transactional]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 #[transactional]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 #[transactional]957 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]958 pub fn accept_resource(959 origin: OriginFor<T>,960 rmrk_collection_id: RmrkCollectionId,961 rmrk_nft_id: RmrkNftId,962 resource_id: RmrkResourceId,963 ) -> DispatchResult {964 let sender = ensure_signed(origin)?;965 let cross_sender = T::CrossAccountId::from_sub(sender);966967 let collection_id = Self::unique_collection_id(rmrk_collection_id)968 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;969 let collection =970 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;971 collection.check_is_external()?;972973 let nft_id = rmrk_nft_id.into();974975 let budget = budget::Value::new(NESTING_BUDGET);976977 let nft_owner =978 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)979 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;980981 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {982 ensure!(res.pending, <Error<T>>::ResourceNotPending);983 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);984985 res.pending = false;986987 Ok(())988 })?;989990 Self::deposit_event(Event::<T>::ResourceAccepted {991 nft_id: rmrk_nft_id,992 resource_id,993 });994995 Ok(())996 }997998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 #[transactional]1011 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]1012 pub fn accept_resource_removal(1013 origin: OriginFor<T>,1014 rmrk_collection_id: RmrkCollectionId,1015 rmrk_nft_id: RmrkNftId,1016 resource_id: RmrkResourceId,1017 ) -> DispatchResult {1018 let sender = ensure_signed(origin)?;1019 let cross_sender = T::CrossAccountId::from_sub(sender);10201021 let collection_id = Self::unique_collection_id(rmrk_collection_id)1022 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;1023 let collection =1024 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1025 collection.check_is_external()?;10261027 let nft_id = rmrk_nft_id.into();10281029 let budget = budget::Value::new(NESTING_BUDGET);10301031 let nft_owner =1032 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1033 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;10341035 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10361037 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10381039 let resource_info = <PalletNft<T>>::token_aux_property((1040 collection_id,1041 nft_id,1042 RMRK_SCOPE,1043 resource_id_key.clone(),1044 ))1045 .ok_or(<Error<T>>::ResourceDoesntExist)?;10461047 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10481049 ensure!(1050 resource_info.pending_removal,1051 <Error<T>>::ResourceNotPending1052 );10531054 <PalletNft<T>>::remove_token_aux_property(1055 collection_id,1056 nft_id,1057 RMRK_SCOPE,1058 resource_id_key,1059 );10601061 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1062 let base_id = resource.base;10631064 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1065 }10661067 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1068 nft_id: rmrk_nft_id,1069 resource_id,1070 });10711072 Ok(())1073 }10741075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 #[transactional]1092 #[pallet::weight(<SelfWeightOf<T>>::set_property())]1093 pub fn set_property(1094 origin: OriginFor<T>,1095 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,1096 maybe_nft_id: Option<RmrkNftId>,1097 key: RmrkKeyString,1098 value: RmrkValueString,1099 ) -> DispatchResult {1100 let sender = ensure_signed(origin)?;1101 let sender = T::CrossAccountId::from_sub(sender);11021103 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1104 let collection =1105 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1106 collection.check_is_external()?;11071108 let budget = budget::Value::new(NESTING_BUDGET);11091110 match maybe_nft_id {1111 Some(nft_id) => {1112 let token_id: TokenId = nft_id.into();11131114 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1115 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;11161117 <PalletNft<T>>::set_scoped_token_property(1118 collection_id,1119 token_id,1120 RMRK_SCOPE,1121 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1122 )?;1123 }1124 None => {1125 let collection = Self::get_typed_nft_collection(1126 collection_id,1127 misc::CollectionType::Regular,1128 )?;11291130 Self::check_collection_owner(&collection, &sender)?;11311132 <PalletCommon<T>>::set_scoped_collection_property(1133 collection_id,1134 RMRK_SCOPE,1135 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1136 )?;1137 }1138 }11391140 Self::deposit_event(Event::PropertySet {1141 collection_id: rmrk_collection_id,1142 maybe_nft_id,1143 key,1144 value,1145 });11461147 Ok(())1148 }11491150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 #[transactional]1165 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]1166 pub fn set_priority(1167 origin: OriginFor<T>,1168 rmrk_collection_id: RmrkCollectionId,1169 rmrk_nft_id: RmrkNftId,1170 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1171 ) -> DispatchResult {1172 let sender = ensure_signed(origin)?;1173 let sender = T::CrossAccountId::from_sub(sender);11741175 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1176 let nft_id = rmrk_nft_id.into();11771178 let collection =1179 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1180 collection.check_is_external()?;11811182 let budget = budget::Value::new(NESTING_BUDGET);11831184 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1185 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;11861187 <PalletNft<T>>::set_scoped_token_property(1188 collection_id,1189 nft_id,1190 RMRK_SCOPE,1191 Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1192 )?;11931194 Self::deposit_event(Event::<T>::PrioritySet {1195 collection_id: rmrk_collection_id,1196 nft_id: rmrk_nft_id,1197 });11981199 Ok(())1200 }12011202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 #[transactional]1216 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1217 pub fn add_basic_resource(1218 origin: OriginFor<T>,1219 rmrk_collection_id: RmrkCollectionId,1220 nft_id: RmrkNftId,1221 resource: RmrkBasicResource,1222 ) -> DispatchResult {1223 let sender = ensure_signed(origin.clone())?;12241225 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1226 let collection =1227 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1228 collection.check_is_external()?;12291230 let resource_id = Self::resource_add(1231 sender,1232 collection_id,1233 nft_id.into(),1234 RmrkResourceTypes::Basic(resource),1235 )?;12361237 Self::deposit_event(Event::ResourceAdded {1238 nft_id,1239 resource_id,1240 });1241 Ok(())1242 }12431244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 #[transactional]1258 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1259 pub fn add_composable_resource(1260 origin: OriginFor<T>,1261 rmrk_collection_id: RmrkCollectionId,1262 nft_id: RmrkNftId,1263 resource: RmrkComposableResource,1264 ) -> DispatchResult {1265 let sender = ensure_signed(origin.clone())?;12661267 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1268 let collection =1269 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1270 collection.check_is_external()?;12711272 let base_id = resource.base;12731274 let resource_id = Self::resource_add(1275 sender,1276 collection_id,1277 nft_id.into(),1278 RmrkResourceTypes::Composable(resource),1279 )?;12801281 <PalletNft<T>>::try_mutate_token_aux_property(1282 collection_id,1283 nft_id.into(),1284 RMRK_SCOPE,1285 Self::get_scoped_property_key(AssociatedBases)?,1286 |value| -> DispatchResult {1287 let mut bases: BasesMap = match value {1288 Some(value) => Self::decode_property_value(value)?,1289 None => BasesMap::new(),1290 };12911292 *bases.entry(base_id).or_insert(0) += 1;12931294 *value = Some(Self::encode_property_value(&bases)?);1295 Ok(())1296 },1297 )?;12981299 Self::deposit_event(Event::ResourceAdded {1300 nft_id,1301 resource_id,1302 });1303 Ok(())1304 }13051306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 #[transactional]1320 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1321 pub fn add_slot_resource(1322 origin: OriginFor<T>,1323 rmrk_collection_id: RmrkCollectionId,1324 nft_id: RmrkNftId,1325 resource: RmrkSlotResource,1326 ) -> DispatchResult {1327 let sender = ensure_signed(origin.clone())?;13281329 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1330 let collection =1331 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1332 collection.check_is_external()?;13331334 let resource_id = Self::resource_add(1335 sender,1336 collection_id,1337 nft_id.into(),1338 RmrkResourceTypes::Slot(resource),1339 )?;13401341 Self::deposit_event(Event::ResourceAdded {1342 nft_id,1343 resource_id,1344 });1345 Ok(())1346 }13471348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 #[transactional]1361 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1362 pub fn remove_resource(1363 origin: OriginFor<T>,1364 rmrk_collection_id: RmrkCollectionId,1365 nft_id: RmrkNftId,1366 resource_id: RmrkResourceId,1367 ) -> DispatchResult {1368 let sender = ensure_signed(origin.clone())?;13691370 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1371 let collection =1372 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1373 collection.check_is_external()?;13741375 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13761377 Self::deposit_event(Event::ResourceRemoval {1378 nft_id,1379 resource_id,1380 });1381 Ok(())1382 }1383 }1384}13851386impl<T: Config> Pallet<T> {1387 1388 pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1389 let key = rmrk_key.to_key::<T>()?;13901391 let scoped_key = RMRK_SCOPE1392 .apply(key)1393 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;13941395 Ok(scoped_key)1396 }13971398 1399 1400 pub fn encode_rmrk_property<E: Encode>(1401 rmrk_key: RmrkProperty,1402 value: &E,1403 ) -> Result<Property, DispatchError> {1404 let key = rmrk_key.to_key::<T>()?;14051406 let value = Self::encode_property_value(value)?;14071408 let property = Property { key, value };14091410 Ok(property)1411 }14121413 1414 pub fn encode_property_value<E: Encode, S: Get<u32>>(1415 value: &E,1416 ) -> Result<BoundedBytes<S>, DispatchError> {1417 let value = value1418 .encode()1419 .try_into()1420 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;14211422 Ok(value)1423 }14241425 1426 pub fn decode_property_value<D: Decode, S: Get<u32>>(1427 vec: &BoundedBytes<S>,1428 ) -> Result<D, DispatchError> {1429 vec.decode()1430 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1431 }14321433 1434 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1435 where1436 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1437 {1438 vec.rebind()1439 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1440 }14411442 1443 1444 1445 fn init_collection(1446 sender: T::CrossAccountId,1447 data: CreateCollectionData<T::AccountId>,1448 properties: impl Iterator<Item = Property>,1449 ) -> Result<CollectionId, DispatchError> {1450 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);14511452 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1453 return Err(<Error<T>>::NoAvailableCollectionId.into());1454 }14551456 <PalletCommon<T>>::set_scoped_collection_properties(1457 collection_id?,1458 RMRK_SCOPE,1459 properties,1460 )?;14611462 collection_id1463 }14641465 1466 1467 1468 pub fn create_nft(1469 sender: &T::CrossAccountId,1470 owner: &T::CrossAccountId,1471 collection: &NonfungibleHandle<T>,1472 properties: impl Iterator<Item = Property>,1473 ) -> Result<TokenId, DispatchError> {1474 let data = CreateNftExData {1475 properties: BoundedVec::default(),1476 owner: owner.clone(),1477 };14781479 let budget = budget::Value::new(NESTING_BUDGET);14801481 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;14821483 let nft_id = <PalletNft<T>>::current_token_id(collection.id);14841485 <PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;14861487 Ok(nft_id)1488 }14891490 1491 1492 1493 fn destroy_nft(1494 sender: T::CrossAccountId,1495 collection_id: CollectionId,1496 token_id: TokenId,1497 max_burns: u32,1498 error_if_not_owned: Error<T>,1499 ) -> DispatchResultWithPostInfo {1500 let collection =1501 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;15021503 let token_data =1504 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;15051506 let from = token_data.owner;15071508 let owner_check_budget = budget::Value::new(NESTING_BUDGET);15091510 ensure!(1511 <PalletStructure<T>>::check_indirectly_owned(1512 sender.clone(),1513 collection_id,1514 token_id,1515 None,1516 &owner_check_budget1517 )?,1518 error_if_not_owned,1519 );15201521 let burns_budget = budget::Value::new(max_burns);1522 let breadth_budget = budget::Value::new(max_burns);15231524 <PalletNft<T>>::burn_recursively(1525 &collection,1526 &from,1527 token_id,1528 &burns_budget,1529 &breadth_budget,1530 )1531 }15321533 1534 fn insert_pending_child(1535 target: (CollectionId, TokenId),1536 child: (RmrkCollectionId, RmrkNftId),1537 ) -> DispatchResult {1538 Self::mutate_pending_children(target, |pending_children| {1539 pending_children.insert(child);1540 })1541 }15421543 1544 fn remove_pending_child(1545 target: (CollectionId, TokenId),1546 child: (RmrkCollectionId, RmrkNftId),1547 ) -> DispatchResult {1548 Self::mutate_pending_children(target, |pending_children| {1549 pending_children.remove(&child);1550 })1551 }15521553 1554 1555 fn mutate_pending_children(1556 (target_collection_id, target_nft_id): (CollectionId, TokenId),1557 f: impl FnOnce(&mut PendingChildrenSet),1558 ) -> DispatchResult {1559 <PalletNft<T>>::try_mutate_token_aux_property(1560 target_collection_id,1561 target_nft_id,1562 RMRK_SCOPE,1563 Self::get_scoped_property_key(PendingChildren)?,1564 |pending_children| -> DispatchResult {1565 let mut map = match pending_children {1566 Some(map) => Self::decode_property_value(map)?,1567 None => PendingChildrenSet::new(),1568 };15691570 f(&mut map);15711572 *pending_children = Some(Self::encode_property_value(&map)?);15731574 Ok(())1575 },1576 )1577 }15781579 1580 1581 fn iterate_pending_children(1582 collection_id: CollectionId,1583 nft_id: TokenId,1584 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1585 let property = <PalletNft<T>>::token_aux_property((1586 collection_id,1587 nft_id,1588 RMRK_SCOPE,1589 Self::get_scoped_property_key(PendingChildren)?,1590 ));15911592 let pending_children = match property {1593 Some(map) => Self::decode_property_value(&map)?,1594 None => PendingChildrenSet::new(),1595 };15961597 Ok(pending_children.into_iter())1598 }15991600 1601 1602 1603 1604 fn acquire_next_resource_id(1605 collection_id: CollectionId,1606 nft_id: TokenId,1607 ) -> Result<RmrkResourceId, DispatchError> {1608 let resource_id: RmrkResourceId =1609 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;16101611 let next_id = resource_id1612 .checked_add(1)1613 .ok_or(<Error<T>>::NoAvailableResourceId)?;16141615 <PalletNft<T>>::set_scoped_token_property(1616 collection_id,1617 nft_id,1618 RMRK_SCOPE,1619 Self::encode_rmrk_property(NextResourceId, &next_id)?,1620 )?;16211622 Ok(resource_id)1623 }16241625 1626 1627 fn resource_add(1628 sender: T::AccountId,1629 collection_id: CollectionId,1630 nft_id: TokenId,1631 resource: RmrkResourceTypes,1632 ) -> Result<RmrkResourceId, DispatchError> {1633 let collection =1634 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1635 ensure!(collection.owner == sender, Error::<T>::NoPermission);16361637 let sender = T::CrossAccountId::from_sub(sender);1638 let budget = budget::Value::new(NESTING_BUDGET);16391640 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1641 .map_err(Self::map_unique_err_to_proxy)?;16421643 let pending = sender != nft_owner;16441645 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16461647 let resource_info = RmrkResourceInfo {1648 id,1649 resource,1650 pending,1651 pending_removal: false,1652 };16531654 <PalletNft<T>>::try_mutate_token_aux_property(1655 collection_id,1656 nft_id,1657 RMRK_SCOPE,1658 Self::get_scoped_property_key(ResourceId(id))?,1659 |value| -> DispatchResult {1660 *value = Some(Self::encode_property_value(&resource_info)?);16611662 Ok(())1663 },1664 )?;16651666 Ok(id)1667 }16681669 1670 1671 fn resource_remove(1672 sender: T::AccountId,1673 collection_id: CollectionId,1674 nft_id: TokenId,1675 resource_id: RmrkResourceId,1676 ) -> DispatchResult {1677 let collection =1678 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1679 ensure!(collection.owner == sender, Error::<T>::NoPermission);16801681 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16821683 let resource = <PalletNft<T>>::token_aux_property((1684 collection_id,1685 nft_id,1686 RMRK_SCOPE,1687 resource_id_key.clone(),1688 ))1689 .ok_or(<Error<T>>::ResourceDoesntExist)?;16901691 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;16921693 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1694 let topmost_owner =1695 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;16961697 let sender = T::CrossAccountId::from_sub(sender);1698 if topmost_owner == sender {1699 <PalletNft<T>>::remove_token_aux_property(1700 collection_id,1701 nft_id,1702 RMRK_SCOPE,1703 Self::get_scoped_property_key(ResourceId(resource_id))?,1704 );17051706 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1707 let base_id = resource.base;17081709 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1710 }1711 } else {1712 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1713 res.pending_removal = true;17141715 Ok(())1716 })?;1717 }17181719 Ok(())1720 }17211722 1723 1724 fn remove_associated_base_id(1725 collection_id: CollectionId,1726 nft_id: TokenId,1727 base_id: RmrkBaseId,1728 ) -> DispatchResult {1729 <PalletNft<T>>::try_mutate_token_aux_property(1730 collection_id,1731 nft_id,1732 RMRK_SCOPE,1733 Self::get_scoped_property_key(AssociatedBases)?,1734 |value| -> DispatchResult {1735 let mut bases: BasesMap = match value {1736 Some(value) => Self::decode_property_value(value)?,1737 None => BasesMap::new(),1738 };17391740 let remaining = bases.get(&base_id);17411742 if let Some(remaining) = remaining {1743 if let Some(0) | None = remaining.checked_sub(1) {1744 bases.remove(&base_id);1745 }1746 }17471748 *value = Some(Self::encode_property_value(&bases)?);1749 Ok(())1750 },1751 )1752 }17531754 1755 fn try_mutate_resource_info(1756 collection_id: CollectionId,1757 nft_id: TokenId,1758 resource_id: RmrkResourceId,1759 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1760 ) -> DispatchResult {1761 <PalletNft<T>>::try_mutate_token_aux_property(1762 collection_id,1763 nft_id,1764 RMRK_SCOPE,1765 Self::get_scoped_property_key(ResourceId(resource_id))?,1766 |value| match value {1767 Some(value) => {1768 let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17691770 f(&mut resource_info)?;17711772 *value = Self::encode_property_value(&resource_info)?;17731774 Ok(())1775 }1776 None => Err(<Error<T>>::ResourceDoesntExist.into()),1777 },1778 )1779 }17801781 1782 fn change_collection_owner(1783 collection_id: CollectionId,1784 collection_type: misc::CollectionType,1785 sender: T::AccountId,1786 new_owner: T::AccountId,1787 ) -> DispatchResult {1788 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1789 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;17901791 let mut collection = collection.into_inner();17921793 collection.owner = new_owner;1794 collection.save()1795 }17961797 1798 pub fn check_collection_owner(1799 collection: &NonfungibleHandle<T>,1800 account: &T::CrossAccountId,1801 ) -> DispatchResult {1802 collection1803 .check_is_owner(account)1804 .map_err(Self::map_unique_err_to_proxy)1805 }18061807 1808 pub fn last_collection_idx() -> RmrkCollectionId {1809 <CollectionIndex<T>>::get()1810 }18111812 1813 pub fn unique_collection_id(1814 rmrk_collection_id: RmrkCollectionId,1815 ) -> Result<CollectionId, DispatchError> {1816 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1817 .map_err(|_| <Error<T>>::CollectionUnknown.into())1818 }18191820 1821 pub fn rmrk_collection_id(1822 unique_collection_id: CollectionId,1823 ) -> Result<RmrkCollectionId, DispatchError> {1824 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1825 }18261827 1828 pub fn get_nft_collection(1829 collection_id: CollectionId,1830 ) -> Result<NonfungibleHandle<T>, DispatchError> {1831 let collection = <CollectionHandle<T>>::try_get(collection_id)1832 .map_err(|_| <Error<T>>::CollectionUnknown)?;18331834 match collection.mode {1835 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1836 _ => Err(<Error<T>>::CollectionUnknown.into()),1837 }1838 }18391840 1841 pub fn collection_exists(collection_id: CollectionId) -> bool {1842 <CollectionHandle<T>>::try_get(collection_id).is_ok()1843 }18441845 1846 pub fn get_collection_property(1847 collection_id: CollectionId,1848 key: RmrkProperty,1849 ) -> Result<PropertyValue, DispatchError> {1850 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1851 .get(&Self::get_scoped_property_key(key)?)1852 .ok_or(<Error<T>>::CollectionUnknown)?1853 .clone();18541855 Ok(collection_property)1856 }18571858 1859 pub fn get_collection_property_decoded<V: Decode>(1860 collection_id: CollectionId,1861 key: RmrkProperty,1862 ) -> Result<V, DispatchError> {1863 Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1864 }18651866 1867 1868 1869 pub fn get_collection_type(1870 collection_id: CollectionId,1871 ) -> Result<misc::CollectionType, DispatchError> {1872 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1873 if err != <Error<T>>::CollectionUnknown.into() {1874 <Error<T>>::CorruptedCollectionType.into()1875 } else {1876 err1877 }1878 })1879 }18801881 1882 1883 pub fn ensure_collection_type(1884 collection_id: CollectionId,1885 collection_type: misc::CollectionType,1886 ) -> DispatchResult {1887 let actual_type = Self::get_collection_type(collection_id)?;1888 ensure!(1889 actual_type == collection_type,1890 <CommonError<T>>::NoPermission1891 );18921893 Ok(())1894 }18951896 1897 pub fn get_typed_nft_collection(1898 collection_id: CollectionId,1899 collection_type: misc::CollectionType,1900 ) -> Result<NonfungibleHandle<T>, DispatchError> {1901 Self::ensure_collection_type(collection_id, collection_type)?;19021903 Self::get_nft_collection(collection_id)1904 }19051906 1907 1908 pub fn get_typed_nft_collection_mapped(1909 rmrk_collection_id: RmrkCollectionId,1910 collection_type: misc::CollectionType,1911 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1912 let unique_collection_id = match collection_type {1913 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1914 _ => rmrk_collection_id.into(),1915 };19161917 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;19181919 Ok((collection, unique_collection_id))1920 }19211922 1923 pub fn get_nft_property(1924 collection_id: CollectionId,1925 nft_id: TokenId,1926 key: RmrkProperty,1927 ) -> Result<PropertyValue, DispatchError> {1928 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1929 .get(&Self::get_scoped_property_key(key)?)1930 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1931 .clone();19321933 Ok(nft_property)1934 }19351936 1937 pub fn get_nft_property_decoded<V: Decode>(1938 collection_id: CollectionId,1939 nft_id: TokenId,1940 key: RmrkProperty,1941 ) -> Result<V, DispatchError> {1942 Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1943 }19441945 1946 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1947 <TokenData<T>>::contains_key((collection_id, nft_id))1948 }19491950 1951 1952 1953 pub fn get_nft_type(1954 collection_id: CollectionId,1955 token_id: TokenId,1956 ) -> Result<NftType, DispatchError> {1957 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1958 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1959 }19601961 1962 pub fn ensure_nft_type(1963 collection_id: CollectionId,1964 token_id: TokenId,1965 nft_type: NftType,1966 ) -> DispatchResult {1967 let actual_type = Self::get_nft_type(collection_id, token_id)?;1968 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19691970 Ok(())1971 }19721973 1974 1975 pub fn ensure_nft_owner(1976 collection_id: CollectionId,1977 token_id: TokenId,1978 possible_owner: &T::CrossAccountId,1979 nesting_budget: &dyn budget::Budget,1980 ) -> DispatchResult {1981 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1982 possible_owner.clone(),1983 collection_id,1984 token_id,1985 None,1986 nesting_budget,1987 )1988 .map_err(Self::map_unique_err_to_proxy)?;19891990 ensure!(is_owned, <Error<T>>::NoPermission);19911992 Ok(())1993 }19941995 1996 1997 pub fn filter_user_properties<Key, Value, R, Mapper>(1998 collection_id: CollectionId,1999 token_id: Option<TokenId>,2000 filter_keys: Option<Vec<RmrkPropertyKey>>,2001 mapper: Mapper,2002 ) -> Result<Vec<R>, DispatchError>2003 where2004 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2005 Value: Decode + Default,2006 Mapper: Fn(Key, Value) -> R,2007 {2008 filter_keys2009 .map(|keys| {2010 let properties = keys2011 .into_iter()2012 .filter_map(|key| {2013 let key: Key = key.try_into().ok()?;20142015 let value = match token_id {2016 Some(token_id) => Self::get_nft_property_decoded(2017 collection_id,2018 token_id,2019 UserProperty(key.as_ref()),2020 ),2021 None => Self::get_collection_property_decoded(2022 collection_id,2023 UserProperty(key.as_ref()),2024 ),2025 }2026 .ok()?;20272028 Some(mapper(key, value))2029 })2030 .collect();20312032 Ok(properties)2033 })2034 .unwrap_or_else(|| {2035 let properties =2036 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20372038 Ok(properties)2039 })2040 }20412042 2043 2044 pub fn iterate_user_properties<Key, Value, R, Mapper>(2045 collection_id: CollectionId,2046 token_id: Option<TokenId>,2047 mapper: Mapper,2048 ) -> Result<impl Iterator<Item = R>, DispatchError>2049 where2050 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2051 Value: Decode + Default,2052 Mapper: Fn(Key, Value) -> R,2053 {2054 let properties = match token_id {2055 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2056 None => <PalletCommon<T>>::collection_properties(collection_id),2057 };20582059 let properties = properties.into_iter().filter_map(move |(key, value)| {2060 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20612062 let key: Key = key.to_vec().try_into().ok()?;2063 let value: Value = value.decode().ok()?;20642065 Some(mapper(key, value))2066 });20672068 Ok(properties)2069 }20702071 2072 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2073 map_unique_err_to_proxy! {2074 match err {2075 CommonError::NoPermission => NoPermission,2076 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2077 CommonError::PublicMintingNotAllowed => NoPermission,2078 CommonError::TokenNotFound => NoAvailableNftId,2079 CommonError::ApprovedValueTooLow => NoPermission,2080 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2081 StructureError::TokenNotFound => NoAvailableNftId,2082 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2083 }2084 }2085 }2086}