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::call_index(0)]361 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]362 pub fn create_collection(363 origin: OriginFor<T>,364 metadata: RmrkString,365 max: Option<u32>,366 symbol: RmrkCollectionSymbol,367 ) -> DispatchResult {368 let sender = ensure_signed(origin)?;369370 let limits = CollectionLimits {371 owner_can_transfer: Some(false),372 token_limit: max,373 ..Default::default()374 };375376 let data = CreateCollectionData {377 limits: Some(limits),378 token_prefix: symbol379 .into_inner()380 .try_into()381 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,382 permissions: Some(CollectionPermissions {383 nesting: Some(NestingPermissions {384 token_owner: true,385 collection_admin: false,386 restricted: None,387 #[cfg(feature = "runtime-benchmarks")]388 permissive: false,389 }),390 ..Default::default()391 }),392 ..Default::default()393 };394395 let unique_collection_id = Self::init_collection(396 T::CrossAccountId::from_sub(sender.clone()),397 data,398 [399 Self::encode_rmrk_property(Metadata, &metadata)?,400 Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,401 ]402 .into_iter(),403 )?;404 let rmrk_collection_id = <CollectionIndex<T>>::get();405406 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);407408 <PalletCommon<T>>::set_scoped_collection_property(409 unique_collection_id,410 RMRK_SCOPE,411 Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,412 )?;413414 <CollectionIndex<T>>::mutate(|n| *n += 1);415416 Self::deposit_event(Event::CollectionCreated {417 issuer: sender,418 collection_id: rmrk_collection_id,419 });420421 Ok(())422 }423424 425 426 427 428 429 430 431 432 433 434 #[pallet::call_index(1)]435 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]436 pub fn destroy_collection(437 origin: OriginFor<T>,438 collection_id: RmrkCollectionId,439 ) -> DispatchResult {440 let sender = ensure_signed(origin)?;441 let cross_sender = T::CrossAccountId::from_sub(sender.clone());442443 let collection = Self::get_typed_nft_collection(444 Self::unique_collection_id(collection_id)?,445 misc::CollectionType::Regular,446 )?;447 collection.check_is_external()?;448449 <PalletNft<T>>::destroy_collection(collection, &cross_sender)450 .map_err(Self::map_unique_err_to_proxy)?;451452 Self::deposit_event(Event::CollectionDestroyed {453 issuer: sender,454 collection_id,455 });456457 Ok(())458 }459460 461 462 463 464 465 466 467 468 469 #[pallet::call_index(2)]470 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]471 pub fn change_collection_issuer(472 origin: OriginFor<T>,473 collection_id: RmrkCollectionId,474 new_issuer: <T::Lookup as StaticLookup>::Source,475 ) -> DispatchResult {476 let sender = ensure_signed(origin)?;477478 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;479 collection.check_is_external()?;480481 let new_issuer = T::Lookup::lookup(new_issuer)?;482483 Self::change_collection_owner(484 Self::unique_collection_id(collection_id)?,485 misc::CollectionType::Regular,486 sender.clone(),487 new_issuer.clone(),488 )?;489490 Self::deposit_event(Event::IssuerChanged {491 old_issuer: sender,492 new_issuer,493 collection_id,494 });495496 Ok(())497 }498499 500 501 502 503 504 505 506 507 #[pallet::call_index(3)]508 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]509 pub fn lock_collection(510 origin: OriginFor<T>,511 collection_id: RmrkCollectionId,512 ) -> DispatchResult {513 let sender = ensure_signed(origin)?;514 let cross_sender = T::CrossAccountId::from_sub(sender.clone());515516 let collection = Self::get_typed_nft_collection(517 Self::unique_collection_id(collection_id)?,518 misc::CollectionType::Regular,519 )?;520 collection.check_is_external()?;521522 Self::check_collection_owner(&collection, &cross_sender)?;523524 let token_count = collection.total_supply();525526 let mut collection = collection.into_inner();527 collection.limits.token_limit = Some(token_count);528 collection.save()?;529530 Self::deposit_event(Event::CollectionLocked {531 issuer: sender,532 collection_id,533 });534535 Ok(())536 }537538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 #[pallet::call_index(4)]553 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]554 pub fn mint_nft(555 origin: OriginFor<T>,556 owner: Option<T::AccountId>,557 collection_id: RmrkCollectionId,558 recipient: Option<T::AccountId>,559 royalty_amount: Option<Permill>,560 metadata: RmrkString,561 transferable: bool,562 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,563 ) -> DispatchResult {564 let sender = ensure_signed(origin)?;565 let cross_sender = T::CrossAccountId::from_sub(sender.clone());566567 let owner = owner.unwrap_or(sender.clone());568 let cross_owner = T::CrossAccountId::from_sub(owner.clone());569570 let collection = Self::get_typed_nft_collection(571 Self::unique_collection_id(collection_id)?,572 misc::CollectionType::Regular,573 )?;574 collection.check_is_external()?;575576 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {577 recipient: recipient.unwrap_or_else(|| owner.clone()),578 amount,579 });580581 let nft_id = Self::create_nft(582 &cross_sender,583 &cross_owner,584 &collection,585 [586 Self::encode_rmrk_property(TokenType, &NftType::Regular)?,587 Self::encode_rmrk_property(Transferable, &transferable)?,588 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,589 Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,590 Self::encode_rmrk_property(Metadata, &metadata)?,591 Self::encode_rmrk_property(Equipped, &false)?,592 Self::encode_rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,593 Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,594 Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,595 Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,596 ]597 .into_iter(),598 )599 .map_err(|err| match err {600 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),601 err => Self::map_unique_err_to_proxy(err),602 })?;603604 if let Some(resources) = resources {605 for resource in resources {606 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;607 }608 }609610 Self::deposit_event(Event::NftMinted {611 owner,612 collection_id,613 nft_id: nft_id.0,614 });615616 Ok(())617 }618619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 #[pallet::call_index(5)]637 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]638 pub fn burn_nft(639 origin: OriginFor<T>,640 collection_id: RmrkCollectionId,641 nft_id: RmrkNftId,642 max_burns: u32,643 ) -> DispatchResult {644 let sender = ensure_signed(origin)?;645 let cross_sender = T::CrossAccountId::from_sub(sender.clone());646647 let collection = Self::get_typed_nft_collection(648 Self::unique_collection_id(collection_id)?,649 misc::CollectionType::Regular,650 )?;651 collection.check_is_external()?;652653 Self::destroy_nft(654 cross_sender,655 Self::unique_collection_id(collection_id)?,656 nft_id.into(),657 max_burns,658 <Error<T>>::NoPermission,659 )660 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;661662 Self::deposit_event(Event::NFTBurned {663 owner: sender,664 nft_id,665 });666667 Ok(())668 }669670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 #[pallet::call_index(6)]685 #[pallet::weight(<SelfWeightOf<T>>::send())]686 pub fn send(687 origin: OriginFor<T>,688 rmrk_collection_id: RmrkCollectionId,689 rmrk_nft_id: RmrkNftId,690 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,691 ) -> DispatchResult {692 let sender = ensure_signed(origin.clone())?;693 let cross_sender = T::CrossAccountId::from_sub(sender.clone());694695 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;696 let nft_id = rmrk_nft_id.into();697698 let collection =699 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;700 collection.check_is_external()?;701702 let token_data =703 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;704705 let from = token_data.owner;706707 ensure!(708 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,709 <Error<T>>::NonTransferable710 );711712 ensure!(713 Self::get_nft_property_decoded::<Option<PendingTarget>>(714 collection_id,715 nft_id,716 RmrkProperty::PendingNftAccept717 )?718 .is_none(),719 <Error<T>>::NoPermission720 );721722 let target_owner;723 let approval_required;724725 match new_owner {726 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {727 target_owner = T::CrossAccountId::from_sub(account_id.clone());728 approval_required = false;729 }730 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(731 target_collection_id,732 target_nft_id,733 ) => {734 let target_collection_id = Self::unique_collection_id(target_collection_id)?;735736 let target_nft_budget = budget::Value::new(NESTING_BUDGET);737738 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(739 target_collection_id,740 target_nft_id.into(),741 Some((collection_id, nft_id)),742 &target_nft_budget,743 )744 .map_err(Self::map_unique_err_to_proxy)?;745746 approval_required = cross_sender != target_nft_owner;747748 if approval_required {749 target_owner = target_nft_owner;750751 <PalletNft<T>>::set_scoped_token_property(752 collection.id,753 nft_id,754 RMRK_SCOPE,755 Self::encode_rmrk_property::<Option<PendingTarget>>(756 PendingNftAccept,757 &Some((target_collection_id, target_nft_id.into())),758 )?,759 )?;760761 Self::insert_pending_child(762 (target_collection_id, target_nft_id.into()),763 (rmrk_collection_id, rmrk_nft_id),764 )?;765 } else {766 target_owner = T::CrossTokenAddressMapping::token_to_address(767 target_collection_id,768 target_nft_id.into(),769 );770 }771 }772 }773774 let src_nft_budget = budget::Value::new(NESTING_BUDGET);775776 <PalletNft<T>>::transfer_from(777 &collection,778 &cross_sender,779 &from,780 &target_owner,781 nft_id,782 &src_nft_budget,783 )784 .map_err(Self::map_unique_err_to_proxy)?;785786 Self::deposit_event(Event::NFTSent {787 sender,788 recipient: new_owner,789 collection_id: rmrk_collection_id,790 nft_id: rmrk_nft_id,791 approval_required,792 });793794 Ok(())795 }796797 798 799 800 801 802 803 804 805 806 807 808 809 810 #[pallet::call_index(7)]811 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]812 pub fn accept_nft(813 origin: OriginFor<T>,814 rmrk_collection_id: RmrkCollectionId,815 rmrk_nft_id: RmrkNftId,816 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,817 ) -> DispatchResult {818 let sender = ensure_signed(origin.clone())?;819 let cross_sender = T::CrossAccountId::from_sub(sender.clone());820821 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;822 let nft_id = rmrk_nft_id.into();823824 let collection =825 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;826 collection.check_is_external()?;827828 let new_cross_owner = match new_owner {829 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {830 T::CrossAccountId::from_sub(account_id.clone())831 }832 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(833 target_collection_id,834 target_nft_id,835 ) => {836 let target_collection_id = Self::unique_collection_id(target_collection_id)?;837838 T::CrossTokenAddressMapping::token_to_address(839 target_collection_id,840 TokenId(target_nft_id),841 )842 }843 };844845 let budget = budget::Value::new(NESTING_BUDGET);846847 <PalletNft<T>>::transfer(848 &collection,849 &cross_sender,850 &new_cross_owner,851 nft_id,852 &budget,853 )854 .map_err(|err| {855 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {856 <Error<T>>::CannotAcceptNonOwnedNft.into()857 } else {858 Self::map_unique_err_to_proxy(err)859 }860 })?;861862 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(863 collection_id,864 nft_id,865 RmrkProperty::PendingNftAccept,866 )?;867868 if let Some(pending_target) = pending_target {869 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;870871 <PalletNft<T>>::set_scoped_token_property(872 collection.id,873 nft_id,874 RMRK_SCOPE,875 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,876 )?;877 }878879 Self::deposit_event(Event::NFTAccepted {880 sender,881 recipient: new_owner,882 collection_id: rmrk_collection_id,883 nft_id: rmrk_nft_id,884 });885886 Ok(())887 }888889 890 891 892 893 894 895 896 897 898 899 900 901 #[pallet::call_index(8)]902 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]903 pub fn reject_nft(904 origin: OriginFor<T>,905 rmrk_collection_id: RmrkCollectionId,906 rmrk_nft_id: RmrkNftId,907 ) -> DispatchResult {908 let sender = ensure_signed(origin)?;909 let cross_sender = T::CrossAccountId::from_sub(sender.clone());910911 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;912 let nft_id = rmrk_nft_id.into();913914 let collection =915 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;916 collection.check_is_external()?;917918 ensure!(919 <TokenData<T>>::get((collection_id, nft_id)).is_some(),920 <Error<T>>::NoAvailableNftId921 );922923 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(924 collection_id,925 nft_id,926 RmrkProperty::PendingNftAccept,927 )?;928929 match pending_target {930 Some(pending_target) => {931 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?932 }933 None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),934 }935936 Self::destroy_nft(937 cross_sender,938 collection_id,939 nft_id,940 NESTING_BUDGET,941 <Error<T>>::CannotRejectNonOwnedNft,942 )943 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;944945 Self::deposit_event(Event::NFTRejected {946 sender,947 collection_id: rmrk_collection_id,948 nft_id: rmrk_nft_id,949 });950951 Ok(())952 }953954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 #[pallet::call_index(9)]970 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]971 pub fn accept_resource(972 origin: OriginFor<T>,973 rmrk_collection_id: RmrkCollectionId,974 rmrk_nft_id: RmrkNftId,975 resource_id: RmrkResourceId,976 ) -> DispatchResult {977 let sender = ensure_signed(origin)?;978 let cross_sender = T::CrossAccountId::from_sub(sender);979980 let collection_id = Self::unique_collection_id(rmrk_collection_id)981 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;982 let collection =983 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;984 collection.check_is_external()?;985986 let nft_id = rmrk_nft_id.into();987988 let budget = budget::Value::new(NESTING_BUDGET);989990 let nft_owner =991 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)992 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;993994 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {995 ensure!(res.pending, <Error<T>>::ResourceNotPending);996 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);997998 res.pending = false;9991000 Ok(())1001 })?;10021003 Self::deposit_event(Event::<T>::ResourceAccepted {1004 nft_id: rmrk_nft_id,1005 resource_id,1006 });10071008 Ok(())1009 }10101011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 #[pallet::call_index(10)]1025 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]1026 pub fn accept_resource_removal(1027 origin: OriginFor<T>,1028 rmrk_collection_id: RmrkCollectionId,1029 rmrk_nft_id: RmrkNftId,1030 resource_id: RmrkResourceId,1031 ) -> DispatchResult {1032 let sender = ensure_signed(origin)?;1033 let cross_sender = T::CrossAccountId::from_sub(sender);10341035 let collection_id = Self::unique_collection_id(rmrk_collection_id)1036 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;1037 let collection =1038 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1039 collection.check_is_external()?;10401041 let nft_id = rmrk_nft_id.into();10421043 let budget = budget::Value::new(NESTING_BUDGET);10441045 let nft_owner =1046 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1047 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;10481049 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10501051 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10521053 let resource_info = <PalletNft<T>>::token_aux_property((1054 collection_id,1055 nft_id,1056 RMRK_SCOPE,1057 resource_id_key.clone(),1058 ))1059 .ok_or(<Error<T>>::ResourceDoesntExist)?;10601061 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10621063 ensure!(1064 resource_info.pending_removal,1065 <Error<T>>::ResourceNotPending1066 );10671068 <PalletNft<T>>::remove_token_aux_property(1069 collection_id,1070 nft_id,1071 RMRK_SCOPE,1072 resource_id_key,1073 );10741075 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1076 let base_id = resource.base;10771078 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1079 }10801081 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1082 nft_id: rmrk_nft_id,1083 resource_id,1084 });10851086 Ok(())1087 }10881089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 #[pallet::call_index(11)]1107 #[pallet::weight(<SelfWeightOf<T>>::set_property())]1108 pub fn set_property(1109 origin: OriginFor<T>,1110 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,1111 maybe_nft_id: Option<RmrkNftId>,1112 key: RmrkKeyString,1113 value: RmrkValueString,1114 ) -> DispatchResult {1115 let sender = ensure_signed(origin)?;1116 let sender = T::CrossAccountId::from_sub(sender);11171118 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1119 let collection =1120 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1121 collection.check_is_external()?;11221123 let budget = budget::Value::new(NESTING_BUDGET);11241125 match maybe_nft_id {1126 Some(nft_id) => {1127 let token_id: TokenId = nft_id.into();11281129 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1130 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;11311132 <PalletNft<T>>::set_scoped_token_property(1133 collection_id,1134 token_id,1135 RMRK_SCOPE,1136 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1137 )?;1138 }1139 None => {1140 let collection = Self::get_typed_nft_collection(1141 collection_id,1142 misc::CollectionType::Regular,1143 )?;11441145 Self::check_collection_owner(&collection, &sender)?;11461147 <PalletCommon<T>>::set_scoped_collection_property(1148 collection_id,1149 RMRK_SCOPE,1150 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1151 )?;1152 }1153 }11541155 Self::deposit_event(Event::PropertySet {1156 collection_id: rmrk_collection_id,1157 maybe_nft_id,1158 key,1159 value,1160 });11611162 Ok(())1163 }11641165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 #[pallet::call_index(12)]1181 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]1182 pub fn set_priority(1183 origin: OriginFor<T>,1184 rmrk_collection_id: RmrkCollectionId,1185 rmrk_nft_id: RmrkNftId,1186 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1187 ) -> DispatchResult {1188 let sender = ensure_signed(origin)?;1189 let sender = T::CrossAccountId::from_sub(sender);11901191 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1192 let nft_id = rmrk_nft_id.into();11931194 let collection =1195 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1196 collection.check_is_external()?;11971198 let budget = budget::Value::new(NESTING_BUDGET);11991200 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1201 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;12021203 <PalletNft<T>>::set_scoped_token_property(1204 collection_id,1205 nft_id,1206 RMRK_SCOPE,1207 Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1208 )?;12091210 Self::deposit_event(Event::<T>::PrioritySet {1211 collection_id: rmrk_collection_id,1212 nft_id: rmrk_nft_id,1213 });12141215 Ok(())1216 }12171218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 #[pallet::call_index(13)]1233 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1234 pub fn add_basic_resource(1235 origin: OriginFor<T>,1236 rmrk_collection_id: RmrkCollectionId,1237 nft_id: RmrkNftId,1238 resource: RmrkBasicResource,1239 ) -> DispatchResult {1240 let sender = ensure_signed(origin.clone())?;12411242 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1243 let collection =1244 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1245 collection.check_is_external()?;12461247 let resource_id = Self::resource_add(1248 sender,1249 collection_id,1250 nft_id.into(),1251 RmrkResourceTypes::Basic(resource),1252 )?;12531254 Self::deposit_event(Event::ResourceAdded {1255 nft_id,1256 resource_id,1257 });1258 Ok(())1259 }12601261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 #[pallet::call_index(14)]1276 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1277 pub fn add_composable_resource(1278 origin: OriginFor<T>,1279 rmrk_collection_id: RmrkCollectionId,1280 nft_id: RmrkNftId,1281 resource: RmrkComposableResource,1282 ) -> DispatchResult {1283 let sender = ensure_signed(origin.clone())?;12841285 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1286 let collection =1287 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1288 collection.check_is_external()?;12891290 let base_id = resource.base;12911292 let resource_id = Self::resource_add(1293 sender,1294 collection_id,1295 nft_id.into(),1296 RmrkResourceTypes::Composable(resource),1297 )?;12981299 <PalletNft<T>>::try_mutate_token_aux_property(1300 collection_id,1301 nft_id.into(),1302 RMRK_SCOPE,1303 Self::get_scoped_property_key(AssociatedBases)?,1304 |value| -> DispatchResult {1305 let mut bases: BasesMap = match value {1306 Some(value) => Self::decode_property_value(value)?,1307 None => BasesMap::new(),1308 };13091310 *bases.entry(base_id).or_insert(0) += 1;13111312 *value = Some(Self::encode_property_value(&bases)?);1313 Ok(())1314 },1315 )?;13161317 Self::deposit_event(Event::ResourceAdded {1318 nft_id,1319 resource_id,1320 });1321 Ok(())1322 }13231324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 #[pallet::call_index(15)]1339 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1340 pub fn add_slot_resource(1341 origin: OriginFor<T>,1342 rmrk_collection_id: RmrkCollectionId,1343 nft_id: RmrkNftId,1344 resource: RmrkSlotResource,1345 ) -> DispatchResult {1346 let sender = ensure_signed(origin.clone())?;13471348 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1349 let collection =1350 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1351 collection.check_is_external()?;13521353 let resource_id = Self::resource_add(1354 sender,1355 collection_id,1356 nft_id.into(),1357 RmrkResourceTypes::Slot(resource),1358 )?;13591360 Self::deposit_event(Event::ResourceAdded {1361 nft_id,1362 resource_id,1363 });1364 Ok(())1365 }13661367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 #[pallet::call_index(16)]1381 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1382 pub fn remove_resource(1383 origin: OriginFor<T>,1384 rmrk_collection_id: RmrkCollectionId,1385 nft_id: RmrkNftId,1386 resource_id: RmrkResourceId,1387 ) -> DispatchResult {1388 let sender = ensure_signed(origin.clone())?;13891390 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1391 let collection =1392 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1393 collection.check_is_external()?;13941395 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13961397 Self::deposit_event(Event::ResourceRemoval {1398 nft_id,1399 resource_id,1400 });1401 Ok(())1402 }1403 }1404}14051406impl<T: Config> Pallet<T> {1407 1408 pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1409 let key = rmrk_key.to_key::<T>()?;14101411 let scoped_key = RMRK_SCOPE1412 .apply(key)1413 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;14141415 Ok(scoped_key)1416 }14171418 1419 1420 pub fn encode_rmrk_property<E: Encode>(1421 rmrk_key: RmrkProperty,1422 value: &E,1423 ) -> Result<Property, DispatchError> {1424 let key = rmrk_key.to_key::<T>()?;14251426 let value = Self::encode_property_value(value)?;14271428 let property = Property { key, value };14291430 Ok(property)1431 }14321433 1434 pub fn encode_property_value<E: Encode, S: Get<u32>>(1435 value: &E,1436 ) -> Result<BoundedBytes<S>, DispatchError> {1437 let value = value1438 .encode()1439 .try_into()1440 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;14411442 Ok(value)1443 }14441445 1446 pub fn decode_property_value<D: Decode, S: Get<u32>>(1447 vec: &BoundedBytes<S>,1448 ) -> Result<D, DispatchError> {1449 vec.decode()1450 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1451 }14521453 1454 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1455 where1456 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1457 {1458 vec.rebind()1459 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1460 }14611462 1463 1464 1465 fn init_collection(1466 sender: T::CrossAccountId,1467 data: CreateCollectionData<T::AccountId>,1468 properties: impl Iterator<Item = Property>,1469 ) -> Result<CollectionId, DispatchError> {1470 let collection_id = <PalletNft<T>>::init_collection(1471 sender.clone(),1472 sender,1473 data,1474 up_data_structs::CollectionFlags {1475 external: true,1476 ..Default::default()1477 },1478 );14791480 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1481 return Err(<Error<T>>::NoAvailableCollectionId.into());1482 }14831484 <PalletCommon<T>>::set_scoped_collection_properties(1485 collection_id?,1486 RMRK_SCOPE,1487 properties,1488 )?;14891490 collection_id1491 }14921493 1494 1495 1496 pub fn create_nft(1497 sender: &T::CrossAccountId,1498 owner: &T::CrossAccountId,1499 collection: &NonfungibleHandle<T>,1500 properties: impl Iterator<Item = Property>,1501 ) -> Result<TokenId, DispatchError> {1502 let data = CreateNftExData {1503 properties: BoundedVec::default(),1504 owner: owner.clone(),1505 };15061507 let budget = budget::Value::new(NESTING_BUDGET);15081509 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;15101511 let nft_id = <PalletNft<T>>::current_token_id(collection.id);15121513 <PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;15141515 Ok(nft_id)1516 }15171518 1519 1520 1521 fn destroy_nft(1522 sender: T::CrossAccountId,1523 collection_id: CollectionId,1524 token_id: TokenId,1525 max_burns: u32,1526 error_if_not_owned: Error<T>,1527 ) -> DispatchResultWithPostInfo {1528 let collection =1529 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;15301531 let token_data =1532 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;15331534 let from = token_data.owner;15351536 let owner_check_budget = budget::Value::new(NESTING_BUDGET);15371538 ensure!(1539 <PalletStructure<T>>::check_indirectly_owned(1540 sender.clone(),1541 collection_id,1542 token_id,1543 None,1544 &owner_check_budget1545 )?,1546 error_if_not_owned,1547 );15481549 let burns_budget = budget::Value::new(max_burns);1550 let breadth_budget = budget::Value::new(max_burns);15511552 <PalletNft<T>>::burn_recursively(1553 &collection,1554 &from,1555 token_id,1556 &burns_budget,1557 &breadth_budget,1558 )1559 }15601561 1562 fn insert_pending_child(1563 target: (CollectionId, TokenId),1564 child: (RmrkCollectionId, RmrkNftId),1565 ) -> DispatchResult {1566 Self::mutate_pending_children(target, |pending_children| {1567 pending_children.insert(child);1568 })1569 }15701571 1572 fn remove_pending_child(1573 target: (CollectionId, TokenId),1574 child: (RmrkCollectionId, RmrkNftId),1575 ) -> DispatchResult {1576 Self::mutate_pending_children(target, |pending_children| {1577 pending_children.remove(&child);1578 })1579 }15801581 1582 1583 fn mutate_pending_children(1584 (target_collection_id, target_nft_id): (CollectionId, TokenId),1585 f: impl FnOnce(&mut PendingChildrenSet),1586 ) -> DispatchResult {1587 <PalletNft<T>>::try_mutate_token_aux_property(1588 target_collection_id,1589 target_nft_id,1590 RMRK_SCOPE,1591 Self::get_scoped_property_key(PendingChildren)?,1592 |pending_children| -> DispatchResult {1593 let mut map = match pending_children {1594 Some(map) => Self::decode_property_value(map)?,1595 None => PendingChildrenSet::new(),1596 };15971598 f(&mut map);15991600 *pending_children = Some(Self::encode_property_value(&map)?);16011602 Ok(())1603 },1604 )1605 }16061607 1608 1609 fn iterate_pending_children(1610 collection_id: CollectionId,1611 nft_id: TokenId,1612 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1613 let property = <PalletNft<T>>::token_aux_property((1614 collection_id,1615 nft_id,1616 RMRK_SCOPE,1617 Self::get_scoped_property_key(PendingChildren)?,1618 ));16191620 let pending_children = match property {1621 Some(map) => Self::decode_property_value(&map)?,1622 None => PendingChildrenSet::new(),1623 };16241625 Ok(pending_children.into_iter())1626 }16271628 1629 1630 1631 1632 fn acquire_next_resource_id(1633 collection_id: CollectionId,1634 nft_id: TokenId,1635 ) -> Result<RmrkResourceId, DispatchError> {1636 let resource_id: RmrkResourceId =1637 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;16381639 let next_id = resource_id1640 .checked_add(1)1641 .ok_or(<Error<T>>::NoAvailableResourceId)?;16421643 <PalletNft<T>>::set_scoped_token_property(1644 collection_id,1645 nft_id,1646 RMRK_SCOPE,1647 Self::encode_rmrk_property(NextResourceId, &next_id)?,1648 )?;16491650 Ok(resource_id)1651 }16521653 1654 1655 fn resource_add(1656 sender: T::AccountId,1657 collection_id: CollectionId,1658 nft_id: TokenId,1659 resource: RmrkResourceTypes,1660 ) -> Result<RmrkResourceId, DispatchError> {1661 let collection =1662 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1663 ensure!(collection.owner == sender, Error::<T>::NoPermission);16641665 let sender = T::CrossAccountId::from_sub(sender);1666 let budget = budget::Value::new(NESTING_BUDGET);16671668 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1669 .map_err(Self::map_unique_err_to_proxy)?;16701671 let pending = sender != nft_owner;16721673 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16741675 let resource_info = RmrkResourceInfo {1676 id,1677 resource,1678 pending,1679 pending_removal: false,1680 };16811682 <PalletNft<T>>::try_mutate_token_aux_property(1683 collection_id,1684 nft_id,1685 RMRK_SCOPE,1686 Self::get_scoped_property_key(ResourceId(id))?,1687 |value| -> DispatchResult {1688 *value = Some(Self::encode_property_value(&resource_info)?);16891690 Ok(())1691 },1692 )?;16931694 Ok(id)1695 }16961697 1698 1699 fn resource_remove(1700 sender: T::AccountId,1701 collection_id: CollectionId,1702 nft_id: TokenId,1703 resource_id: RmrkResourceId,1704 ) -> DispatchResult {1705 let collection =1706 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1707 ensure!(collection.owner == sender, Error::<T>::NoPermission);17081709 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;17101711 let resource = <PalletNft<T>>::token_aux_property((1712 collection_id,1713 nft_id,1714 RMRK_SCOPE,1715 resource_id_key.clone(),1716 ))1717 .ok_or(<Error<T>>::ResourceDoesntExist)?;17181719 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;17201721 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1722 let topmost_owner =1723 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;17241725 let sender = T::CrossAccountId::from_sub(sender);1726 if topmost_owner == sender {1727 <PalletNft<T>>::remove_token_aux_property(1728 collection_id,1729 nft_id,1730 RMRK_SCOPE,1731 Self::get_scoped_property_key(ResourceId(resource_id))?,1732 );17331734 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1735 let base_id = resource.base;17361737 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1738 }1739 } else {1740 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1741 res.pending_removal = true;17421743 Ok(())1744 })?;1745 }17461747 Ok(())1748 }17491750 1751 1752 fn remove_associated_base_id(1753 collection_id: CollectionId,1754 nft_id: TokenId,1755 base_id: RmrkBaseId,1756 ) -> DispatchResult {1757 <PalletNft<T>>::try_mutate_token_aux_property(1758 collection_id,1759 nft_id,1760 RMRK_SCOPE,1761 Self::get_scoped_property_key(AssociatedBases)?,1762 |value| -> DispatchResult {1763 let mut bases: BasesMap = match value {1764 Some(value) => Self::decode_property_value(value)?,1765 None => BasesMap::new(),1766 };17671768 let remaining = bases.get(&base_id);17691770 if let Some(remaining) = remaining {1771 if let Some(0) | None = remaining.checked_sub(1) {1772 bases.remove(&base_id);1773 }1774 }17751776 *value = Some(Self::encode_property_value(&bases)?);1777 Ok(())1778 },1779 )1780 }17811782 1783 fn try_mutate_resource_info(1784 collection_id: CollectionId,1785 nft_id: TokenId,1786 resource_id: RmrkResourceId,1787 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1788 ) -> DispatchResult {1789 <PalletNft<T>>::try_mutate_token_aux_property(1790 collection_id,1791 nft_id,1792 RMRK_SCOPE,1793 Self::get_scoped_property_key(ResourceId(resource_id))?,1794 |value| match value {1795 Some(value) => {1796 let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17971798 f(&mut resource_info)?;17991800 *value = Self::encode_property_value(&resource_info)?;18011802 Ok(())1803 }1804 None => Err(<Error<T>>::ResourceDoesntExist.into()),1805 },1806 )1807 }18081809 1810 fn change_collection_owner(1811 collection_id: CollectionId,1812 collection_type: misc::CollectionType,1813 sender: T::AccountId,1814 new_owner: T::AccountId,1815 ) -> DispatchResult {1816 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1817 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;18181819 let mut collection = collection.into_inner();18201821 collection.owner = new_owner;1822 collection.save()1823 }18241825 1826 pub fn check_collection_owner(1827 collection: &NonfungibleHandle<T>,1828 account: &T::CrossAccountId,1829 ) -> DispatchResult {1830 collection1831 .check_is_owner(account)1832 .map_err(Self::map_unique_err_to_proxy)1833 }18341835 1836 pub fn last_collection_idx() -> RmrkCollectionId {1837 <CollectionIndex<T>>::get()1838 }18391840 1841 pub fn unique_collection_id(1842 rmrk_collection_id: RmrkCollectionId,1843 ) -> Result<CollectionId, DispatchError> {1844 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1845 .map_err(|_| <Error<T>>::CollectionUnknown.into())1846 }18471848 1849 pub fn rmrk_collection_id(1850 unique_collection_id: CollectionId,1851 ) -> Result<RmrkCollectionId, DispatchError> {1852 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1853 }18541855 1856 pub fn get_nft_collection(1857 collection_id: CollectionId,1858 ) -> Result<NonfungibleHandle<T>, DispatchError> {1859 let collection = <CollectionHandle<T>>::try_get(collection_id)1860 .map_err(|_| <Error<T>>::CollectionUnknown)?;18611862 match collection.mode {1863 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1864 _ => Err(<Error<T>>::CollectionUnknown.into()),1865 }1866 }18671868 1869 pub fn collection_exists(collection_id: CollectionId) -> bool {1870 <CollectionHandle<T>>::try_get(collection_id).is_ok()1871 }18721873 1874 pub fn get_collection_property(1875 collection_id: CollectionId,1876 key: RmrkProperty,1877 ) -> Result<PropertyValue, DispatchError> {1878 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1879 .get(&Self::get_scoped_property_key(key)?)1880 .ok_or(<Error<T>>::CollectionUnknown)?1881 .clone();18821883 Ok(collection_property)1884 }18851886 1887 pub fn get_collection_property_decoded<V: Decode>(1888 collection_id: CollectionId,1889 key: RmrkProperty,1890 ) -> Result<V, DispatchError> {1891 Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1892 }18931894 1895 1896 1897 pub fn get_collection_type(1898 collection_id: CollectionId,1899 ) -> Result<misc::CollectionType, DispatchError> {1900 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1901 if err != <Error<T>>::CollectionUnknown.into() {1902 <Error<T>>::CorruptedCollectionType.into()1903 } else {1904 err1905 }1906 })1907 }19081909 1910 1911 pub fn ensure_collection_type(1912 collection_id: CollectionId,1913 collection_type: misc::CollectionType,1914 ) -> DispatchResult {1915 let actual_type = Self::get_collection_type(collection_id)?;1916 ensure!(1917 actual_type == collection_type,1918 <CommonError<T>>::NoPermission1919 );19201921 Ok(())1922 }19231924 1925 pub fn get_typed_nft_collection(1926 collection_id: CollectionId,1927 collection_type: misc::CollectionType,1928 ) -> Result<NonfungibleHandle<T>, DispatchError> {1929 Self::ensure_collection_type(collection_id, collection_type)?;19301931 Self::get_nft_collection(collection_id)1932 }19331934 1935 1936 pub fn get_typed_nft_collection_mapped(1937 rmrk_collection_id: RmrkCollectionId,1938 collection_type: misc::CollectionType,1939 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1940 let unique_collection_id = match collection_type {1941 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1942 _ => rmrk_collection_id.into(),1943 };19441945 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;19461947 Ok((collection, unique_collection_id))1948 }19491950 1951 pub fn get_nft_property(1952 collection_id: CollectionId,1953 nft_id: TokenId,1954 key: RmrkProperty,1955 ) -> Result<PropertyValue, DispatchError> {1956 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1957 .get(&Self::get_scoped_property_key(key)?)1958 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1959 .clone();19601961 Ok(nft_property)1962 }19631964 1965 pub fn get_nft_property_decoded<V: Decode>(1966 collection_id: CollectionId,1967 nft_id: TokenId,1968 key: RmrkProperty,1969 ) -> Result<V, DispatchError> {1970 Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1971 }19721973 1974 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1975 <TokenData<T>>::contains_key((collection_id, nft_id))1976 }19771978 1979 1980 1981 pub fn get_nft_type(1982 collection_id: CollectionId,1983 token_id: TokenId,1984 ) -> Result<NftType, DispatchError> {1985 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1986 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1987 }19881989 1990 pub fn ensure_nft_type(1991 collection_id: CollectionId,1992 token_id: TokenId,1993 nft_type: NftType,1994 ) -> DispatchResult {1995 let actual_type = Self::get_nft_type(collection_id, token_id)?;1996 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19971998 Ok(())1999 }20002001 2002 2003 pub fn ensure_nft_owner(2004 collection_id: CollectionId,2005 token_id: TokenId,2006 possible_owner: &T::CrossAccountId,2007 nesting_budget: &dyn budget::Budget,2008 ) -> DispatchResult {2009 let is_owned = <PalletStructure<T>>::check_indirectly_owned(2010 possible_owner.clone(),2011 collection_id,2012 token_id,2013 None,2014 nesting_budget,2015 )2016 .map_err(Self::map_unique_err_to_proxy)?;20172018 ensure!(is_owned, <Error<T>>::NoPermission);20192020 Ok(())2021 }20222023 2024 2025 pub fn filter_user_properties<Key, Value, R, Mapper>(2026 collection_id: CollectionId,2027 token_id: Option<TokenId>,2028 filter_keys: Option<Vec<RmrkPropertyKey>>,2029 mapper: Mapper,2030 ) -> Result<Vec<R>, DispatchError>2031 where2032 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2033 Value: Decode + Default,2034 Mapper: Fn(Key, Value) -> R,2035 {2036 filter_keys2037 .map(|keys| {2038 let properties = keys2039 .into_iter()2040 .filter_map(|key| {2041 let key: Key = key.try_into().ok()?;20422043 let value = match token_id {2044 Some(token_id) => Self::get_nft_property_decoded(2045 collection_id,2046 token_id,2047 UserProperty(key.as_ref()),2048 ),2049 None => Self::get_collection_property_decoded(2050 collection_id,2051 UserProperty(key.as_ref()),2052 ),2053 }2054 .ok()?;20552056 Some(mapper(key, value))2057 })2058 .collect();20592060 Ok(properties)2061 })2062 .unwrap_or_else(|| {2063 let properties =2064 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20652066 Ok(properties)2067 })2068 }20692070 2071 2072 pub fn iterate_user_properties<Key, Value, R, Mapper>(2073 collection_id: CollectionId,2074 token_id: Option<TokenId>,2075 mapper: Mapper,2076 ) -> Result<impl Iterator<Item = R>, DispatchError>2077 where2078 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2079 Value: Decode + Default,2080 Mapper: Fn(Key, Value) -> R,2081 {2082 let properties = match token_id {2083 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2084 None => <PalletCommon<T>>::collection_properties(collection_id),2085 };20862087 let properties = properties.into_iter().filter_map(move |(key, value)| {2088 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20892090 let key: Key = key.to_vec().try_into().ok()?;2091 let value: Value = value.decode().ok()?;20922093 Some(mapper(key, value))2094 });20952096 Ok(properties)2097 }20982099 2100 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2101 map_unique_err_to_proxy! {2102 match err {2103 CommonError::NoPermission => NoPermission,2104 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2105 CommonError::PublicMintingNotAllowed => NoPermission,2106 CommonError::TokenNotFound => NoAvailableNftId,2107 CommonError::ApprovedValueTooLow => NoPermission,2108 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2109 StructureError::TokenNotFound => NoAvailableNftId,2110 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2111 }2112 }2113 }2114}