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)?745 .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;746747 approval_required = cross_sender != target_nft_owner;748749 if approval_required {750 target_owner = target_nft_owner;751752 <PalletNft<T>>::set_scoped_token_property(753 collection.id,754 nft_id,755 RMRK_SCOPE,756 Self::encode_rmrk_property::<Option<PendingTarget>>(757 PendingNftAccept,758 &Some((target_collection_id, target_nft_id.into())),759 )?,760 )?;761762 Self::insert_pending_child(763 (target_collection_id, target_nft_id.into()),764 (rmrk_collection_id, rmrk_nft_id),765 )?;766 } else {767 target_owner = T::CrossTokenAddressMapping::token_to_address(768 target_collection_id,769 target_nft_id.into(),770 );771 }772 }773 }774775 let src_nft_budget = budget::Value::new(NESTING_BUDGET);776777 <PalletNft<T>>::transfer_from(778 &collection,779 &cross_sender,780 &from,781 &target_owner,782 nft_id,783 &src_nft_budget,784 )785 .map_err(Self::map_unique_err_to_proxy)?;786787 Self::deposit_event(Event::NFTSent {788 sender,789 recipient: new_owner,790 collection_id: rmrk_collection_id,791 nft_id: rmrk_nft_id,792 approval_required,793 });794795 Ok(())796 }797798 799 800 801 802 803 804 805 806 807 808 809 810 811 #[pallet::call_index(7)]812 #[pallet::weight(<SelfWeightOf<T>>::accept_nft())]813 pub fn accept_nft(814 origin: OriginFor<T>,815 rmrk_collection_id: RmrkCollectionId,816 rmrk_nft_id: RmrkNftId,817 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,818 ) -> DispatchResult {819 let sender = ensure_signed(origin.clone())?;820 let cross_sender = T::CrossAccountId::from_sub(sender.clone());821822 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;823 let nft_id = rmrk_nft_id.into();824825 let collection =826 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;827 collection.check_is_external()?;828829 let new_cross_owner = match new_owner {830 RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {831 T::CrossAccountId::from_sub(account_id.clone())832 }833 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(834 target_collection_id,835 target_nft_id,836 ) => {837 let target_collection_id = Self::unique_collection_id(target_collection_id)?;838839 T::CrossTokenAddressMapping::token_to_address(840 target_collection_id,841 TokenId(target_nft_id),842 )843 }844 };845846 let budget = budget::Value::new(NESTING_BUDGET);847848 <PalletNft<T>>::transfer(849 &collection,850 &cross_sender,851 &new_cross_owner,852 nft_id,853 &budget,854 )855 .map_err(|err| {856 if err == <CommonError<T>>::UserIsNotAllowedToNest.into() {857 <Error<T>>::CannotAcceptNonOwnedNft.into()858 } else {859 Self::map_unique_err_to_proxy(err)860 }861 })?;862863 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(864 collection_id,865 nft_id,866 RmrkProperty::PendingNftAccept,867 )?;868869 if let Some(pending_target) = pending_target {870 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?;871872 <PalletNft<T>>::set_scoped_token_property(873 collection.id,874 nft_id,875 RMRK_SCOPE,876 Self::encode_rmrk_property(PendingNftAccept, &None::<PendingTarget>)?,877 )?;878 }879880 Self::deposit_event(Event::NFTAccepted {881 sender,882 recipient: new_owner,883 collection_id: rmrk_collection_id,884 nft_id: rmrk_nft_id,885 });886887 Ok(())888 }889890 891 892 893 894 895 896 897 898 899 900 901 902 #[pallet::call_index(8)]903 #[pallet::weight(<SelfWeightOf<T>>::reject_nft())]904 pub fn reject_nft(905 origin: OriginFor<T>,906 rmrk_collection_id: RmrkCollectionId,907 rmrk_nft_id: RmrkNftId,908 ) -> DispatchResult {909 let sender = ensure_signed(origin)?;910 let cross_sender = T::CrossAccountId::from_sub(sender.clone());911912 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;913 let nft_id = rmrk_nft_id.into();914915 let collection =916 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;917 collection.check_is_external()?;918919 ensure!(920 <TokenData<T>>::get((collection_id, nft_id)).is_some(),921 <Error<T>>::NoAvailableNftId922 );923924 let pending_target = Self::get_nft_property_decoded::<Option<PendingTarget>>(925 collection_id,926 nft_id,927 RmrkProperty::PendingNftAccept,928 )?;929930 match pending_target {931 Some(pending_target) => {932 Self::remove_pending_child(pending_target, (rmrk_collection_id, rmrk_nft_id))?933 }934 None => return Err(<Error<T>>::CannotRejectNonPendingNft.into()),935 }936937 Self::destroy_nft(938 cross_sender,939 collection_id,940 nft_id,941 NESTING_BUDGET,942 <Error<T>>::CannotRejectNonOwnedNft,943 )944 .map_err(|err| Self::map_unique_err_to_proxy(err.error))?;945946 Self::deposit_event(Event::NFTRejected {947 sender,948 collection_id: rmrk_collection_id,949 nft_id: rmrk_nft_id,950 });951952 Ok(())953 }954955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 #[pallet::call_index(9)]971 #[pallet::weight(<SelfWeightOf<T>>::accept_resource())]972 pub fn accept_resource(973 origin: OriginFor<T>,974 rmrk_collection_id: RmrkCollectionId,975 rmrk_nft_id: RmrkNftId,976 resource_id: RmrkResourceId,977 ) -> DispatchResult {978 let sender = ensure_signed(origin)?;979 let cross_sender = T::CrossAccountId::from_sub(sender);980981 let collection_id = Self::unique_collection_id(rmrk_collection_id)982 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;983 let collection =984 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;985 collection.check_is_external()?;986987 let nft_id = rmrk_nft_id.into();988989 let budget = budget::Value::new(NESTING_BUDGET);990991 let nft_owner =992 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)993 .map_err(|_| <Error<T>>::ResourceDoesntExist)?994 .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;995996 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {997 ensure!(res.pending, <Error<T>>::ResourceNotPending);998 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);9991000 res.pending = false;10011002 Ok(())1003 })?;10041005 Self::deposit_event(Event::<T>::ResourceAccepted {1006 nft_id: rmrk_nft_id,1007 resource_id,1008 });10091010 Ok(())1011 }10121013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 #[pallet::call_index(10)]1027 #[pallet::weight(<SelfWeightOf<T>>::accept_resource_removal())]1028 pub fn accept_resource_removal(1029 origin: OriginFor<T>,1030 rmrk_collection_id: RmrkCollectionId,1031 rmrk_nft_id: RmrkNftId,1032 resource_id: RmrkResourceId,1033 ) -> DispatchResult {1034 let sender = ensure_signed(origin)?;1035 let cross_sender = T::CrossAccountId::from_sub(sender);10361037 let collection_id = Self::unique_collection_id(rmrk_collection_id)1038 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;1039 let collection =1040 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1041 collection.check_is_external()?;10421043 let nft_id = rmrk_nft_id.into();10441045 let budget = budget::Value::new(NESTING_BUDGET);10461047 let nft_owner =1048 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1049 .map_err(|_| <Error<T>>::ResourceDoesntExist)?1050 .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;10511052 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);10531054 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;10551056 let resource_info = <PalletNft<T>>::token_aux_property((1057 collection_id,1058 nft_id,1059 RMRK_SCOPE,1060 resource_id_key.clone(),1061 ))1062 .ok_or(<Error<T>>::ResourceDoesntExist)?;10631064 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource_info)?;10651066 ensure!(1067 resource_info.pending_removal,1068 <Error<T>>::ResourceNotPending1069 );10701071 <PalletNft<T>>::remove_token_aux_property(1072 collection_id,1073 nft_id,1074 RMRK_SCOPE,1075 resource_id_key,1076 );10771078 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1079 let base_id = resource.base;10801081 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1082 }10831084 Self::deposit_event(Event::<T>::ResourceRemovalAccepted {1085 nft_id: rmrk_nft_id,1086 resource_id,1087 });10881089 Ok(())1090 }10911092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 #[pallet::call_index(11)]1110 #[pallet::weight(<SelfWeightOf<T>>::set_property())]1111 pub fn set_property(1112 origin: OriginFor<T>,1113 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,1114 maybe_nft_id: Option<RmrkNftId>,1115 key: RmrkKeyString,1116 value: RmrkValueString,1117 ) -> DispatchResult {1118 let sender = ensure_signed(origin)?;1119 let sender = T::CrossAccountId::from_sub(sender);11201121 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1122 let collection =1123 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1124 collection.check_is_external()?;11251126 let budget = budget::Value::new(NESTING_BUDGET);11271128 match maybe_nft_id {1129 Some(nft_id) => {1130 let token_id: TokenId = nft_id.into();11311132 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;1133 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;11341135 <PalletNft<T>>::set_scoped_token_property(1136 collection_id,1137 token_id,1138 RMRK_SCOPE,1139 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1140 )?;1141 }1142 None => {1143 let collection = Self::get_typed_nft_collection(1144 collection_id,1145 misc::CollectionType::Regular,1146 )?;11471148 Self::check_collection_owner(&collection, &sender)?;11491150 <PalletCommon<T>>::set_scoped_collection_property(1151 collection_id,1152 RMRK_SCOPE,1153 Self::encode_rmrk_property(UserProperty(key.as_slice()), &value)?,1154 )?;1155 }1156 }11571158 Self::deposit_event(Event::PropertySet {1159 collection_id: rmrk_collection_id,1160 maybe_nft_id,1161 key,1162 value,1163 });11641165 Ok(())1166 }11671168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 #[pallet::call_index(12)]1184 #[pallet::weight(<SelfWeightOf<T>>::set_priority())]1185 pub fn set_priority(1186 origin: OriginFor<T>,1187 rmrk_collection_id: RmrkCollectionId,1188 rmrk_nft_id: RmrkNftId,1189 priorities: BoundedVec<RmrkResourceId, RmrkMaxPriorities>,1190 ) -> DispatchResult {1191 let sender = ensure_signed(origin)?;1192 let sender = T::CrossAccountId::from_sub(sender);11931194 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1195 let nft_id = rmrk_nft_id.into();11961197 let collection =1198 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1199 collection.check_is_external()?;12001201 let budget = budget::Value::new(NESTING_BUDGET);12021203 Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;1204 Self::ensure_nft_owner(collection_id, nft_id, &sender, &budget)?;12051206 <PalletNft<T>>::set_scoped_token_property(1207 collection_id,1208 nft_id,1209 RMRK_SCOPE,1210 Self::encode_rmrk_property(ResourcePriorities, &priorities.into_inner())?,1211 )?;12121213 Self::deposit_event(Event::<T>::PrioritySet {1214 collection_id: rmrk_collection_id,1215 nft_id: rmrk_nft_id,1216 });12171218 Ok(())1219 }12201221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 #[pallet::call_index(13)]1236 #[pallet::weight(<SelfWeightOf<T>>::add_basic_resource())]1237 pub fn add_basic_resource(1238 origin: OriginFor<T>,1239 rmrk_collection_id: RmrkCollectionId,1240 nft_id: RmrkNftId,1241 resource: RmrkBasicResource,1242 ) -> DispatchResult {1243 let sender = ensure_signed(origin.clone())?;12441245 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1246 let collection =1247 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1248 collection.check_is_external()?;12491250 let resource_id = Self::resource_add(1251 sender,1252 collection_id,1253 nft_id.into(),1254 RmrkResourceTypes::Basic(resource),1255 )?;12561257 Self::deposit_event(Event::ResourceAdded {1258 nft_id,1259 resource_id,1260 });1261 Ok(())1262 }12631264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 #[pallet::call_index(14)]1279 #[pallet::weight(<SelfWeightOf<T>>::add_composable_resource())]1280 pub fn add_composable_resource(1281 origin: OriginFor<T>,1282 rmrk_collection_id: RmrkCollectionId,1283 nft_id: RmrkNftId,1284 resource: RmrkComposableResource,1285 ) -> DispatchResult {1286 let sender = ensure_signed(origin.clone())?;12871288 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1289 let collection =1290 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1291 collection.check_is_external()?;12921293 let base_id = resource.base;12941295 let resource_id = Self::resource_add(1296 sender,1297 collection_id,1298 nft_id.into(),1299 RmrkResourceTypes::Composable(resource),1300 )?;13011302 <PalletNft<T>>::try_mutate_token_aux_property(1303 collection_id,1304 nft_id.into(),1305 RMRK_SCOPE,1306 Self::get_scoped_property_key(AssociatedBases)?,1307 |value| -> DispatchResult {1308 let mut bases: BasesMap = match value {1309 Some(value) => Self::decode_property_value(value)?,1310 None => BasesMap::new(),1311 };13121313 *bases.entry(base_id).or_insert(0) += 1;13141315 *value = Some(Self::encode_property_value(&bases)?);1316 Ok(())1317 },1318 )?;13191320 Self::deposit_event(Event::ResourceAdded {1321 nft_id,1322 resource_id,1323 });1324 Ok(())1325 }13261327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 #[pallet::call_index(15)]1342 #[pallet::weight(<SelfWeightOf<T>>::add_slot_resource())]1343 pub fn add_slot_resource(1344 origin: OriginFor<T>,1345 rmrk_collection_id: RmrkCollectionId,1346 nft_id: RmrkNftId,1347 resource: RmrkSlotResource,1348 ) -> DispatchResult {1349 let sender = ensure_signed(origin.clone())?;13501351 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1352 let collection =1353 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1354 collection.check_is_external()?;13551356 let resource_id = Self::resource_add(1357 sender,1358 collection_id,1359 nft_id.into(),1360 RmrkResourceTypes::Slot(resource),1361 )?;13621363 Self::deposit_event(Event::ResourceAdded {1364 nft_id,1365 resource_id,1366 });1367 Ok(())1368 }13691370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 #[pallet::call_index(16)]1384 #[pallet::weight(<SelfWeightOf<T>>::remove_resource())]1385 pub fn remove_resource(1386 origin: OriginFor<T>,1387 rmrk_collection_id: RmrkCollectionId,1388 nft_id: RmrkNftId,1389 resource_id: RmrkResourceId,1390 ) -> DispatchResult {1391 let sender = ensure_signed(origin.clone())?;13921393 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;1394 let collection =1395 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1396 collection.check_is_external()?;13971398 Self::resource_remove(sender, collection_id, nft_id.into(), resource_id)?;13991400 Self::deposit_event(Event::ResourceRemoval {1401 nft_id,1402 resource_id,1403 });1404 Ok(())1405 }1406 }1407}14081409impl<T: Config> Pallet<T> {1410 1411 pub fn get_scoped_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {1412 let key = rmrk_key.to_key::<T>()?;14131414 let scoped_key = RMRK_SCOPE1415 .apply(key)1416 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;14171418 Ok(scoped_key)1419 }14201421 1422 1423 pub fn encode_rmrk_property<E: Encode>(1424 rmrk_key: RmrkProperty,1425 value: &E,1426 ) -> Result<Property, DispatchError> {1427 let key = rmrk_key.to_key::<T>()?;14281429 let value = Self::encode_property_value(value)?;14301431 let property = Property { key, value };14321433 Ok(property)1434 }14351436 1437 pub fn encode_property_value<E: Encode, S: Get<u32>>(1438 value: &E,1439 ) -> Result<BoundedBytes<S>, DispatchError> {1440 let value = value1441 .encode()1442 .try_into()1443 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;14441445 Ok(value)1446 }14471448 1449 pub fn decode_property_value<D: Decode, S: Get<u32>>(1450 vec: &BoundedBytes<S>,1451 ) -> Result<D, DispatchError> {1452 vec.decode()1453 .map_err(|_| <Error<T>>::UnableToDecodeRmrkData.into())1454 }14551456 1457 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>1458 where1459 BoundedVec<u8, S>: TryFrom<Vec<u8>>,1460 {1461 vec.rebind()1462 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())1463 }14641465 1466 1467 1468 fn init_collection(1469 sender: T::CrossAccountId,1470 data: CreateCollectionData<T::AccountId>,1471 properties: impl Iterator<Item = Property>,1472 ) -> Result<CollectionId, DispatchError> {1473 let collection_id = <PalletNft<T>>::init_collection(1474 sender.clone(),1475 sender,1476 data,1477 up_data_structs::CollectionFlags {1478 external: true,1479 ..Default::default()1480 },1481 );14821483 if let Err(DispatchError::Arithmetic(_)) = &collection_id {1484 return Err(<Error<T>>::NoAvailableCollectionId.into());1485 }14861487 <PalletCommon<T>>::set_scoped_collection_properties(1488 collection_id?,1489 RMRK_SCOPE,1490 properties,1491 )?;14921493 collection_id1494 }14951496 1497 1498 1499 pub fn create_nft(1500 sender: &T::CrossAccountId,1501 owner: &T::CrossAccountId,1502 collection: &NonfungibleHandle<T>,1503 properties: impl Iterator<Item = Property>,1504 ) -> Result<TokenId, DispatchError> {1505 let data = CreateNftExData {1506 properties: BoundedVec::default(),1507 owner: owner.clone(),1508 };15091510 let budget = budget::Value::new(NESTING_BUDGET);15111512 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;15131514 let nft_id = <PalletNft<T>>::current_token_id(collection.id);15151516 <PalletNft<T>>::set_scoped_token_properties(collection.id, nft_id, RMRK_SCOPE, properties)?;15171518 Ok(nft_id)1519 }15201521 1522 1523 1524 fn destroy_nft(1525 sender: T::CrossAccountId,1526 collection_id: CollectionId,1527 token_id: TokenId,1528 max_burns: u32,1529 error_if_not_owned: Error<T>,1530 ) -> DispatchResultWithPostInfo {1531 let collection =1532 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;15331534 let token_data =1535 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;15361537 let from = token_data.owner;15381539 let owner_check_budget = budget::Value::new(NESTING_BUDGET);15401541 ensure!(1542 <PalletStructure<T>>::check_indirectly_owned(1543 sender.clone(),1544 collection_id,1545 token_id,1546 None,1547 &owner_check_budget1548 )?,1549 error_if_not_owned,1550 );15511552 let burns_budget = budget::Value::new(max_burns);1553 let breadth_budget = budget::Value::new(max_burns);15541555 <PalletNft<T>>::burn_recursively(1556 &collection,1557 &from,1558 token_id,1559 &burns_budget,1560 &breadth_budget,1561 )1562 }15631564 1565 fn insert_pending_child(1566 target: (CollectionId, TokenId),1567 child: (RmrkCollectionId, RmrkNftId),1568 ) -> DispatchResult {1569 Self::mutate_pending_children(target, |pending_children| {1570 pending_children.insert(child);1571 })1572 }15731574 1575 fn remove_pending_child(1576 target: (CollectionId, TokenId),1577 child: (RmrkCollectionId, RmrkNftId),1578 ) -> DispatchResult {1579 Self::mutate_pending_children(target, |pending_children| {1580 pending_children.remove(&child);1581 })1582 }15831584 1585 1586 fn mutate_pending_children(1587 (target_collection_id, target_nft_id): (CollectionId, TokenId),1588 f: impl FnOnce(&mut PendingChildrenSet),1589 ) -> DispatchResult {1590 <PalletNft<T>>::try_mutate_token_aux_property(1591 target_collection_id,1592 target_nft_id,1593 RMRK_SCOPE,1594 Self::get_scoped_property_key(PendingChildren)?,1595 |pending_children| -> DispatchResult {1596 let mut map = match pending_children {1597 Some(map) => Self::decode_property_value(map)?,1598 None => PendingChildrenSet::new(),1599 };16001601 f(&mut map);16021603 *pending_children = Some(Self::encode_property_value(&map)?);16041605 Ok(())1606 },1607 )1608 }16091610 1611 1612 fn iterate_pending_children(1613 collection_id: CollectionId,1614 nft_id: TokenId,1615 ) -> Result<impl Iterator<Item = PendingChild>, DispatchError> {1616 let property = <PalletNft<T>>::token_aux_property((1617 collection_id,1618 nft_id,1619 RMRK_SCOPE,1620 Self::get_scoped_property_key(PendingChildren)?,1621 ));16221623 let pending_children = match property {1624 Some(map) => Self::decode_property_value(&map)?,1625 None => PendingChildrenSet::new(),1626 };16271628 Ok(pending_children.into_iter())1629 }16301631 1632 1633 1634 1635 fn acquire_next_resource_id(1636 collection_id: CollectionId,1637 nft_id: TokenId,1638 ) -> Result<RmrkResourceId, DispatchError> {1639 let resource_id: RmrkResourceId =1640 Self::get_nft_property_decoded(collection_id, nft_id, NextResourceId)?;16411642 let next_id = resource_id1643 .checked_add(1)1644 .ok_or(<Error<T>>::NoAvailableResourceId)?;16451646 <PalletNft<T>>::set_scoped_token_property(1647 collection_id,1648 nft_id,1649 RMRK_SCOPE,1650 Self::encode_rmrk_property(NextResourceId, &next_id)?,1651 )?;16521653 Ok(resource_id)1654 }16551656 1657 1658 fn resource_add(1659 sender: T::AccountId,1660 collection_id: CollectionId,1661 nft_id: TokenId,1662 resource: RmrkResourceTypes,1663 ) -> Result<RmrkResourceId, DispatchError> {1664 let collection =1665 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1666 ensure!(collection.owner == sender, Error::<T>::NoPermission);16671668 let sender = T::CrossAccountId::from_sub(sender);1669 let budget = budget::Value::new(NESTING_BUDGET);16701671 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)1672 .map_err(Self::map_unique_err_to_proxy)?1673 .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;16741675 let pending = sender != nft_owner;16761677 let id = Self::acquire_next_resource_id(collection_id, nft_id)?;16781679 let resource_info = RmrkResourceInfo {1680 id,1681 resource,1682 pending,1683 pending_removal: false,1684 };16851686 <PalletNft<T>>::try_mutate_token_aux_property(1687 collection_id,1688 nft_id,1689 RMRK_SCOPE,1690 Self::get_scoped_property_key(ResourceId(id))?,1691 |value| -> DispatchResult {1692 *value = Some(Self::encode_property_value(&resource_info)?);16931694 Ok(())1695 },1696 )?;16971698 Ok(id)1699 }17001701 1702 1703 fn resource_remove(1704 sender: T::AccountId,1705 collection_id: CollectionId,1706 nft_id: TokenId,1707 resource_id: RmrkResourceId,1708 ) -> DispatchResult {1709 let collection =1710 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;1711 ensure!(collection.owner == sender, Error::<T>::NoPermission);17121713 let resource_id_key = Self::get_scoped_property_key(ResourceId(resource_id))?;17141715 let resource = <PalletNft<T>>::token_aux_property((1716 collection_id,1717 nft_id,1718 RMRK_SCOPE,1719 resource_id_key.clone(),1720 ))1721 .ok_or(<Error<T>>::ResourceDoesntExist)?;17221723 let resource_info: RmrkResourceInfo = Self::decode_property_value(&resource)?;17241725 let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);1726 let topmost_owner =1727 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?1728 .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;17291730 let sender = T::CrossAccountId::from_sub(sender);1731 if topmost_owner == sender {1732 <PalletNft<T>>::remove_token_aux_property(1733 collection_id,1734 nft_id,1735 RMRK_SCOPE,1736 Self::get_scoped_property_key(ResourceId(resource_id))?,1737 );17381739 if let RmrkResourceTypes::Composable(resource) = resource_info.resource {1740 let base_id = resource.base;17411742 Self::remove_associated_base_id(collection_id, nft_id, base_id)?;1743 }1744 } else {1745 Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {1746 res.pending_removal = true;17471748 Ok(())1749 })?;1750 }17511752 Ok(())1753 }17541755 1756 1757 fn remove_associated_base_id(1758 collection_id: CollectionId,1759 nft_id: TokenId,1760 base_id: RmrkBaseId,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(AssociatedBases)?,1767 |value| -> DispatchResult {1768 let mut bases: BasesMap = match value {1769 Some(value) => Self::decode_property_value(value)?,1770 None => BasesMap::new(),1771 };17721773 let remaining = bases.get(&base_id);17741775 if let Some(remaining) = remaining {1776 if let Some(0) | None = remaining.checked_sub(1) {1777 bases.remove(&base_id);1778 }1779 }17801781 *value = Some(Self::encode_property_value(&bases)?);1782 Ok(())1783 },1784 )1785 }17861787 1788 fn try_mutate_resource_info(1789 collection_id: CollectionId,1790 nft_id: TokenId,1791 resource_id: RmrkResourceId,1792 f: impl FnOnce(&mut RmrkResourceInfo) -> DispatchResult,1793 ) -> DispatchResult {1794 <PalletNft<T>>::try_mutate_token_aux_property(1795 collection_id,1796 nft_id,1797 RMRK_SCOPE,1798 Self::get_scoped_property_key(ResourceId(resource_id))?,1799 |value| match value {1800 Some(value) => {1801 let mut resource_info: RmrkResourceInfo = Self::decode_property_value(value)?;18021803 f(&mut resource_info)?;18041805 *value = Self::encode_property_value(&resource_info)?;18061807 Ok(())1808 }1809 None => Err(<Error<T>>::ResourceDoesntExist.into()),1810 },1811 )1812 }18131814 1815 fn change_collection_owner(1816 collection_id: CollectionId,1817 collection_type: misc::CollectionType,1818 sender: T::AccountId,1819 new_owner: T::AccountId,1820 ) -> DispatchResult {1821 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1822 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;18231824 let mut collection = collection.into_inner();18251826 collection.owner = new_owner;1827 collection.save()1828 }18291830 1831 pub fn check_collection_owner(1832 collection: &NonfungibleHandle<T>,1833 account: &T::CrossAccountId,1834 ) -> DispatchResult {1835 collection1836 .check_is_owner(account)1837 .map_err(Self::map_unique_err_to_proxy)1838 }18391840 1841 pub fn last_collection_idx() -> RmrkCollectionId {1842 <CollectionIndex<T>>::get()1843 }18441845 1846 pub fn unique_collection_id(1847 rmrk_collection_id: RmrkCollectionId,1848 ) -> Result<CollectionId, DispatchError> {1849 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1850 .map_err(|_| <Error<T>>::CollectionUnknown.into())1851 }18521853 1854 pub fn rmrk_collection_id(1855 unique_collection_id: CollectionId,1856 ) -> Result<RmrkCollectionId, DispatchError> {1857 Self::get_collection_property_decoded(unique_collection_id, RmrkInternalCollectionId)1858 }18591860 1861 pub fn get_nft_collection(1862 collection_id: CollectionId,1863 ) -> Result<NonfungibleHandle<T>, DispatchError> {1864 let collection = <CollectionHandle<T>>::try_get(collection_id)1865 .map_err(|_| <Error<T>>::CollectionUnknown)?;18661867 match collection.mode {1868 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1869 _ => Err(<Error<T>>::CollectionUnknown.into()),1870 }1871 }18721873 1874 pub fn collection_exists(collection_id: CollectionId) -> bool {1875 <CollectionHandle<T>>::try_get(collection_id).is_ok()1876 }18771878 1879 pub fn get_collection_property(1880 collection_id: CollectionId,1881 key: RmrkProperty,1882 ) -> Result<PropertyValue, DispatchError> {1883 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1884 .get(&Self::get_scoped_property_key(key)?)1885 .ok_or(<Error<T>>::CollectionUnknown)?1886 .clone();18871888 Ok(collection_property)1889 }18901891 1892 pub fn get_collection_property_decoded<V: Decode>(1893 collection_id: CollectionId,1894 key: RmrkProperty,1895 ) -> Result<V, DispatchError> {1896 Self::decode_property_value(&Self::get_collection_property(collection_id, key)?)1897 }18981899 1900 1901 1902 pub fn get_collection_type(1903 collection_id: CollectionId,1904 ) -> Result<misc::CollectionType, DispatchError> {1905 Self::get_collection_property_decoded(collection_id, CollectionType).map_err(|err| {1906 if err != <Error<T>>::CollectionUnknown.into() {1907 <Error<T>>::CorruptedCollectionType.into()1908 } else {1909 err1910 }1911 })1912 }19131914 1915 1916 pub fn ensure_collection_type(1917 collection_id: CollectionId,1918 collection_type: misc::CollectionType,1919 ) -> DispatchResult {1920 let actual_type = Self::get_collection_type(collection_id)?;1921 ensure!(1922 actual_type == collection_type,1923 <CommonError<T>>::NoPermission1924 );19251926 Ok(())1927 }19281929 1930 pub fn get_typed_nft_collection(1931 collection_id: CollectionId,1932 collection_type: misc::CollectionType,1933 ) -> Result<NonfungibleHandle<T>, DispatchError> {1934 Self::ensure_collection_type(collection_id, collection_type)?;19351936 Self::get_nft_collection(collection_id)1937 }19381939 1940 1941 pub fn get_typed_nft_collection_mapped(1942 rmrk_collection_id: RmrkCollectionId,1943 collection_type: misc::CollectionType,1944 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1945 let unique_collection_id = match collection_type {1946 misc::CollectionType::Regular => Self::unique_collection_id(rmrk_collection_id)?,1947 _ => rmrk_collection_id.into(),1948 };19491950 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;19511952 Ok((collection, unique_collection_id))1953 }19541955 1956 pub fn get_nft_property(1957 collection_id: CollectionId,1958 nft_id: TokenId,1959 key: RmrkProperty,1960 ) -> Result<PropertyValue, DispatchError> {1961 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1962 .get(&Self::get_scoped_property_key(key)?)1963 .ok_or(<Error<T>>::RmrkPropertyIsNotFound)?1964 .clone();19651966 Ok(nft_property)1967 }19681969 1970 pub fn get_nft_property_decoded<V: Decode>(1971 collection_id: CollectionId,1972 nft_id: TokenId,1973 key: RmrkProperty,1974 ) -> Result<V, DispatchError> {1975 Self::decode_property_value(&Self::get_nft_property(collection_id, nft_id, key)?)1976 }19771978 1979 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1980 <TokenData<T>>::contains_key((collection_id, nft_id))1981 }19821983 1984 1985 1986 pub fn get_nft_type(1987 collection_id: CollectionId,1988 token_id: TokenId,1989 ) -> Result<NftType, DispatchError> {1990 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1991 .map_err(|_| <Error<T>>::NoAvailableNftId.into())1992 }19931994 1995 pub fn ensure_nft_type(1996 collection_id: CollectionId,1997 token_id: TokenId,1998 nft_type: NftType,1999 ) -> DispatchResult {2000 let actual_type = Self::get_nft_type(collection_id, token_id)?;2001 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);20022003 Ok(())2004 }20052006 2007 2008 pub fn ensure_nft_owner(2009 collection_id: CollectionId,2010 token_id: TokenId,2011 possible_owner: &T::CrossAccountId,2012 nesting_budget: &dyn budget::Budget,2013 ) -> DispatchResult {2014 let is_owned = <PalletStructure<T>>::check_indirectly_owned(2015 possible_owner.clone(),2016 collection_id,2017 token_id,2018 None,2019 nesting_budget,2020 )2021 .map_err(Self::map_unique_err_to_proxy)?;20222023 ensure!(is_owned, <Error<T>>::NoPermission);20242025 Ok(())2026 }20272028 2029 2030 pub fn filter_user_properties<Key, Value, R, Mapper>(2031 collection_id: CollectionId,2032 token_id: Option<TokenId>,2033 filter_keys: Option<Vec<RmrkPropertyKey>>,2034 mapper: Mapper,2035 ) -> Result<Vec<R>, DispatchError>2036 where2037 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2038 Value: Decode + Default,2039 Mapper: Fn(Key, Value) -> R,2040 {2041 filter_keys2042 .map(|keys| {2043 let properties = keys2044 .into_iter()2045 .filter_map(|key| {2046 let key: Key = key.try_into().ok()?;20472048 let value = match token_id {2049 Some(token_id) => Self::get_nft_property_decoded(2050 collection_id,2051 token_id,2052 UserProperty(key.as_ref()),2053 ),2054 None => Self::get_collection_property_decoded(2055 collection_id,2056 UserProperty(key.as_ref()),2057 ),2058 }2059 .ok()?;20602061 Some(mapper(key, value))2062 })2063 .collect();20642065 Ok(properties)2066 })2067 .unwrap_or_else(|| {2068 let properties =2069 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();20702071 Ok(properties)2072 })2073 }20742075 2076 2077 pub fn iterate_user_properties<Key, Value, R, Mapper>(2078 collection_id: CollectionId,2079 token_id: Option<TokenId>,2080 mapper: Mapper,2081 ) -> Result<impl Iterator<Item = R>, DispatchError>2082 where2083 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,2084 Value: Decode + Default,2085 Mapper: Fn(Key, Value) -> R,2086 {2087 let properties = match token_id {2088 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),2089 None => <PalletCommon<T>>::collection_properties(collection_id),2090 };20912092 let properties = properties.into_iter().filter_map(move |(key, value)| {2093 let key = strip_key_prefix(&key, USER_PROPERTY_PREFIX)?;20942095 let key: Key = key.to_vec().try_into().ok()?;2096 let value: Value = value.decode().ok()?;20972098 Some(mapper(key, value))2099 });21002101 Ok(properties)2102 }21032104 2105 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {2106 map_unique_err_to_proxy! {2107 match err {2108 CommonError::NoPermission => NoPermission,2109 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,2110 CommonError::PublicMintingNotAllowed => NoPermission,2111 CommonError::TokenNotFound => NoAvailableNftId,2112 CommonError::ApprovedValueTooLow => NoPermission,2113 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,2114 StructureError::TokenNotFound => NoAvailableNftId,2115 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,2116 }2117 }2118 }2119}