123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147#![cfg_attr(not(feature = "std"), no_std)]148149use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};150use frame_system::{pallet_prelude::*, ensure_signed};151use sp_runtime::{DispatchError, Permill, traits::StaticLookup};152use sp_std::{153 vec::Vec,154 collections::{btree_set::BTreeSet, btree_map::BTreeMap},155};156use up_data_structs::{*, mapping::TokenAddressMapping};157use pallet_common::{158 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,159};160use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};161use pallet_structure::{Pallet as PalletStructure, Error as StructureError};162use pallet_evm::account::CrossAccountId;163use core::convert::AsRef;164165pub use pallet::*;166167#[cfg(feature = "runtime-benchmarks")]168pub mod benchmarking;169pub mod misc;170pub mod property;171pub mod rpc;172pub mod weights;173174pub type SelfWeightOf<T> = <T as Config>::WeightInfo;175176use weights::WeightInfo;177use misc::*;178pub use property::*;179180use RmrkProperty::*;181182183pub const NESTING_BUDGET: u32 = 5;184185type PendingTarget = (CollectionId, TokenId);186type PendingChild = (RmrkCollectionId, RmrkNftId);187type PendingChildrenSet = BTreeSet<PendingChild>;188189type BasesMap = BTreeMap<RmrkBaseId, u32>;190191#[frame_support::pallet]192pub mod pallet {193 use super::*;194 use pallet_evm::account;195196 #[pallet::config]197 pub trait Config:198 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config199 {200 201 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;202203 204 type WeightInfo: WeightInfo;205 }206207 208 #[pallet::storage]209 #[pallet::getter(fn collection_index)]210 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;211212 213 #[pallet::storage]214 pub type UniqueCollectionId<T: Config> =215 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;216217 #[pallet::pallet]218 #[pallet::generate_store(pub(super) trait Store)]219 pub struct Pallet<T>(_);220221 #[pallet::event]222 #[pallet::generate_deposit(pub(super) fn deposit_event)]223 pub enum Event<T: Config> {224 CollectionCreated {225 issuer: T::AccountId,226 collection_id: RmrkCollectionId,227 },228 CollectionDestroyed {229 issuer: T::AccountId,230 collection_id: RmrkCollectionId,231 },232 IssuerChanged {233 old_issuer: T::AccountId,234 new_issuer: T::AccountId,235 collection_id: RmrkCollectionId,236 },237 CollectionLocked {238 issuer: T::AccountId,239 collection_id: RmrkCollectionId,240 },241 NftMinted {242 owner: T::AccountId,243 collection_id: RmrkCollectionId,244 nft_id: RmrkNftId,245 },246 NFTBurned {247 owner: T::AccountId,248 nft_id: RmrkNftId,249 },250 NFTSent {251 sender: T::AccountId,252 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,253 collection_id: RmrkCollectionId,254 nft_id: RmrkNftId,255 approval_required: bool,256 },257 NFTAccepted {258 sender: T::AccountId,259 recipient: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,260 collection_id: RmrkCollectionId,261 nft_id: RmrkNftId,262 },263 NFTRejected {264 sender: T::AccountId,265 collection_id: RmrkCollectionId,266 nft_id: RmrkNftId,267 },268 PropertySet {269 collection_id: RmrkCollectionId,270 maybe_nft_id: Option<RmrkNftId>,271 key: RmrkKeyString,272 value: RmrkValueString,273 },274 ResourceAdded {275 nft_id: RmrkNftId,276 resource_id: RmrkResourceId,277 },278 ResourceRemoval {279 nft_id: RmrkNftId,280 resource_id: RmrkResourceId,281 },282 ResourceAccepted {283 nft_id: RmrkNftId,284 resource_id: RmrkResourceId,285 },286 ResourceRemovalAccepted {287 nft_id: RmrkNftId,288 resource_id: RmrkResourceId,289 },290 PrioritySet {291 collection_id: RmrkCollectionId,292 nft_id: RmrkNftId,293 },294 }295296 #[pallet::error]297 pub enum Error<T> {298 299 300 CorruptedCollectionType,301 302 303 RmrkPropertyKeyIsTooLong,304 305 RmrkPropertyValueIsTooLong,306 307 RmrkPropertyIsNotFound,308 309 310 UnableToDecodeRmrkData,311312 313 314 CollectionNotEmpty,315 316 NoAvailableCollectionId,317 318 NoAvailableNftId,319 320 CollectionUnknown,321 322 NoPermission,323 324 NonTransferable,325 326 CollectionFullOrLocked,327 328 ResourceDoesntExist,329 330 331 CannotSendToDescendentOrSelf,332 333 CannotAcceptNonOwnedNft,334 335 CannotRejectNonOwnedNft,336 337 CannotRejectNonPendingNft,338 339 ResourceNotPending,340 341 NoAvailableResourceId,342 }343344 #[pallet::call]345 impl<T: Config> Pallet<T> {346 347348 349 350 351 352 353 354 355 356 357 358 #[transactional]359 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]360 pub fn create_collection(361 origin: OriginFor<T>,362 metadata: RmrkString,363 max: Option<u32>,364 symbol: RmrkCollectionSymbol,365 ) -> DispatchResult {366 let sender = ensure_signed(origin)?;367368 let limits = CollectionLimits {369 owner_can_transfer: Some(false),370 token_limit: max,371 ..Default::default()372 };373374 let data = CreateCollectionData {375 limits: Some(limits),376 token_prefix: symbol377 .into_inner()378 .try_into()379 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,380 permissions: Some(CollectionPermissions {381 nesting: Some(NestingPermissions {382 token_owner: true,383 collection_admin: false,384 restricted: None,385 #[cfg(feature = "runtime-benchmarks")]386 permissive: false,387 }),388 ..Default::default()389 }),390 ..Default::default()391 };392393 let unique_collection_id = Self::init_collection(394 T::CrossAccountId::from_sub(sender.clone()),395 data,396 [397 Self::encode_rmrk_property(Metadata, &metadata)?,398 Self::encode_rmrk_property(CollectionType, &misc::CollectionType::Regular)?,399 ]400 .into_iter(),401 )?;402 let rmrk_collection_id = <CollectionIndex<T>>::get();403404 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);405406 <PalletCommon<T>>::set_scoped_collection_property(407 unique_collection_id,408 RMRK_SCOPE,409 Self::encode_rmrk_property(RmrkInternalCollectionId, &rmrk_collection_id)?,410 )?;411412 <CollectionIndex<T>>::mutate(|n| *n += 1);413414 Self::deposit_event(Event::CollectionCreated {415 issuer: sender,416 collection_id: rmrk_collection_id,417 });418419 Ok(())420 }421422 423 424 425 426 427 428 429 430 431 #[transactional]432 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]433 pub fn destroy_collection(434 origin: OriginFor<T>,435 collection_id: RmrkCollectionId,436 ) -> DispatchResult {437 let sender = ensure_signed(origin)?;438 let cross_sender = T::CrossAccountId::from_sub(sender.clone());439440 let collection = Self::get_typed_nft_collection(441 Self::unique_collection_id(collection_id)?,442 misc::CollectionType::Regular,443 )?;444 collection.check_is_external()?;445446 <PalletNft<T>>::destroy_collection(collection, &cross_sender)447 .map_err(Self::map_unique_err_to_proxy)?;448449 Self::deposit_event(Event::CollectionDestroyed {450 issuer: sender,451 collection_id,452 });453454 Ok(())455 }456457 458 459 460 461 462 463 464 465 #[transactional]466 #[pallet::weight(<SelfWeightOf<T>>::change_collection_issuer())]467 pub fn change_collection_issuer(468 origin: OriginFor<T>,469 collection_id: RmrkCollectionId,470 new_issuer: <T::Lookup as StaticLookup>::Source,471 ) -> DispatchResult {472 let sender = ensure_signed(origin)?;473474 let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;475 collection.check_is_external()?;476477 let new_issuer = T::Lookup::lookup(new_issuer)?;478479 Self::change_collection_owner(480 Self::unique_collection_id(collection_id)?,481 misc::CollectionType::Regular,482 sender.clone(),483 new_issuer.clone(),484 )?;485486 Self::deposit_event(Event::IssuerChanged {487 old_issuer: sender,488 new_issuer,489 collection_id,490 });491492 Ok(())493 }494495 496 497 498 499 500 501 502 #[transactional]503 #[pallet::weight(<SelfWeightOf<T>>::lock_collection())]504 pub fn lock_collection(505 origin: OriginFor<T>,506 collection_id: RmrkCollectionId,507 ) -> DispatchResult {508 let sender = ensure_signed(origin)?;509 let cross_sender = T::CrossAccountId::from_sub(sender.clone());510511 let collection = Self::get_typed_nft_collection(512 Self::unique_collection_id(collection_id)?,513 misc::CollectionType::Regular,514 )?;515 collection.check_is_external()?;516517 Self::check_collection_owner(&collection, &cross_sender)?;518519 let token_count = collection.total_supply();520521 let mut collection = collection.into_inner();522 collection.limits.token_limit = Some(token_count);523 collection.save()?;524525 Self::deposit_event(Event::CollectionLocked {526 issuer: sender,527 collection_id,528 });529530 Ok(())531 }532533 534 535 536 537 538 539 540 541 542 543 544 545 546 #[transactional]547 #[pallet::weight(<SelfWeightOf<T>>::mint_nft(resources.as_ref().map(|r| r.len() as u32).unwrap_or(0)))]548 pub fn mint_nft(549 origin: OriginFor<T>,550 owner: Option<T::AccountId>,551 collection_id: RmrkCollectionId,552 recipient: Option<T::AccountId>,553 royalty_amount: Option<Permill>,554 metadata: RmrkString,555 transferable: bool,556 resources: Option<BoundedVec<RmrkResourceTypes, MaxResourcesOnMint>>,557 ) -> DispatchResult {558 let sender = ensure_signed(origin)?;559 let cross_sender = T::CrossAccountId::from_sub(sender.clone());560561 let owner = owner.unwrap_or(sender.clone());562 let cross_owner = T::CrossAccountId::from_sub(owner.clone());563564 let collection = Self::get_typed_nft_collection(565 Self::unique_collection_id(collection_id)?,566 misc::CollectionType::Regular,567 )?;568 collection.check_is_external()?;569570 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {571 recipient: recipient.unwrap_or_else(|| owner.clone()),572 amount,573 });574575 let nft_id = Self::create_nft(576 &cross_sender,577 &cross_owner,578 &collection,579 [580 Self::encode_rmrk_property(TokenType, &NftType::Regular)?,581 Self::encode_rmrk_property(Transferable, &transferable)?,582 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,583 Self::encode_rmrk_property(RoyaltyInfo, &royalty_info)?,584 Self::encode_rmrk_property(Metadata, &metadata)?,585 Self::encode_rmrk_property(Equipped, &false)?,586 Self::encode_rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,587 Self::encode_rmrk_property(NextResourceId, &(0 as RmrkResourceId))?,588 Self::encode_rmrk_property(PendingChildren, &PendingChildrenSet::new())?,589 Self::encode_rmrk_property(AssociatedBases, &BasesMap::new())?,590 ]591 .into_iter(),592 )593 .map_err(|err| match err {594 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),595 err => Self::map_unique_err_to_proxy(err),596 })?;597598 if let Some(resources) = resources {599 for resource in resources {600 Self::resource_add(sender.clone(), collection.id, nft_id, resource)?;601 }602 }603604 Self::deposit_event(Event::NftMinted {605 owner,606 collection_id,607 nft_id: nft_id.0,608 });609610 Ok(())611 }612613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 #[transactional]630 #[pallet::weight(<SelfWeightOf<T>>::burn_nft(*max_burns))]631 pub fn burn_nft(632 origin: OriginFor<T>,633 collection_id: RmrkCollectionId,634 nft_id: RmrkNftId,635 max_burns: u32,636 ) -> DispatchResult {637 let sender = ensure_signed(origin)?;638 let cross_sender = T::CrossAccountId::from_sub(sender.clone());639640 let collection = Self::get_typed_nft_collection(641 Self::unique_collection_id(collection_id)?,642 misc::CollectionType::Regular,643 )?;644 collection.check_is_external()?;645646 Self::destroy_nft(647 cross_sender,648 Self::unique_collection_id(collection_id)?,649 nft_id.into(),650 max_burns,651 <Error<T>>::NoPermission,652 )653 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;654655 Self::deposit_event(Event::NFTBurned {656 owner: sender,657 nft_id,658 });659660 Ok(())661 }662663 664 665 666 667 668 669 670 671 672 673 674 675 676 #[transactional]677 #[pallet::weight(<SelfWeightOf<T>>::send())]678 pub fn send(679 origin: OriginFor<T>,680 rmrk_collection_id: RmrkCollectionId,681 rmrk_nft_id: RmrkNftId,682 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,683 ) -> DispatchResult {684 let sender = ensure_signed(origin.clone())?;685 let cross_sender = T::CrossAccountId::from_sub(sender.clone());686687 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;688 let nft_id = rmrk_nft_id.into();689690 let collection =691 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;692 collection.check_is_external()?;693694 let token_data =695 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;696697 let from = token_data.owner;698699 ensure!(700 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,701 <Error<T>>::NonTransferable702 );703704 ensure!(705 Self::get_nft_property_decoded::<Option<PendingTarget>>(706 collection_id,707 nft_id,708 RmrkProperty::PendingNftAccept709 )?710 .is_none(),711 <Error<T>>::NoPermission712 );713714 let target_owner;715 let approval_required;716717 match new_owner {718 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {719 target_owner = T::CrossAccountId::from_sub(account_id.clone());720 approval_required = false;721 }722 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(723 target_collection_id,724 target_nft_id,725 ) => {726 let target_collection_id = Self::unique_collection_id(target_collection_id)?;727728 let target_nft_budget = budget::Value::new(NESTING_BUDGET);729730 let target_nft_owner = <PalletStructure<T>>::get_checked_topmost_owner(731 target_collection_id,732 target_nft_id.into(),733 Some((collection_id, nft_id)),734 &target_nft_budget,735 )736 .map_err(Self::map_unique_err_to_proxy)?;737738 approval_required = cross_sender != target_nft_owner;739740 if approval_required {741 target_owner = target_nft_owner;742743 <PalletNft<T>>::set_scoped_token_property(744 collection.id,745 nft_id,746 RMRK_SCOPE,747 Self::encode_rmrk_property::<Option<PendingTarget>>(748 PendingNftAccept,749 &Some((target_collection_id, target_nft_id.into())),750 )?,751 )?;752753 Self::insert_pending_child(754 (target_collection_id, target_nft_id.into()),755 (rmrk_collection_id, rmrk_nft_id),756 )?;757 } else {758 target_owner = T::CrossTokenAddressMapping::token_to_address(759 target_collection_id,760 target_nft_id.into(),761 );762 }763 }764 }765766 let src_nft_budget = budget::Value::new(NESTING_BUDGET);767768 <PalletNft<T>>::transfer_from(769 &collection,770 &cross_sender,771 &from,772 &target_owner,773 nft_id,774 &src_nft_budget,775 )776 .map_err(Self::map_unique_err_to_proxy)?;777778 Self::deposit_event(Event::NFTSent {779 sender,780 recipient: new_owner,781 collection_id: rmrk_collection_id,782 nft_id: rmrk_nft_id,783 approval_required,784 });785786 Ok(())787 }788789 790 791 792 793 794 795 796 797 798 799 800 801 #[transactional]802 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]803 pub fn accept_nft(804 origin: OriginFor<T>,805 rmrk_collection_id: RmrkCollectionId,806 rmrk_nft_id: RmrkNftId,807 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,808 ) -> DispatchResult {809 let sender = ensure_signed(origin.clone())?;810 let cross_sender = T::CrossAccountId::from_sub(sender.clone());811812 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;813 let nft_id = rmrk_nft_id.into();814815 let collection =816 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;817 collection.check_is_external()?;818819 let new_cross_owner = match new_owner {820 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {821 T::CrossAccountId::from_sub(account_id.clone())822 }823 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(824 target_collection_id,825 target_nft_id,826 ) => {827 let target_collection_id = Self::unique_collection_id(target_collection_id)?;828829 T::CrossTokenAddressMapping::token_to_address(830 target_collection_id,831 TokenId(target_nft_id),832 )833 }834 };835836 let budget = budget::Value::new(NESTING_BUDGET);837838 <PalletNft<T>>::transfer(839 &collection,840 &cross_sender,841 &new_cross_owner,842 nft_id,843 &budget,844 )845 .map_err(|err| {846 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {847 <Error<T>>::CannotAcceptNonOwnedNft.into()848 } else {849 Self::map_unique_err_to_proxy(err)850 }851 })?;852853 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(854 collection_id,855 nft_id,856 RmrkProperty::PendingNftAccept,857 )?;858859 if let Some(pending_target) = pending_target {860 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;861862 <PalletNft<T>>::set_scoped_token_property(863 collection.id,864 nft_id,865 RMRK_SCOPE,866 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,867 )?;868 }869870 Self::deposit_event(Event::NFTAccepted {871 sender,872 recipient: new_owner,873 collection_id: rmrk_collection_id,874 nft_id: rmrk_nft_id,875 });876877 Ok(())878 }879880 881 882 883 884 885 886 887 888 889 890 891 #[transactional]892 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]893 pub fn reject_nft(894 origin: OriginFor<T>,895 rmrk_collection_id: RmrkCollectionId,896 rmrk_nft_id: RmrkNftId,897 ) -> DispatchResult {898 let sender = ensure_signed(origin)?;899 let cross_sender = T::CrossAccountId::from_sub(sender.clone());900901 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;902 let nft_id = rmrk_nft_id.into();903904 let collection =905 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;906 collection.check_is_external()?;907908 ensure!(909 <TokenData<T>>::get((collection_id, nft_id)).is_some(),910 <Error<T>>::NoAvailableNftId911 );912913 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(914 collection_id,915 nft_id,916 RmrkProperty::PendingNftAccept,917 )?;918919 match pending_target {920 Some(pending_target) => {921 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?922 }923 None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),924 }925926 Self::destroy_nft(927 cross_sender,928 collection_id,929 nft_id,930 NESTING_BUDGET,931 <Error<T>>::CannotRejectNonOwnedNft,932 )933 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;934935 Self::deposit_event(Event::NFTRejected {936 sender,937 collection_id: rmrk_collection_id,938 nft_id: rmrk_nft_id,939 });940941 Ok(())942 }943944 945 946 947 948 949 950 951 952 953 954 955 956 957 #[transactional]958 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]959 pub fn accept_resource(960 origin: OriginFor<T>,961 rmrk_collection_id: RmrkCollectionId,962 rmrk_nft_id: RmrkNftId,963 resource_id: RmrkResourceId,964 ) -> DispatchResult {965 let sender = ensure_signed(origin)?;966 let cross_sender = T::CrossAccountId::from_sub(sender);967968 let collection_id = Self::unique_collection_id(rmrk_collection_id)969 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;970 let collection =971 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;972 collection.check_is_external()?;973974 let nft_id = rmrk_nft_id.into();975976 let budget = budget::Value::new(NESTING_BUDGET);977978 let nft_owner =979 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)980 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;981982 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {983 ensure!(res.pending, <Error<T>>::ResourceNotPending);984 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);985986 res.pending = false;987988 Ok(())989 })?;990991 Self::deposit_event(Event::<T>::ResourceAccepted {992 nft_id: rmrk_nft_id,993 resource_id,994 });995996 Ok(())997 }998999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 #[transactional]1012 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]1013 pub fn accept_resource_removal(1014 origin: OriginFor<T>,1015 rmrk_collection_id: RmrkCollectionId,1016 rmrk_nft_id: RmrkNftId,1017 resource_id: RmrkResourceId,1018 ) -> DispatchResult {1019 let sender = ensure_signed(origin)?;1020 let cross_sender = T::CrossAccountId::from_sub(sender);10211022 let collection_id = Self::unique_collection_id(rmrk_collection_id)1023 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;1024 let collection =1025 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1026 collection.check_is_external()?;10271028 let nft_id = rmrk_nft_id.into();10291030 let budget = budget::Value::new(NESTING_BUDGET);10311032 let nft_owner =1033 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1034 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;10351036 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10371038 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10391040 let resource_info = <PalletNft<T>>::token_aux_property((1041 collection_id,1042 nft_id,1043 RMRK_SCOPE,1044 resource_id_key.clone(),1045 ))1046 .ok_or(<Error<T>>::ResourceDoesntExist)?;10471048 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10491050 ensure!(1051 resource_info.pending_removal,1052 <Error<T>>::ResourceNotPending1053 );10541055 <PalletNft<T>>::remove_token_aux_property(1056 collection_id,1057 nft_id,1058 RMRK_SCOPE,1059 resource_id_key,1060 );10611062 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1063 let base_id = resource.base;10641065 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1066 }10671068 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1069 nft_id: rmrk_nft_id,1070 resource_id,1071 });10721073 Ok(())1074 }10751076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 #[transactional]1093 #[pallet::weight(<SelfWeightOf<T>>::set_property())]1094 pub fn set_property(1095 origin: OriginFor<T>,1096 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,1097 maybe_nft_id: Option<RmrkNftId>,1098 key: RmrkKeyString,1099 value: RmrkValueString,1100 ) -> DispatchResult {1101 let sender = ensure_signed(origin)?;1102 let sender = T::CrossAccountId::from_sub(sender);11031104 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1105 let collection =1106 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1107 collection.check_is_external()?;11081109 let budget = budget::Value::new(NESTING_BUDGET);11101111 match maybe_nft_id {1112 Some(nft_id) => {1113 let token_id: TokenId = nft_id.into();11141115 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1116 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;11171118 <PalletNft<T>>::set_scoped_token_property(1119 collection_id,1120 token_id,1121 RMRK_SCOPE,1122 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1123 )?;1124 }1125 None => {1126 let collection = Self::get_typed_nft_collection(1127 collection_id,1128 misc::CollectionType::Regular,1129 )?;11301131 Self::check_collection_owner(&collection, &sender)?;11321133 <PalletCommon<T>>::set_scoped_collection_property(1134 collection_id,1135 RMRK_SCOPE,1136 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1137 )?;1138 }1139 }11401141 Self::deposit_event(Event::PropertySet {1142 collection_id: rmrk_collection_id,1143 maybe_nft_id,1144 key,1145 value,1146 });11471148 Ok(())1149 }11501151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 #[transactional]1166 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]1167 pub fn set_priority(1168 origin: OriginFor<T>,1169 rmrk_collection_id: RmrkCollectionId,1170 rmrk_nft_id: RmrkNftId,1171 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1172 ) -> DispatchResult {1173 let sender = ensure_signed(origin)?;1174 let sender = T::CrossAccountId::from_sub(sender);11751176 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1177 let nft_id = rmrk_nft_id.into();11781179 let collection =1180 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1181 collection.check_is_external()?;11821183 let budget = budget::Value::new(NESTING_BUDGET);11841185 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1186 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;11871188 <PalletNft<T>>::set_scoped_token_property(1189 collection_id,1190 nft_id,1191 RMRK_SCOPE,1192 Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1193 )?;11941195 Self::deposit_event(Event::<T>::PrioritySet {1196 collection_id: rmrk_collection_id,1197 nft_id: rmrk_nft_id,1198 });11991200 Ok(())1201 }12021203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 #[transactional]1217 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1218 pub fn add_basic_resource(1219 origin: OriginFor<T>,1220 rmrk_collection_id: RmrkCollectionId,1221 nft_id: RmrkNftId,1222 resource: RmrkBasicResource,1223 ) -> DispatchResult {1224 let sender = ensure_signed(origin.clone())?;12251226 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1227 let collection =1228 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1229 collection.check_is_external()?;12301231 let resource_id = Self::resource_add(1232 sender,1233 collection_id,1234 nft_id.into(),1235 RmrkResourceTypes::Basic(resource),1236 )?;12371238 Self::deposit_event(Event::ResourceAdded {1239 nft_id,1240 resource_id,1241 });1242 Ok(())1243 }12441245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 #[transactional]1259 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1260 pub fn add_composable_resource(1261 origin: OriginFor<T>,1262 rmrk_collection_id: RmrkCollectionId,1263 nft_id: RmrkNftId,1264 resource: RmrkComposableResource,1265 ) -> DispatchResult {1266 let sender = ensure_signed(origin.clone())?;12671268 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1269 let collection =1270 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1271 collection.check_is_external()?;12721273 let base_id = resource.base;12741275 let resource_id = Self::resource_add(1276 sender,1277 collection_id,1278 nft_id.into(),1279 RmrkResourceTypes::Composable(resource),1280 )?;12811282 <PalletNft<T>>::try_mutate_token_aux_property(1283 collection_id,1284 nft_id.into(),1285 RMRK_SCOPE,1286 Self::get_scoped_property_key(AssociatedBases)?,1287 |value| -> DispatchResult {1288 let mut bases: BasesMap = match value {1289 Some(value) => Self::decode_property_value(value)?,1290 None => BasesMap::new(),1291 };12921293 *bases.entry(base_id).or_insert(0) += 1;12941295 *value = Some(Self::encode_property_value(&bases)?);1296 Ok(())1297 },1298 )?;12991300 Self::deposit_event(Event::ResourceAdded {1301 nft_id,1302 resource_id,1303 });1304 Ok(())1305 }13061307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 #[transactional]1321 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1322 pub fn add_slot_resource(1323 origin: OriginFor<T>,1324 rmrk_collection_id: RmrkCollectionId,1325 nft_id: RmrkNftId,1326 resource: RmrkSlotResource,1327 ) -> DispatchResult {1328 let sender = ensure_signed(origin.clone())?;13291330 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1331 let collection =1332 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1333 collection.check_is_external()?;13341335 let resource_id = Self::resource_add(1336 sender,1337 collection_id,1338 nft_id.into(),1339 RmrkResourceTypes::Slot(resource),1340 )?;13411342 Self::deposit_event(Event::ResourceAdded {1343 nft_id,1344 resource_id,1345 });1346 Ok(())1347 }13481349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 #[transactional]1362 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1363 pub fn remove_resource(1364 origin: OriginFor<T>,1365 rmrk_collection_id: RmrkCollectionId,1366 nft_id: RmrkNftId,1367 resource_id: RmrkResourceId,1368 ) -> DispatchResult {1369 let sender = ensure_signed(origin.clone())?;13701371 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1372 let collection =1373 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1374 collection.check_is_external()?;13751376 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13771378 Self::deposit_event(Event::ResourceRemoval {1379 nft_id,1380 resource_id,1381 });1382 Ok(())1383 }1384 }1385}13861387impl<T: Config> Pallet<T> {1388 1389 pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1390 let key = rmrk_key.to_key::<T>()?;13911392 let scoped_key = RMRK_SCOPE1393 .apply(key)1394 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;13951396 Ok(scoped_key)1397 }13981399 1400 1401 pub fn encode_rmrk_property<E: Encode>(1402 rmrk_key: RmrkProperty,1403 value: &E,1404 ) -> Result<Property, DispatchError> {1405 let key = rmrk_key.to_key::<T>()?;14061407 let value = Self::encode_property_value(value)?;14081409 let property = Property { key, value };14101411 Ok(property)1412 }14131414 1415 pub fn encode_property_value<E: Encode, S: Get<u32>>(1416 value: &E,1417 ) -> Result<BoundedBytes<S>, DispatchError> {1418 let value = value1419 .encode()1420 .try_into()1421 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;14221423 Ok(value)1424 }14251426 1427 pub fn decode_property_value<D: Decode, S: Get<u32>>(1428 vec: &BoundedBytes<S>,1429 ) -> Result<D, DispatchError> {1430 vec.decode()1431 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1432 }14331434 1435 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1436 where1437 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1438 {1439 vec.rebind()1440 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1441 }14421443 1444 1445 1446 fn init_collection(1447 sender: T::CrossAccountId,1448 data: CreateCollectionData<T::AccountId>,1449 properties: impl Iterator<Item = Property>,1450 ) -> Result<CollectionId, DispatchError> {1451 let collection_id = <PalletNft<T>>::init_collection(sender, data, true);14521453 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1454 return Err(<Error<T>>::NoAvailableCollectionId.into());1455 }14561457 <PalletCommon<T>>::set_scoped_collection_properties(1458 collection_id?,1459 RMRK_SCOPE,1460 properties,1461 )?;14621463 collection_id1464 }14651466 1467 1468 1469 pub fn create_nft(1470 sender: &T::CrossAccountId,1471 owner: &T::CrossAccountId,1472 collection: &NonfungibleHandle<T>,1473 properties: impl Iterator<Item = Property>,1474 ) -> Result<TokenId, DispatchError> {1475 let data = CreateNftExData {1476 properties: BoundedVec::default(),1477 owner: owner.clone(),1478 };14791480 let budget = budget::Value::new(NESTING_BUDGET);14811482 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;14831484 let nft_id = <PalletNft<T>>::current_token_id(collection.id);14851486 <PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;14871488 Ok(nft_id)1489 }14901491 1492 1493 1494 fn destroy_nft(1495 sender: T::CrossAccountId,1496 collection_id: CollectionId,1497 token_id: TokenId,1498 max_burns: u32,1499 error_if_not_owned: Error<T>,1500 ) -> DispatchResultWithPostInfo {1501 let collection =1502 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;15031504 let token_data =1505 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;15061507 let from = token_data.owner;15081509 let owner_check_budget = budget::Value::new(NESTING_BUDGET);15101511 ensure!(1512 <PalletStructure<T>>::check_indirectly_owned(1513 sender.clone(),1514 collection_id,1515 token_id,1516 None,1517 &owner_check_budget1518 )?,1519 error_if_not_owned,1520 );15211522 let burns_budget = budget::Value::new(max_burns);1523 let breadth_budget = budget::Value::new(max_burns);15241525 <PalletNft<T>>::burn_recursively(1526 &collection,1527 &from,1528 token_id,1529 &burns_budget,1530 &breadth_budget,1531 )1532 }15331534 1535 fn insert_pending_child(1536 target: (CollectionId, TokenId),1537 child: (RmrkCollectionId, RmrkNftId),1538 ) -> DispatchResult {1539 Self::mutate_pending_children(target, |pending_children| {1540 pending_children.insert(child);1541 })1542 }15431544 1545 fn remove_pending_child(1546 target: (CollectionId, TokenId),1547 child: (RmrkCollectionId, RmrkNftId),1548 ) -> DispatchResult {1549 Self::mutate_pending_children(target, |pending_children| {1550 pending_children.remove(&child);1551 })1552 }15531554 1555 1556 fn mutate_pending_children(1557 (target_collection_id, target_nft_id): (CollectionId, TokenId),1558 f: impl FnOnce(&mut PendingChildrenSet),1559 ) -> DispatchResult {1560 <PalletNft<T>>::try_mutate_token_aux_property(1561 target_collection_id,1562 target_nft_id,1563 RMRK_SCOPE,1564 Self::get_scoped_property_key(PendingChildren)?,1565 |pending_children| -> DispatchResult {1566 let mut map = match pending_children {1567 Some(map) => Self::decode_property_value(map)?,1568 None => PendingChildrenSet::new(),1569 };15701571 f(&mut map);15721573 *pending_children = Some(Self::encode_property_value(&map)?);15741575 Ok(())1576 },1577 )1578 }15791580 1581 1582 fn iterate_pending_children(1583 collection_id: CollectionId,1584 nft_id: TokenId,1585 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1586 let property = <PalletNft<T>>::token_aux_property((1587 collection_id,1588 nft_id,1589 RMRK_SCOPE,1590 Self::get_scoped_property_key(PendingChildren)?,1591 ));15921593 let pending_children = match property {1594 Some(map) => Self::decode_property_value(&map)?,1595 None => PendingChildrenSet::new(),1596 };15971598 Ok(pending_children.into_iter())1599 }16001601 1602 1603 1604 1605 fn acquire_next_resource_id(1606 collection_id: CollectionId,1607 nft_id: TokenId,1608 ) -> Result<RmrkResourceId, DispatchError> {1609 let resource_id: RmrkResourceId =1610 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;16111612 let next_id = resource_id1613 .checked_add(1)1614 .ok_or(<Error<T>>::NoAvailableResourceId)?;16151616 <PalletNft<T>>::set_scoped_token_property(1617 collection_id,1618 nft_id,1619 RMRK_SCOPE,1620 Self::encode_rmrk_property(NextResourceId, &next_id)?,1621 )?;16221623 Ok(resource_id)1624 }16251626 1627 1628 fn resource_add(1629 sender: T::AccountId,1630 collection_id: CollectionId,1631 nft_id: TokenId,1632 resource: RmrkResourceTypes,1633 ) -> Result<RmrkResourceId, DispatchError> {1634 let collection =1635 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1636 ensure!(collection.owner == sender, Error::<T>::NoPermission);16371638 let sender = T::CrossAccountId::from_sub(sender);1639 let budget = budget::Value::new(NESTING_BUDGET);16401641 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1642 .map_err(Self::map_unique_err_to_proxy)?;16431644 let pending = sender != nft_owner;16451646 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16471648 let resource_info = RmrkResourceInfo {1649 id,1650 resource,1651 pending,1652 pending_removal: false,1653 };16541655 <PalletNft<T>>::try_mutate_token_aux_property(1656 collection_id,1657 nft_id,1658 RMRK_SCOPE,1659 Self::get_scoped_property_key(ResourceId(id))?,1660 |value| -> DispatchResult {1661 *value = Some(Self::encode_property_value(&resource_info)?);16621663 Ok(())1664 },1665 )?;16661667 Ok(id)1668 }16691670 1671 1672 fn resource_remove(1673 sender: T::AccountId,1674 collection_id: CollectionId,1675 nft_id: TokenId,1676 resource_id: RmrkResourceId,1677 ) -> DispatchResult {1678 let collection =1679 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1680 ensure!(collection.owner == sender, Error::<T>::NoPermission);16811682 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;16831684 let resource = <PalletNft<T>>::token_aux_property((1685 collection_id,1686 nft_id,1687 RMRK_SCOPE,1688 resource_id_key.clone(),1689 ))1690 .ok_or(<Error<T>>::ResourceDoesntExist)?;16911692 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;16931694 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1695 let topmost_owner =1696 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;16971698 let sender = T::CrossAccountId::from_sub(sender);1699 if topmost_owner == sender {1700 <PalletNft<T>>::remove_token_aux_property(1701 collection_id,1702 nft_id,1703 RMRK_SCOPE,1704 Self::get_scoped_property_key(ResourceId(resource_id))?,1705 );17061707 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1708 let base_id = resource.base;17091710 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1711 }1712 } else {1713 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1714 res.pending_removal = true;17151716 Ok(())1717 })?;1718 }17191720 Ok(())1721 }17221723 1724 1725 fn remove_associated_base_id(1726 collection_id: CollectionId,1727 nft_id: TokenId,1728 base_id: RmrkBaseId,1729 ) -> DispatchResult {1730 <PalletNft<T>>::try_mutate_token_aux_property(1731 collection_id,1732 nft_id,1733 RMRK_SCOPE,1734 Self::get_scoped_property_key(AssociatedBases)?,1735 |value| -> DispatchResult {1736 let mut bases: BasesMap = match value {1737 Some(value) => Self::decode_property_value(value)?,1738 None => BasesMap::new(),1739 };17401741 let remaining = bases.get(&base_id);17421743 if let Some(remaining) = remaining {1744 if let Some(0) | None = remaining.checked_sub(1) {1745 bases.remove(&base_id);1746 }1747 }17481749 *value = Some(Self::encode_property_value(&bases)?);1750 Ok(())1751 },1752 )1753 }17541755 1756 fn try_mutate_resource_info(1757 collection_id: CollectionId,1758 nft_id: TokenId,1759 resource_id: RmrkResourceId,1760 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1761 ) -> DispatchResult {1762 <PalletNft<T>>::try_mutate_token_aux_property(1763 collection_id,1764 nft_id,1765 RMRK_SCOPE,1766 Self::get_scoped_property_key(ResourceId(resource_id))?,1767 |value| match value {1768 Some(value) => {1769 let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;17701771 f(&mut resource_info)?;17721773 *value = Self::encode_property_value(&resource_info)?;17741775 Ok(())1776 }1777 None => Err(<Error<T>>::ResourceDoesntExist.into()),1778 },1779 )1780 }17811782 1783 fn change_collection_owner(1784 collection_id: CollectionId,1785 collection_type: misc::CollectionType,1786 sender: T::AccountId,1787 new_owner: T::AccountId,1788 ) -> DispatchResult {1789 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1790 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;17911792 let mut collection = collection.into_inner();17931794 collection.owner = new_owner;1795 collection.save()1796 }17971798 1799 pub fn check_collection_owner(1800 collection: &NonfungibleHandle<T>,1801 account: &T::CrossAccountId,1802 ) -> DispatchResult {1803 collection1804 .check_is_owner(account)1805 .map_err(Self::map_unique_err_to_proxy)1806 }18071808 1809 pub fn last_collection_idx() -> RmrkCollectionId {1810 <CollectionIndex<T>>::get()1811 }18121813 1814 pub fn unique_collection_id(1815 rmrk_collection_id: RmrkCollectionId,1816 ) -> Result<CollectionId, DispatchError> {1817 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1818 .map_err(|_| <Error<T>>::CollectionUnknown.into())1819 }18201821 1822 pub fn rmrk_collection_id(1823 unique_collection_id: CollectionId,1824 ) -> Result<RmrkCollectionId, DispatchError> {1825 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1826 }18271828 1829 pub fn get_nft_collection(1830 collection_id: CollectionId,1831 ) -> Result<NonfungibleHandle<T>, DispatchError> {1832 let collection = <CollectionHandle<T>>::try_get(collection_id)1833 .map_err(|_| <Error<T>>::CollectionUnknown)?;18341835 match collection.mode {1836 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1837 _ => Err(<Error<T>>::CollectionUnknown.into()),1838 }1839 }18401841 1842 pub fn collection_exists(collection_id: CollectionId) -> bool {1843 <CollectionHandle<T>>::try_get(collection_id).is_ok()1844 }18451846 1847 pub fn get_collection_property(1848 collection_id: CollectionId,1849 key: RmrkProperty,1850 ) -> Result<PropertyValue, DispatchError> {1851 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1852 .get(&Self::get_scoped_property_key(key)?)1853 .ok_or(<Error<T>>::CollectionUnknown)?1854 .clone();18551856 Ok(collection_property)1857 }18581859 1860 pub fn get_collection_property_decoded<V: Decode>(1861 collection_id: CollectionId,1862 key: RmrkProperty,1863 ) -> Result<V, DispatchError> {1864 Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1865 }18661867 1868 1869 1870 pub fn get_collection_type(1871 collection_id: CollectionId,1872 ) -> Result<misc::CollectionType, DispatchError> {1873 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1874 if err != <Error<T>>::CollectionUnknown.into() {1875 <Error<T>>::CorruptedCollectionType.into()1876 } else {1877 err1878 }1879 })1880 }18811882 1883 1884 pub fn ensure_collection_type(1885 collection_id: CollectionId,1886 collection_type: misc::CollectionType,1887 ) -> DispatchResult {1888 let actual_type = Self::get_collection_type(collection_id)?;1889 ensure!(1890 actual_type == collection_type,1891 <CommonError<T>>::NoPermission1892 );18931894 Ok(())1895 }18961897 1898 pub fn get_typed_nft_collection(1899 collection_id: CollectionId,1900 collection_type: misc::CollectionType,1901 ) -> Result<NonfungibleHandle<T>, DispatchError> {1902 Self::ensure_collection_type(collection_id, collection_type)?;19031904 Self::get_nft_collection(collection_id)1905 }19061907 1908 1909 pub fn get_typed_nft_collection_mapped(1910 rmrk_collection_id: RmrkCollectionId,1911 collection_type: misc::CollectionType,1912 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1913 let unique_collection_id = match collection_type {1914 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1915 _ => rmrk_collection_id.into(),1916 };19171918 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;19191920 Ok((collection, unique_collection_id))1921 }19221923 1924 pub fn get_nft_property(1925 collection_id: CollectionId,1926 nft_id: TokenId,1927 key: RmrkProperty,1928 ) -> Result<PropertyValue, DispatchError> {1929 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1930 .get(&Self::get_scoped_property_key(key)?)1931 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1932 .clone();19331934 Ok(nft_property)1935 }19361937 1938 pub fn get_nft_property_decoded<V: Decode>(1939 collection_id: CollectionId,1940 nft_id: TokenId,1941 key: RmrkProperty,1942 ) -> Result<V, DispatchError> {1943 Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1944 }19451946 1947 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1948 <TokenData<T>>::contains_key((collection_id, nft_id))1949 }19501951 1952 1953 1954 pub fn get_nft_type(1955 collection_id: CollectionId,1956 token_id: TokenId,1957 ) -> Result<NftType, DispatchError> {1958 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1959 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1960 }19611962 1963 pub fn ensure_nft_type(1964 collection_id: CollectionId,1965 token_id: TokenId,1966 nft_type: NftType,1967 ) -> DispatchResult {1968 let actual_type = Self::get_nft_type(collection_id, token_id)?;1969 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);19701971 Ok(())1972 }19731974 1975 1976 pub fn ensure_nft_owner(1977 collection_id: CollectionId,1978 token_id: TokenId,1979 possible_owner: &T::CrossAccountId,1980 nesting_budget: &dyn budget::Budget,1981 ) -> DispatchResult {1982 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1983 possible_owner.clone(),1984 collection_id,1985 token_id,1986 None,1987 nesting_budget,1988 )1989 .map_err(Self::map_unique_err_to_proxy)?;19901991 ensure!(is_owned, <Error<T>>::NoPermission);19921993 Ok(())1994 }19951996 1997 1998 pub fn filter_user_properties<Key, Value, R, Mapper>(1999 collection_id: CollectionId,2000 token_id: Option<TokenId>,2001 filter_keys: Option<Vec<RmrkPropertyKey>>,2002 mapper: Mapper,2003 ) -> Result<Vec<R>, DispatchError>2004 where2005 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2006 Value: Decode + Default,2007 Mapper: Fn(Key, Value) -> R,2008 {2009 filter_keys2010 .map(|keys| {2011 let properties = keys2012 .into_iter()2013 .filter_map(|key| {2014 let key: Key = key.try_into().ok()?;20152016 let value = match token_id {2017 Some(token_id) => Self::get_nft_property_decoded(2018 collection_id,2019 token_id,2020 UserProperty(key.as_ref()),2021 ),2022 None => Self::get_collection_property_decoded(2023 collection_id,2024 UserProperty(key.as_ref()),2025 ),2026 }2027 .ok()?;20282029 Some(mapper(key, value))2030 })2031 .collect();20322033 Ok(properties)2034 })2035 .unwrap_or_else(|| {2036 let properties =2037 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20382039 Ok(properties)2040 })2041 }20422043 2044 2045 pub fn iterate_user_properties<Key, Value, R, Mapper>(2046 collection_id: CollectionId,2047 token_id: Option<TokenId>,2048 mapper: Mapper,2049 ) -> Result<impl Iterator<Item = R>, DispatchError>2050 where2051 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2052 Value: Decode + Default,2053 Mapper: Fn(Key, Value) -> R,2054 {2055 let properties = match token_id {2056 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2057 None => <PalletCommon<T>>::collection_properties(collection_id),2058 };20592060 let properties = properties.into_iter().filter_map(move |(key, value)| {2061 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20622063 let key: Key = key.to_vec().try_into().ok()?;2064 let value: Value = value.decode().ok()?;20652066 Some(mapper(key, value))2067 });20682069 Ok(properties)2070 }20712072 2073 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2074 map_unique_err_to_proxy! {2075 match err {2076 CommonError::NoPermission => NoPermission,2077 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2078 CommonError::PublicMintingNotAllowed => NoPermission,2079 CommonError::TokenNotFound => NoAvailableNftId,2080 CommonError::ApprovedValueTooLow => NoPermission,2081 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2082 StructureError::TokenNotFound => NoAvailableNftId,2083 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2084 }2085 }2086 }2087}