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::*;193194 #[pallet::config]195 pub trait Config:196 frame_system::Config197 + pallet_common::Config198 + pallet_nonfungible::Config199 + pallet_evm::Config200 {201 202 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;203204 205 type WeightInfo: WeightInfo;206 }207208 209 #[pallet::storage]210 #[pallet::getter(fn collection_index)]211 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;212213 214 #[pallet::storage]215 pub type UniqueCollectionId<T: Config> =216 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;217218 #[pallet::pallet]219 #[pallet::generate_store(pub(super) trait Store)]220 pub struct Pallet<T>(_);221222 #[pallet::event]223 #[pallet::generate_deposit(pub(super) fn deposit_event)]224 pub enum Event<T: Config> {225 CollectionCreated {226 issuer: T::AccountId,227 collection_id: RmrkCollectionId,228 },229 CollectionDestroyed {230 issuer: T::AccountId,231 collection_id: RmrkCollectionId,232 },233 IssuerChanged {234 old_issuer: T::AccountId,235 new_issuer: T::AccountId,236 collection_id: RmrkCollectionId,237 },238 CollectionLocked {239 issuer: T::AccountId,240 collection_id: RmrkCollectionId,241 },242 NftMinted {243 owner: T::AccountId,244 collection_id: RmrkCollectionId,245 nft_id: RmrkNftId,246 },247 NFTBurned {248 owner: T::AccountId,249 nft_id: RmrkNftId,250 },251 NFTSent {252 sender: T::AccountId,253 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,254 collection_id: RmrkCollectionId,255 nft_id: RmrkNftId,256 approval_required: bool,257 },258 NFTAccepted {259 sender: T::AccountId,260 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,261 collection_id: RmrkCollectionId,262 nft_id: RmrkNftId,263 },264 NFTRejected {265 sender: T::AccountId,266 collection_id: RmrkCollectionId,267 nft_id: RmrkNftId,268 },269 PropertySet {270 collection_id: RmrkCollectionId,271 maybe_nft_id: Option<RmrkNftId>,272 key: RmrkKeyString,273 value: RmrkValueString,274 },275 ResourceAdded {276 nft_id: RmrkNftId,277 resource_id: RmrkResourceId,278 },279 ResourceRemoval {280 nft_id: RmrkNftId,281 resource_id: RmrkResourceId,282 },283 ResourceAccepted {284 nft_id: RmrkNftId,285 resource_id: RmrkResourceId,286 },287 ResourceRemovalAccepted {288 nft_id: RmrkNftId,289 resource_id: RmrkResourceId,290 },291 PrioritySet {292 collection_id: RmrkCollectionId,293 nft_id: RmrkNftId,294 },295 }296297 #[pallet::error]298 pub enum Error<T> {299 300 301 CorruptedCollectionType,302 303 304 RmrkPropertyKeyIsTooLong,305 306 RmrkPropertyValueIsTooLong,307 308 RmrkPropertyIsNotFound,309 310 311 UnableToDecodeRmrkData,312313 314 315 CollectionNotEmpty,316 317 NoAvailableCollectionId,318 319 NoAvailableNftId,320 321 CollectionUnknown,322 323 NoPermission,324 325 NonTransferable,326 327 CollectionFullOrLocked,328 329 ResourceDoesntExist,330 331 332 CannotSendToDescendentOrSelf,333 334 CannotAcceptNonOwnedNft,335 336 CannotRejectNonOwnedNft,337 338 CannotRejectNonPendingNft,339 340 ResourceNotPending,341 342 NoAvailableResourceId,343 }344345 #[pallet::call]346 impl<T: Config> Pallet<T> {347 348349 350 351 352 353 354 355 356 357 358 359 360 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]361 pub fn create_collection(362 origin: OriginFor<T>,363 metadata: RmrkString,364 max: Option<u32>,365 symbol: RmrkCollectionSymbol,366 ) -> DispatchResult {367 let sender = ensure_signed(origin)?;368369 let limits = CollectionLimits {370 owner_can_transfer: Some(false),371 token_limit: max,372 ..Default::default()373 };374375 let data = CreateCollectionData {376 limits: Some(limits),377 token_prefix: symbol378 .into_inner()379 .try_into()380 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,381 permissions: Some(CollectionPermissions {382 nesting: Some(NestingPermissions {383 token_owner: true,384 collection_admin: false,385 restricted: None,386 #[cfg(feature = "runtime-benchmarks")]387 permissive: false,388 }),389 ..Default::default()390 }),391 ..Default::default()392 };393394 let unique_collection_id = Self::init_collection(395 T::CrossAccountId::from_sub(sender.clone()),396 data,397 [398 Self::encode_rmrk_property(Metadata, &metadata)?,399 Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,400 ]401 .into_iter(),402 )?;403 let rmrk_collection_id = <CollectionIndex<T>>::get();404405 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);406407 <PalletCommon<T>>::set_scoped_collection_property(408 unique_collection_id,409 RMRK_SCOPE,410 Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,411 )?;412413 <CollectionIndex<T>>::mutate(|n| *n += 1);414415 Self::deposit_event(Event::CollectionCreated {416 issuer: sender,417 collection_id: rmrk_collection_id,418 });419420 Ok(())421 }422423 424 425 426 427 428 429 430 431 432 433 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]434 pub fn destroy_collection(435 origin: OriginFor<T>,436 collection_id: RmrkCollectionId,437 ) -> DispatchResult {438 let sender = ensure_signed(origin)?;439 let cross_sender = T::CrossAccountId::from_sub(sender.clone());440441 let collection = Self::get_typed_nft_collection(442 Self::unique_collection_id(collection_id)?,443 misc::CollectionType::Regular,444 )?;445 collection.check_is_external()?;446447 <PalletNft<T>>::destroy_collection(collection, &cross_sender)448 .map_err(Self::map_unique_err_to_proxy)?;449450 Self::deposit_event(Event::CollectionDestroyed {451 issuer: sender,452 collection_id,453 });454455 Ok(())456 }457458 459 460 461 462 463 464 465 466 467 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]468 pub fn change_collection_issuer(469 origin: OriginFor<T>,470 collection_id: RmrkCollectionId,471 new_issuer: <T::Lookup as StaticLookup>::Source,472 ) -> DispatchResult {473 let sender = ensure_signed(origin)?;474475 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;476 collection.check_is_external()?;477478 let new_issuer = T::Lookup::lookup(new_issuer)?;479480 Self::change_collection_owner(481 Self::unique_collection_id(collection_id)?,482 misc::CollectionType::Regular,483 sender.clone(),484 new_issuer.clone(),485 )?;486487 Self::deposit_event(Event::IssuerChanged {488 old_issuer: sender,489 new_issuer,490 collection_id,491 });492493 Ok(())494 }495496 497 498 499 500 501 502 503 504 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]505 pub fn lock_collection(506 origin: OriginFor<T>,507 collection_id: RmrkCollectionId,508 ) -> DispatchResult {509 let sender = ensure_signed(origin)?;510 let cross_sender = T::CrossAccountId::from_sub(sender.clone());511512 let collection = Self::get_typed_nft_collection(513 Self::unique_collection_id(collection_id)?,514 misc::CollectionType::Regular,515 )?;516 collection.check_is_external()?;517518 Self::check_collection_owner(&collection, &cross_sender)?;519520 let token_count = collection.total_supply();521522 let mut collection = collection.into_inner();523 collection.limits.token_limit = Some(token_count);524 collection.save()?;525526 Self::deposit_event(Event::CollectionLocked {527 issuer: sender,528 collection_id,529 });530531 Ok(())532 }533534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]549 pub fn mint_nft(550 origin: OriginFor<T>,551 owner: Option<T::AccountId>,552 collection_id: RmrkCollectionId,553 recipient: Option<T::AccountId>,554 royalty_amount: Option<Permill>,555 metadata: RmrkString,556 transferable: bool,557 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,558 ) -> DispatchResult {559 let sender = ensure_signed(origin)?;560 let cross_sender = T::CrossAccountId::from_sub(sender.clone());561562 let owner = owner.unwrap_or(sender.clone());563 let cross_owner = T::CrossAccountId::from_sub(owner.clone());564565 let collection = Self::get_typed_nft_collection(566 Self::unique_collection_id(collection_id)?,567 misc::CollectionType::Regular,568 )?;569 collection.check_is_external()?;570571 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {572 recipient: recipient.unwrap_or_else(|| owner.clone()),573 amount,574 });575576 let nft_id = Self::create_nft(577 &cross_sender,578 &cross_owner,579 &collection,580 [581 Self::encode_rmrk_property(TokenType, &NftType::Regular)?,582 Self::encode_rmrk_property(Transferable, &transferable)?,583 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,584 Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,585 Self::encode_rmrk_property(Metadata, &metadata)?,586 Self::encode_rmrk_property(Equipped, &false)?,587 Self::encode_rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,588 Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,589 Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,590 Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,591 ]592 .into_iter(),593 )594 .map_err(|err| match err {595 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),596 err => Self::map_unique_err_to_proxy(err),597 })?;598599 if let Some(resources) = resources {600 for resource in resources {601 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;602 }603 }604605 Self::deposit_event(Event::NftMinted {606 owner,607 collection_id,608 nft_id: nft_id.0,609 });610611 Ok(())612 }613614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]632 pub fn burn_nft(633 origin: OriginFor<T>,634 collection_id: RmrkCollectionId,635 nft_id: RmrkNftId,636 max_burns: u32,637 ) -> DispatchResult {638 let sender = ensure_signed(origin)?;639 let cross_sender = T::CrossAccountId::from_sub(sender.clone());640641 let collection = Self::get_typed_nft_collection(642 Self::unique_collection_id(collection_id)?,643 misc::CollectionType::Regular,644 )?;645 collection.check_is_external()?;646647 Self::destroy_nft(648 cross_sender,649 Self::unique_collection_id(collection_id)?,650 nft_id.into(),651 max_burns,652 <Error<T>>::NoPermission,653 )654 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;655656 Self::deposit_event(Event::NFTBurned {657 owner: sender,658 nft_id,659 });660661 Ok(())662 }663664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 #[pallet::weight(<SelfWeightOf<T>>::send())]679 pub fn send(680 origin: OriginFor<T>,681 rmrk_collection_id: RmrkCollectionId,682 rmrk_nft_id: RmrkNftId,683 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,684 ) -> DispatchResult {685 let sender = ensure_signed(origin.clone())?;686 let cross_sender = T::CrossAccountId::from_sub(sender.clone());687688 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;689 let nft_id = rmrk_nft_id.into();690691 let collection =692 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;693 collection.check_is_external()?;694695 let token_data =696 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;697698 let from = token_data.owner;699700 ensure!(701 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,702 <Error<T>>::NonTransferable703 );704705 ensure!(706 Self::get_nft_property_decoded::<Option<PendingTarget>>(707 collection_id,708 nft_id,709 RmrkProperty::PendingNftAccept710 )?711 .is_none(),712 <Error<T>>::NoPermission713 );714715 let target_owner;716 let approval_required;717718 match new_owner {719 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {720 target_owner = T::CrossAccountId::from_sub(account_id.clone());721 approval_required = false;722 }723 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(724 target_collection_id,725 target_nft_id,726 ) => {727 let target_collection_id = Self::unique_collection_id(target_collection_id)?;728729 let target_nft_budget = budget::Value::new(NESTING_BUDGET);730731 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(732 target_collection_id,733 target_nft_id.into(),734 Some((collection_id, nft_id)),735 &target_nft_budget,736 )737 .map_err(Self::map_unique_err_to_proxy)?;738739 approval_required = cross_sender != target_nft_owner;740741 if approval_required {742 target_owner = target_nft_owner;743744 <PalletNft<T>>::set_scoped_token_property(745 collection.id,746 nft_id,747 RMRK_SCOPE,748 Self::encode_rmrk_property::<Option<PendingTarget>>(749 PendingNftAccept,750 &Some((target_collection_id, target_nft_id.into())),751 )?,752 )?;753754 Self::insert_pending_child(755 (target_collection_id, target_nft_id.into()),756 (rmrk_collection_id, rmrk_nft_id),757 )?;758 } else {759 target_owner = T::CrossTokenAddressMapping::token_to_address(760 target_collection_id,761 target_nft_id.into(),762 );763 }764 }765 }766767 let src_nft_budget = budget::Value::new(NESTING_BUDGET);768769 <PalletNft<T>>::transfer_from(770 &collection,771 &cross_sender,772 &from,773 &target_owner,774 nft_id,775 &src_nft_budget,776 )777 .map_err(Self::map_unique_err_to_proxy)?;778779 Self::deposit_event(Event::NFTSent {780 sender,781 recipient: new_owner,782 collection_id: rmrk_collection_id,783 nft_id: rmrk_nft_id,784 approval_required,785 });786787 Ok(())788 }789790 791 792 793 794 795 796 797 798 799 800 801 802 803 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]804 pub fn accept_nft(805 origin: OriginFor<T>,806 rmrk_collection_id: RmrkCollectionId,807 rmrk_nft_id: RmrkNftId,808 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,809 ) -> DispatchResult {810 let sender = ensure_signed(origin.clone())?;811 let cross_sender = T::CrossAccountId::from_sub(sender.clone());812813 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;814 let nft_id = rmrk_nft_id.into();815816 let collection =817 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;818 collection.check_is_external()?;819820 let new_cross_owner = match new_owner {821 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {822 T::CrossAccountId::from_sub(account_id.clone())823 }824 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(825 target_collection_id,826 target_nft_id,827 ) => {828 let target_collection_id = Self::unique_collection_id(target_collection_id)?;829830 T::CrossTokenAddressMapping::token_to_address(831 target_collection_id,832 TokenId(target_nft_id),833 )834 }835 };836837 let budget = budget::Value::new(NESTING_BUDGET);838839 <PalletNft<T>>::transfer(840 &collection,841 &cross_sender,842 &new_cross_owner,843 nft_id,844 &budget,845 )846 .map_err(|err| {847 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {848 <Error<T>>::CannotAcceptNonOwnedNft.into()849 } else {850 Self::map_unique_err_to_proxy(err)851 }852 })?;853854 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(855 collection_id,856 nft_id,857 RmrkProperty::PendingNftAccept,858 )?;859860 if let Some(pending_target) = pending_target {861 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;862863 <PalletNft<T>>::set_scoped_token_property(864 collection.id,865 nft_id,866 RMRK_SCOPE,867 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,868 )?;869 }870871 Self::deposit_event(Event::NFTAccepted {872 sender,873 recipient: new_owner,874 collection_id: rmrk_collection_id,875 nft_id: rmrk_nft_id,876 });877878 Ok(())879 }880881 882 883 884 885 886 887 888 889 890 891 892 893 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]894 pub fn reject_nft(895 origin: OriginFor<T>,896 rmrk_collection_id: RmrkCollectionId,897 rmrk_nft_id: RmrkNftId,898 ) -> DispatchResult {899 let sender = ensure_signed(origin)?;900 let cross_sender = T::CrossAccountId::from_sub(sender.clone());901902 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;903 let nft_id = rmrk_nft_id.into();904905 let collection =906 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;907 collection.check_is_external()?;908909 ensure!(910 <TokenData<T>>::get((collection_id, nft_id)).is_some(),911 <Error<T>>::NoAvailableNftId912 );913914 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(915 collection_id,916 nft_id,917 RmrkProperty::PendingNftAccept,918 )?;919920 match pending_target {921 Some(pending_target) => {922 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?923 }924 None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),925 }926927 Self::destroy_nft(928 cross_sender,929 collection_id,930 nft_id,931 NESTING_BUDGET,932 <Error<T>>::CannotRejectNonOwnedNft,933 )934 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;935936 Self::deposit_event(Event::NFTRejected {937 sender,938 collection_id: rmrk_collection_id,939 nft_id: rmrk_nft_id,940 });941942 Ok(())943 }944945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]961 pub fn accept_resource(962 origin: OriginFor<T>,963 rmrk_collection_id: RmrkCollectionId,964 rmrk_nft_id: RmrkNftId,965 resource_id: RmrkResourceId,966 ) -> DispatchResult {967 let sender = ensure_signed(origin)?;968 let cross_sender = T::CrossAccountId::from_sub(sender);969970 let collection_id = Self::unique_collection_id(rmrk_collection_id)971 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;972 let collection =973 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;974 collection.check_is_external()?;975976 let nft_id = rmrk_nft_id.into();977978 let budget = budget::Value::new(NESTING_BUDGET);979980 let nft_owner =981 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)982 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;983984 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {985 ensure!(res.pending, <Error<T>>::ResourceNotPending);986 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);987988 res.pending = false;989990 Ok(())991 })?;992993 Self::deposit_event(Event::<T>::ResourceAccepted {994 nft_id: rmrk_nft_id,995 resource_id,996 });997998 Ok(())999 }10001001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]1015 pub fn accept_resource_removal(1016 origin: OriginFor<T>,1017 rmrk_collection_id: RmrkCollectionId,1018 rmrk_nft_id: RmrkNftId,1019 resource_id: RmrkResourceId,1020 ) -> DispatchResult {1021 let sender = ensure_signed(origin)?;1022 let cross_sender = T::CrossAccountId::from_sub(sender);10231024 let collection_id = Self::unique_collection_id(rmrk_collection_id)1025 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;1026 let collection =1027 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1028 collection.check_is_external()?;10291030 let nft_id = rmrk_nft_id.into();10311032 let budget = budget::Value::new(NESTING_BUDGET);10331034 let nft_owner =1035 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1036 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;10371038 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10391040 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10411042 let resource_info = <PalletNft<T>>::token_aux_property((1043 collection_id,1044 nft_id,1045 RMRK_SCOPE,1046 resource_id_key.clone(),1047 ))1048 .ok_or(<Error<T>>::ResourceDoesntExist)?;10491050 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10511052 ensure!(1053 resource_info.pending_removal,1054 <Error<T>>::ResourceNotPending1055 );10561057 <PalletNft<T>>::remove_token_aux_property(1058 collection_id,1059 nft_id,1060 RMRK_SCOPE,1061 resource_id_key,1062 );10631064 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1065 let base_id = resource.base;10661067 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1068 }10691070 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1071 nft_id: rmrk_nft_id,1072 resource_id,1073 });10741075 Ok(())1076 }10771078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 #[pallet::weight(<SelfWeightOf<T>>::set_property())]1096 pub fn set_property(1097 origin: OriginFor<T>,1098 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,1099 maybe_nft_id: Option<RmrkNftId>,1100 key: RmrkKeyString,1101 value: RmrkValueString,1102 ) -> DispatchResult {1103 let sender = ensure_signed(origin)?;1104 let sender = T::CrossAccountId::from_sub(sender);11051106 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1107 let collection =1108 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1109 collection.check_is_external()?;11101111 let budget = budget::Value::new(NESTING_BUDGET);11121113 match maybe_nft_id {1114 Some(nft_id) => {1115 let token_id: TokenId = nft_id.into();11161117 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1118 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;11191120 <PalletNft<T>>::set_scoped_token_property(1121 collection_id,1122 token_id,1123 RMRK_SCOPE,1124 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1125 )?;1126 }1127 None => {1128 let collection = Self::get_typed_nft_collection(1129 collection_id,1130 misc::CollectionType::Regular,1131 )?;11321133 Self::check_collection_owner(&collection, &sender)?;11341135 <PalletCommon<T>>::set_scoped_collection_property(1136 collection_id,1137 RMRK_SCOPE,1138 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1139 )?;1140 }1141 }11421143 Self::deposit_event(Event::PropertySet {1144 collection_id: rmrk_collection_id,1145 maybe_nft_id,1146 key,1147 value,1148 });11491150 Ok(())1151 }11521153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]1169 pub fn set_priority(1170 origin: OriginFor<T>,1171 rmrk_collection_id: RmrkCollectionId,1172 rmrk_nft_id: RmrkNftId,1173 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1174 ) -> DispatchResult {1175 let sender = ensure_signed(origin)?;1176 let sender = T::CrossAccountId::from_sub(sender);11771178 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1179 let nft_id = rmrk_nft_id.into();11801181 let collection =1182 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1183 collection.check_is_external()?;11841185 let budget = budget::Value::new(NESTING_BUDGET);11861187 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1188 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;11891190 <PalletNft<T>>::set_scoped_token_property(1191 collection_id,1192 nft_id,1193 RMRK_SCOPE,1194 Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1195 )?;11961197 Self::deposit_event(Event::<T>::PrioritySet {1198 collection_id: rmrk_collection_id,1199 nft_id: rmrk_nft_id,1200 });12011202 Ok(())1203 }12041205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1220 pub fn add_basic_resource(1221 origin: OriginFor<T>,1222 rmrk_collection_id: RmrkCollectionId,1223 nft_id: RmrkNftId,1224 resource: RmrkBasicResource,1225 ) -> DispatchResult {1226 let sender = ensure_signed(origin.clone())?;12271228 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1229 let collection =1230 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1231 collection.check_is_external()?;12321233 let resource_id = Self::resource_add(1234 sender,1235 collection_id,1236 nft_id.into(),1237 RmrkResourceTypes::Basic(resource),1238 )?;12391240 Self::deposit_event(Event::ResourceAdded {1241 nft_id,1242 resource_id,1243 });1244 Ok(())1245 }12461247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1262 pub fn add_composable_resource(1263 origin: OriginFor<T>,1264 rmrk_collection_id: RmrkCollectionId,1265 nft_id: RmrkNftId,1266 resource: RmrkComposableResource,1267 ) -> DispatchResult {1268 let sender = ensure_signed(origin.clone())?;12691270 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1271 let collection =1272 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1273 collection.check_is_external()?;12741275 let base_id = resource.base;12761277 let resource_id = Self::resource_add(1278 sender,1279 collection_id,1280 nft_id.into(),1281 RmrkResourceTypes::Composable(resource),1282 )?;12831284 <PalletNft<T>>::try_mutate_token_aux_property(1285 collection_id,1286 nft_id.into(),1287 RMRK_SCOPE,1288 Self::get_scoped_property_key(AssociatedBases)?,1289 |value| -> DispatchResult {1290 let mut bases: BasesMap = match value {1291 Some(value) => Self::decode_property_value(value)?,1292 None => BasesMap::new(),1293 };12941295 *bases.entry(base_id).or_insert(0) += 1;12961297 *value = Some(Self::encode_property_value(&bases)?);1298 Ok(())1299 },1300 )?;13011302 Self::deposit_event(Event::ResourceAdded {1303 nft_id,1304 resource_id,1305 });1306 Ok(())1307 }13081309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1324 pub fn add_slot_resource(1325 origin: OriginFor<T>,1326 rmrk_collection_id: RmrkCollectionId,1327 nft_id: RmrkNftId,1328 resource: RmrkSlotResource,1329 ) -> DispatchResult {1330 let sender = ensure_signed(origin.clone())?;13311332 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1333 let collection =1334 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1335 collection.check_is_external()?;13361337 let resource_id = Self::resource_add(1338 sender,1339 collection_id,1340 nft_id.into(),1341 RmrkResourceTypes::Slot(resource),1342 )?;13431344 Self::deposit_event(Event::ResourceAdded {1345 nft_id,1346 resource_id,1347 });1348 Ok(())1349 }13501351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1365 pub fn remove_resource(1366 origin: OriginFor<T>,1367 rmrk_collection_id: RmrkCollectionId,1368 nft_id: RmrkNftId,1369 resource_id: RmrkResourceId,1370 ) -> DispatchResult {1371 let sender = ensure_signed(origin.clone())?;13721373 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1374 let collection =1375 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1376 collection.check_is_external()?;13771378 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13791380 Self::deposit_event(Event::ResourceRemoval {1381 nft_id,1382 resource_id,1383 });1384 Ok(())1385 }1386 }1387}13881389impl<T: Config> Pallet<T> {1390 1391 pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1392 let key = rmrk_key.to_key::<T>()?;13931394 let scoped_key = RMRK_SCOPE1395 .apply(key)1396 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;13971398 Ok(scoped_key)1399 }14001401 1402 1403 pub fn encode_rmrk_property<E: Encode>(1404 rmrk_key: RmrkProperty,1405 value: &E,1406 ) -> Result<Property, DispatchError> {1407 let key = rmrk_key.to_key::<T>()?;14081409 let value = Self::encode_property_value(value)?;14101411 let property = Property { key, value };14121413 Ok(property)1414 }14151416 1417 pub fn encode_property_value<E: Encode, S: Get<u32>>(1418 value: &E,1419 ) -> Result<BoundedBytes<S>, DispatchError> {1420 let value = value1421 .encode()1422 .try_into()1423 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;14241425 Ok(value)1426 }14271428 1429 pub fn decode_property_value<D: Decode, S: Get<u32>>(1430 vec: &BoundedBytes<S>,1431 ) -> Result<D, DispatchError> {1432 vec.decode()1433 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1434 }14351436 1437 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1438 where1439 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1440 {1441 vec.rebind()1442 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1443 }14441445 1446 1447 1448 fn init_collection(1449 sender: T::CrossAccountId,1450 data: CreateCollectionData<T::AccountId>,1451 properties: impl Iterator<Item = Property>,1452 ) -> Result<CollectionId, DispatchError> {1453 let collection_id = <PalletNft<T>>::init_collection(1454 sender.clone(),1455 sender,1456 data,1457 up_data_structs::CollectionFlags {1458 external: true,1459 ..Default::default()1460 },1461 );14621463 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1464 return Err(<Error<T>>::NoAvailableCollectionId.into());1465 }14661467 <PalletCommon<T>>::set_scoped_collection_properties(1468 collection_id?,1469 RMRK_SCOPE,1470 properties,1471 )?;14721473 collection_id1474 }14751476 1477 1478 1479 pub fn create_nft(1480 sender: &T::CrossAccountId,1481 owner: &T::CrossAccountId,1482 collection: &NonfungibleHandle<T>,1483 properties: impl Iterator<Item = Property>,1484 ) -> Result<TokenId, DispatchError> {1485 let data = CreateNftExData {1486 properties: BoundedVec::default(),1487 owner: owner.clone(),1488 };14891490 let budget = budget::Value::new(NESTING_BUDGET);14911492 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;14931494 let nft_id = <PalletNft<T>>::current_token_id(collection.id);14951496 <PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;14971498 Ok(nft_id)1499 }15001501 1502 1503 1504 fn destroy_nft(1505 sender: T::CrossAccountId,1506 collection_id: CollectionId,1507 token_id: TokenId,1508 max_burns: u32,1509 error_if_not_owned: Error<T>,1510 ) -> DispatchResultWithPostInfo {1511 let collection =1512 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;15131514 let token_data =1515 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;15161517 let from = token_data.owner;15181519 let owner_check_budget = budget::Value::new(NESTING_BUDGET);15201521 ensure!(1522 <PalletStructure<T>>::check_indirectly_owned(1523 sender.clone(),1524 collection_id,1525 token_id,1526 None,1527 &owner_check_budget1528 )?,1529 error_if_not_owned,1530 );15311532 let burns_budget = budget::Value::new(max_burns);1533 let breadth_budget = budget::Value::new(max_burns);15341535 <PalletNft<T>>::burn_recursively(1536 &collection,1537 &from,1538 token_id,1539 &burns_budget,1540 &breadth_budget,1541 )1542 }15431544 1545 fn insert_pending_child(1546 target: (CollectionId, TokenId),1547 child: (RmrkCollectionId, RmrkNftId),1548 ) -> DispatchResult {1549 Self::mutate_pending_children(target, |pending_children| {1550 pending_children.insert(child);1551 })1552 }15531554 1555 fn remove_pending_child(1556 target: (CollectionId, TokenId),1557 child: (RmrkCollectionId, RmrkNftId),1558 ) -> DispatchResult {1559 Self::mutate_pending_children(target, |pending_children| {1560 pending_children.remove(&child);1561 })1562 }15631564 1565 1566 fn mutate_pending_children(1567 (target_collection_id, target_nft_id): (CollectionId, TokenId),1568 f: impl FnOnce(&mut PendingChildrenSet),1569 ) -> DispatchResult {1570 <PalletNft<T>>::try_mutate_token_aux_property(1571 target_collection_id,1572 target_nft_id,1573 RMRK_SCOPE,1574 Self::get_scoped_property_key(PendingChildren)?,1575 |pending_children| -> DispatchResult {1576 let mut map = match pending_children {1577 Some(map) => Self::decode_property_value(map)?,1578 None => PendingChildrenSet::new(),1579 };15801581 f(&mut map);15821583 *pending_children = Some(Self::encode_property_value(&map)?);15841585 Ok(())1586 },1587 )1588 }15891590 1591 1592 fn iterate_pending_children(1593 collection_id: CollectionId,1594 nft_id: TokenId,1595 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1596 let property = <PalletNft<T>>::token_aux_property((1597 collection_id,1598 nft_id,1599 RMRK_SCOPE,1600 Self::get_scoped_property_key(PendingChildren)?,1601 ));16021603 let pending_children = match property {1604 Some(map) => Self::decode_property_value(&map)?,1605 None => PendingChildrenSet::new(),1606 };16071608 Ok(pending_children.into_iter())1609 }16101611 1612 1613 1614 1615 fn acquire_next_resource_id(1616 collection_id: CollectionId,1617 nft_id: TokenId,1618 ) -> Result<RmrkResourceId, DispatchError> {1619 let resource_id: RmrkResourceId =1620 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;16211622 let next_id = resource_id1623 .checked_add(1)1624 .ok_or(<Error<T>>::NoAvailableResourceId)?;16251626 <PalletNft<T>>::set_scoped_token_property(1627 collection_id,1628 nft_id,1629 RMRK_SCOPE,1630 Self::encode_rmrk_property(NextResourceId, &next_id)?,1631 )?;16321633 Ok(resource_id)1634 }16351636 1637 1638 fn resource_add(1639 sender: T::AccountId,1640 collection_id: CollectionId,1641 nft_id: TokenId,1642 resource: RmrkResourceTypes,1643 ) -> Result<RmrkResourceId, DispatchError> {1644 let collection =1645 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1646 ensure!(collection.owner == sender, Error::<T>::NoPermission);16471648 let sender = T::CrossAccountId::from_sub(sender);1649 let budget = budget::Value::new(NESTING_BUDGET);16501651 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1652 .map_err(Self::map_unique_err_to_proxy)?;16531654 let pending = sender != nft_owner;16551656 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16571658 let resource_info = RmrkResourceInfo {1659 id,1660 resource,1661 pending,1662 pending_removal: false,1663 };16641665 <PalletNft<T>>::try_mutate_token_aux_property(1666 collection_id,1667 nft_id,1668 RMRK_SCOPE,1669 Self::get_scoped_property_key(ResourceId(id))?,1670 |value| -> DispatchResult {1671 *value = Some(Self::encode_property_value(&resource_info)?);16721673 Ok(())1674 },1675 )?;16761677 Ok(id)1678 }16791680 1681 1682 fn resource_remove(1683 sender: T::AccountId,1684 collection_id: CollectionId,1685 nft_id: TokenId,1686 resource_id: RmrkResourceId,1687 ) -> DispatchResult {1688 let collection =1689 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1690 ensure!(collection.owner == sender, Error::<T>::NoPermission);16911692 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16931694 let resource = <PalletNft<T>>::token_aux_property((1695 collection_id,1696 nft_id,1697 RMRK_SCOPE,1698 resource_id_key.clone(),1699 ))1700 .ok_or(<Error<T>>::ResourceDoesntExist)?;17011702 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;17031704 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1705 let topmost_owner =1706 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;17071708 let sender = T::CrossAccountId::from_sub(sender);1709 if topmost_owner == sender {1710 <PalletNft<T>>::remove_token_aux_property(1711 collection_id,1712 nft_id,1713 RMRK_SCOPE,1714 Self::get_scoped_property_key(ResourceId(resource_id))?,1715 );17161717 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1718 let base_id = resource.base;17191720 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1721 }1722 } else {1723 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1724 res.pending_removal = true;17251726 Ok(())1727 })?;1728 }17291730 Ok(())1731 }17321733 1734 1735 fn remove_associated_base_id(1736 collection_id: CollectionId,1737 nft_id: TokenId,1738 base_id: RmrkBaseId,1739 ) -> DispatchResult {1740 <PalletNft<T>>::try_mutate_token_aux_property(1741 collection_id,1742 nft_id,1743 RMRK_SCOPE,1744 Self::get_scoped_property_key(AssociatedBases)?,1745 |value| -> DispatchResult {1746 let mut bases: BasesMap = match value {1747 Some(value) => Self::decode_property_value(value)?,1748 None => BasesMap::new(),1749 };17501751 let remaining = bases.get(&base_id);17521753 if let Some(remaining) = remaining {1754 if let Some(0) | None = remaining.checked_sub(1) {1755 bases.remove(&base_id);1756 }1757 }17581759 *value = Some(Self::encode_property_value(&bases)?);1760 Ok(())1761 },1762 )1763 }17641765 1766 fn try_mutate_resource_info(1767 collection_id: CollectionId,1768 nft_id: TokenId,1769 resource_id: RmrkResourceId,1770 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1771 ) -> DispatchResult {1772 <PalletNft<T>>::try_mutate_token_aux_property(1773 collection_id,1774 nft_id,1775 RMRK_SCOPE,1776 Self::get_scoped_property_key(ResourceId(resource_id))?,1777 |value| match value {1778 Some(value) => {1779 let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17801781 f(&mut resource_info)?;17821783 *value = Self::encode_property_value(&resource_info)?;17841785 Ok(())1786 }1787 None => Err(<Error<T>>::ResourceDoesntExist.into()),1788 },1789 )1790 }17911792 1793 fn change_collection_owner(1794 collection_id: CollectionId,1795 collection_type: misc::CollectionType,1796 sender: T::AccountId,1797 new_owner: T::AccountId,1798 ) -> DispatchResult {1799 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1800 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;18011802 let mut collection = collection.into_inner();18031804 collection.owner = new_owner;1805 collection.save()1806 }18071808 1809 pub fn check_collection_owner(1810 collection: &NonfungibleHandle<T>,1811 account: &T::CrossAccountId,1812 ) -> DispatchResult {1813 collection1814 .check_is_owner(account)1815 .map_err(Self::map_unique_err_to_proxy)1816 }18171818 1819 pub fn last_collection_idx() -> RmrkCollectionId {1820 <CollectionIndex<T>>::get()1821 }18221823 1824 pub fn unique_collection_id(1825 rmrk_collection_id: RmrkCollectionId,1826 ) -> Result<CollectionId, DispatchError> {1827 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1828 .map_err(|_| <Error<T>>::CollectionUnknown.into())1829 }18301831 1832 pub fn rmrk_collection_id(1833 unique_collection_id: CollectionId,1834 ) -> Result<RmrkCollectionId, DispatchError> {1835 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1836 }18371838 1839 pub fn get_nft_collection(1840 collection_id: CollectionId,1841 ) -> Result<NonfungibleHandle<T>, DispatchError> {1842 let collection = <CollectionHandle<T>>::try_get(collection_id)1843 .map_err(|_| <Error<T>>::CollectionUnknown)?;18441845 match collection.mode {1846 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1847 _ => Err(<Error<T>>::CollectionUnknown.into()),1848 }1849 }18501851 1852 pub fn collection_exists(collection_id: CollectionId) -> bool {1853 <CollectionHandle<T>>::try_get(collection_id).is_ok()1854 }18551856 1857 pub fn get_collection_property(1858 collection_id: CollectionId,1859 key: RmrkProperty,1860 ) -> Result<PropertyValue, DispatchError> {1861 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1862 .get(&Self::get_scoped_property_key(key)?)1863 .ok_or(<Error<T>>::CollectionUnknown)?1864 .clone();18651866 Ok(collection_property)1867 }18681869 1870 pub fn get_collection_property_decoded<V: Decode>(1871 collection_id: CollectionId,1872 key: RmrkProperty,1873 ) -> Result<V, DispatchError> {1874 Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1875 }18761877 1878 1879 1880 pub fn get_collection_type(1881 collection_id: CollectionId,1882 ) -> Result<misc::CollectionType, DispatchError> {1883 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1884 if err != <Error<T>>::CollectionUnknown.into() {1885 <Error<T>>::CorruptedCollectionType.into()1886 } else {1887 err1888 }1889 })1890 }18911892 1893 1894 pub fn ensure_collection_type(1895 collection_id: CollectionId,1896 collection_type: misc::CollectionType,1897 ) -> DispatchResult {1898 let actual_type = Self::get_collection_type(collection_id)?;1899 ensure!(1900 actual_type == collection_type,1901 <CommonError<T>>::NoPermission1902 );19031904 Ok(())1905 }19061907 1908 pub fn get_typed_nft_collection(1909 collection_id: CollectionId,1910 collection_type: misc::CollectionType,1911 ) -> Result<NonfungibleHandle<T>, DispatchError> {1912 Self::ensure_collection_type(collection_id, collection_type)?;19131914 Self::get_nft_collection(collection_id)1915 }19161917 1918 1919 pub fn get_typed_nft_collection_mapped(1920 rmrk_collection_id: RmrkCollectionId,1921 collection_type: misc::CollectionType,1922 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1923 let unique_collection_id = match collection_type {1924 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1925 _ => rmrk_collection_id.into(),1926 };19271928 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;19291930 Ok((collection, unique_collection_id))1931 }19321933 1934 pub fn get_nft_property(1935 collection_id: CollectionId,1936 nft_id: TokenId,1937 key: RmrkProperty,1938 ) -> Result<PropertyValue, DispatchError> {1939 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1940 .get(&Self::get_scoped_property_key(key)?)1941 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1942 .clone();19431944 Ok(nft_property)1945 }19461947 1948 pub fn get_nft_property_decoded<V: Decode>(1949 collection_id: CollectionId,1950 nft_id: TokenId,1951 key: RmrkProperty,1952 ) -> Result<V, DispatchError> {1953 Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1954 }19551956 1957 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1958 <TokenData<T>>::contains_key((collection_id, nft_id))1959 }19601961 1962 1963 1964 pub fn get_nft_type(1965 collection_id: CollectionId,1966 token_id: TokenId,1967 ) -> Result<NftType, DispatchError> {1968 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1969 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1970 }19711972 1973 pub fn ensure_nft_type(1974 collection_id: CollectionId,1975 token_id: TokenId,1976 nft_type: NftType,1977 ) -> DispatchResult {1978 let actual_type = Self::get_nft_type(collection_id, token_id)?;1979 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19801981 Ok(())1982 }19831984 1985 1986 pub fn ensure_nft_owner(1987 collection_id: CollectionId,1988 token_id: TokenId,1989 possible_owner: &T::CrossAccountId,1990 nesting_budget: &dyn budget::Budget,1991 ) -> DispatchResult {1992 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1993 possible_owner.clone(),1994 collection_id,1995 token_id,1996 None,1997 nesting_budget,1998 )1999 .map_err(Self::map_unique_err_to_proxy)?;20002001 ensure!(is_owned, <Error<T>>::NoPermission);20022003 Ok(())2004 }20052006 2007 2008 pub fn filter_user_properties<Key, Value, R, Mapper>(2009 collection_id: CollectionId,2010 token_id: Option<TokenId>,2011 filter_keys: Option<Vec<RmrkPropertyKey>>,2012 mapper: Mapper,2013 ) -> Result<Vec<R>, DispatchError>2014 where2015 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2016 Value: Decode + Default,2017 Mapper: Fn(Key, Value) -> R,2018 {2019 filter_keys2020 .map(|keys| {2021 let properties = keys2022 .into_iter()2023 .filter_map(|key| {2024 let key: Key = key.try_into().ok()?;20252026 let value = match token_id {2027 Some(token_id) => Self::get_nft_property_decoded(2028 collection_id,2029 token_id,2030 UserProperty(key.as_ref()),2031 ),2032 None => Self::get_collection_property_decoded(2033 collection_id,2034 UserProperty(key.as_ref()),2035 ),2036 }2037 .ok()?;20382039 Some(mapper(key, value))2040 })2041 .collect();20422043 Ok(properties)2044 })2045 .unwrap_or_else(|| {2046 let properties =2047 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20482049 Ok(properties)2050 })2051 }20522053 2054 2055 pub fn iterate_user_properties<Key, Value, R, Mapper>(2056 collection_id: CollectionId,2057 token_id: Option<TokenId>,2058 mapper: Mapper,2059 ) -> Result<impl Iterator<Item = R>, DispatchError>2060 where2061 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2062 Value: Decode + Default,2063 Mapper: Fn(Key, Value) -> R,2064 {2065 let properties = match token_id {2066 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2067 None => <PalletCommon<T>>::collection_properties(collection_id),2068 };20692070 let properties = properties.into_iter().filter_map(move |(key, value)| {2071 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20722073 let key: Key = key.to_vec().try_into().ok()?;2074 let value: Value = value.decode().ok()?;20752076 Some(mapper(key, value))2077 });20782079 Ok(properties)2080 }20812082 2083 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2084 map_unique_err_to_proxy! {2085 match err {2086 CommonError::NoPermission => NoPermission,2087 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2088 CommonError::PublicMintingNotAllowed => NoPermission,2089 CommonError::TokenNotFound => NoAvailableNftId,2090 CommonError::ApprovedValueTooLow => NoPermission,2091 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2092 StructureError::TokenNotFound => NoAvailableNftId,2093 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2094 }2095 }2096 }2097}