1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46 use super::*;47 use pallet_evm::account;4849 #[pallet::config]50 pub trait Config:51 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52 {53 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54 }5556 #[pallet::storage]57 #[pallet::getter(fn collection_index)]58 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960 #[pallet::storage]61 pub type UniqueCollectionId<T: Config> =62 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364 #[pallet::storage]65 pub type RmrkInernalCollectionId<T: Config> =66 StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768 #[pallet::pallet]69 #[pallet::generate_store(pub(super) trait Store)]70 pub struct Pallet<T>(_);7172 #[pallet::event]73 #[pallet::generate_deposit(pub(super) fn deposit_event)]74 pub enum Event<T: Config> {75 CollectionCreated {76 issuer: T::AccountId,77 collection_id: RmrkCollectionId,78 },79 CollectionDestroyed {80 issuer: T::AccountId,81 collection_id: RmrkCollectionId,82 },83 IssuerChanged {84 old_issuer: T::AccountId,85 new_issuer: T::AccountId,86 collection_id: RmrkCollectionId,87 },88 CollectionLocked {89 issuer: T::AccountId,90 collection_id: RmrkCollectionId,91 },92 NftMinted {93 owner: T::AccountId,94 collection_id: RmrkCollectionId,95 nft_id: RmrkNftId,96 },97 NFTBurned {98 owner: T::AccountId,99 nft_id: RmrkNftId,100 },101 PropertySet {102 collection_id: RmrkCollectionId,103 maybe_nft_id: Option<RmrkNftId>,104 key: RmrkKeyString,105 value: RmrkValueString,106 },107 ResourceAdded {108 nft_id: RmrkNftId,109 resource_id: RmrkResourceId,110 },111 ResourceRemoval {112 nft_id: RmrkNftId,113 resource_id: RmrkResourceId,114 },115 }116117 #[pallet::error]118 pub enum Error<T> {119 120 CorruptedCollectionType,121 NftTypeEncodeError,122 RmrkPropertyKeyIsTooLong,123 RmrkPropertyValueIsTooLong,124125 126 CollectionNotEmpty,127 NoAvailableCollectionId,128 NoAvailableNftId,129 CollectionUnknown,130 NoPermission,131 NonTransferable,132 CollectionFullOrLocked,133 ResourceDoesntExist,134 CannotSendToDescendentOrSelf,135 }136137 #[pallet::call]138 impl<T: Config> Pallet<T> {139 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]140 #[transactional]141 pub fn create_collection(142 origin: OriginFor<T>,143 metadata: RmrkString,144 max: Option<u32>,145 symbol: RmrkCollectionSymbol,146 ) -> DispatchResult {147 let sender = ensure_signed(origin)?;148149 let limits = CollectionLimits {150 owner_can_transfer: Some(false),151 token_limit: max,152 ..Default::default()153 };154155 let data = CreateCollectionData {156 limits: Some(limits),157 token_prefix: symbol158 .into_inner()159 .try_into()160 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,161 permissions: Some(CollectionPermissions {162 nesting: Some(NestingRule::Owner),163 ..Default::default()164 }),165 ..Default::default()166 };167168 let unique_collection_id = Self::init_collection(169 T::CrossAccountId::from_sub(sender.clone()),170 data,171 [172 Self::rmrk_property(Metadata, &metadata)?,173 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,174 ]175 .into_iter(),176 )?;177 let rmrk_collection_id = <CollectionIndex<T>>::get();178179 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);180 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);181182 <CollectionIndex<T>>::mutate(|n| *n += 1);183184 Self::deposit_event(Event::CollectionCreated {185 issuer: sender,186 collection_id: rmrk_collection_id,187 });188189 Ok(())190 }191192 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]193 #[transactional]194 pub fn destroy_collection(195 origin: OriginFor<T>,196 collection_id: RmrkCollectionId,197 ) -> DispatchResult {198 let sender = ensure_signed(origin)?;199 let cross_sender = T::CrossAccountId::from_sub(sender.clone());200201 let collection = Self::get_typed_nft_collection(202 Self::unique_collection_id(collection_id)?,203 misc::CollectionType::Regular,204 )?;205206 ensure!(207 collection.total_supply() == 0,208 <Error<T>>::CollectionNotEmpty209 );210211 <PalletNft<T>>::destroy_collection(collection, &cross_sender)212 .map_err(Self::map_unique_err_to_proxy)?;213214 Self::deposit_event(Event::CollectionDestroyed {215 issuer: sender,216 collection_id,217 });218219 Ok(())220 }221222 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]223 #[transactional]224 pub fn change_collection_issuer(225 origin: OriginFor<T>,226 collection_id: RmrkCollectionId,227 new_issuer: <T::Lookup as StaticLookup>::Source,228 ) -> DispatchResult {229 let sender = ensure_signed(origin)?;230231 let new_issuer = T::Lookup::lookup(new_issuer)?;232233 Self::change_collection_owner(234 Self::unique_collection_id(collection_id)?,235 misc::CollectionType::Regular,236 sender.clone(),237 new_issuer.clone(),238 )?;239240 Self::deposit_event(Event::IssuerChanged {241 old_issuer: sender,242 new_issuer,243 collection_id,244 });245246 Ok(())247 }248249 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]250 #[transactional]251 pub fn lock_collection(252 origin: OriginFor<T>,253 collection_id: RmrkCollectionId,254 ) -> DispatchResult {255 let sender = ensure_signed(origin)?;256 let cross_sender = T::CrossAccountId::from_sub(sender.clone());257258 let collection = Self::get_typed_nft_collection(259 Self::unique_collection_id(collection_id)?,260 misc::CollectionType::Regular,261 )?;262263 Self::check_collection_owner(&collection, &cross_sender)?;264265 let token_count = collection.total_supply();266267 let mut collection = collection.into_inner();268 collection.limits.token_limit = Some(token_count);269 collection.save()?;270271 Self::deposit_event(Event::CollectionLocked {272 issuer: sender,273 collection_id,274 });275276 Ok(())277 }278279 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]280 #[transactional]281 pub fn mint_nft(282 origin: OriginFor<T>,283 owner: T::AccountId,284 collection_id: RmrkCollectionId,285 recipient: Option<T::AccountId>,286 royalty_amount: Option<Permill>,287 metadata: RmrkString,288 transferable: bool,289 ) -> DispatchResult {290 let sender = ensure_signed(origin)?;291 let sender = T::CrossAccountId::from_sub(sender);292 let cross_owner = T::CrossAccountId::from_sub(owner.clone());293294 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {295 recipient: recipient.unwrap_or_else(|| owner.clone()),296 amount,297 });298299 let collection = Self::get_typed_nft_collection(300 Self::unique_collection_id(collection_id)?,301 misc::CollectionType::Regular,302 )?;303304 let nft_id = Self::create_nft(305 &sender,306 &cross_owner,307 &collection,308 [309 Self::rmrk_property(TokenType, &NftType::Regular)?,310 Self::rmrk_property(Transferable, &transferable)?,311 Self::rmrk_property(PendingNftAccept, &false)?,312 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,313 Self::rmrk_property(Metadata, &metadata)?,314 Self::rmrk_property(Equipped, &false)?,315 Self::rmrk_property(316 ResourceCollection,317 &Self::init_collection(318 sender.clone(),319 CreateCollectionData {320 ..Default::default()321 },322 [Self::rmrk_property(323 CollectionType,324 &misc::CollectionType::Resource,325 )?]326 .into_iter(),327 )?,328 )?, 329 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,330 ]331 .into_iter(),332 )333 .map_err(|err| match err {334 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),335 err => Self::map_unique_err_to_proxy(err),336 })?;337338 Self::deposit_event(Event::NftMinted {339 owner,340 collection_id,341 nft_id: nft_id.0,342 });343344 Ok(())345 }346347 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]348 #[transactional]349 pub fn burn_nft(350 origin: OriginFor<T>,351 collection_id: RmrkCollectionId,352 nft_id: RmrkNftId,353 ) -> DispatchResult {354 let sender = ensure_signed(origin)?;355 let cross_sender = T::CrossAccountId::from_sub(sender.clone());356357 Self::destroy_nft(358 cross_sender,359 Self::unique_collection_id(collection_id)?,360 misc::CollectionType::Regular,361 nft_id.into(),362 )?;363364 Self::deposit_event(Event::NFTBurned {365 owner: sender,366 nft_id,367 });368369 Ok(())370 }371372 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]373 #[transactional]374 pub fn send(375 origin: OriginFor<T>,376 rmrk_collection_id: RmrkCollectionId,377 rmrk_nft_id: RmrkNftId,378 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,379 ) -> DispatchResult {380 let sender = ensure_signed(origin.clone())?;381 let cross_sender = T::CrossAccountId::from_sub(sender);382383 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;384 let nft_id = rmrk_nft_id.into();385386 let token_data = <TokenData<T>>::get((collection_id, nft_id))387 .ok_or(<Error<T>>::NoAvailableNftId)?;388389 let from = token_data.owner;390391 let collection = Self::get_typed_nft_collection(392 collection_id,393 misc::CollectionType::Regular,394 )?;395396 if !Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)? {397 return Err(<Error<T>>::NonTransferable.into());398 }399400 if Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)? {401 return Err(<Error<T>>::NoPermission.into());402 }403404 let target_owner;405406 match new_owner {407 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {408 target_owner = T::CrossAccountId::from_sub(account_id);409 },410 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {411 let target_collection_id = Self::unique_collection_id(target_collection_id)?;412413 let target_nft_budget = budget::Value::new(NESTING_BUDGET);414415 let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(416 target_collection_id,417 target_nft_id.into(),418 Some((collection_id, nft_id)),419 &target_nft_budget,420 ).map_err(Self::map_unique_err_to_proxy)?;421422 let is_approval_required = cross_sender != target_nft_owner;423424 if is_approval_required {425 target_owner = target_nft_owner;426427 <PalletNft<T>>::set_scoped_token_property(428 collection.id,429 nft_id,430 PropertyScope::Rmrk,431 Self::rmrk_property(PendingNftAccept, &is_approval_required)?,432 )?;433 } else {434 target_owner = T::CrossTokenAddressMapping::token_to_address(435 target_collection_id,436 target_nft_id.into(),437 );438 }439 }440 }441442 let src_nft_budget = budget::Value::new(NESTING_BUDGET);443444 <PalletNft<T>>::transfer_from(445 &collection,446 &cross_sender,447 &from,448 &target_owner,449 nft_id,450 &src_nft_budget451 ).map_err(Self::map_unique_err_to_proxy)?;452453 Ok(())454 }455456 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]457 #[transactional]458 pub fn set_property(459 origin: OriginFor<T>,460 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,461 maybe_nft_id: Option<RmrkNftId>,462 key: RmrkKeyString,463 value: RmrkValueString,464 ) -> DispatchResult {465 let sender = ensure_signed(origin)?;466 let sender = T::CrossAccountId::from_sub(sender);467468 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;469 let budget = budget::Value::new(NESTING_BUDGET);470471 match maybe_nft_id {472 Some(nft_id) => {473 let token_id: TokenId = nft_id.into();474475 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;476 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;477478 <PalletNft<T>>::set_scoped_token_property(479 collection_id,480 token_id,481 PropertyScope::Rmrk,482 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,483 )?;484 }485 None => {486 let collection = Self::get_typed_nft_collection(487 collection_id,488 misc::CollectionType::Regular,489 )?;490491 Self::check_collection_owner(&collection, &sender)?;492493 <PalletCommon<T>>::set_scoped_collection_property(494 collection_id,495 PropertyScope::Rmrk,496 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,497 )?;498 }499 }500501 Self::deposit_event(Event::PropertySet {502 collection_id: rmrk_collection_id,503 maybe_nft_id,504 key,505 value,506 });507508 Ok(())509 }510511 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]512 #[transactional]513 pub fn add_basic_resource(514 origin: OriginFor<T>,515 collection_id: RmrkCollectionId,516 nft_id: RmrkNftId,517 resource: RmrkBasicResource,518 ) -> DispatchResult {519 let sender = ensure_signed(origin.clone())?;520521 let resource_id = Self::resource_add(522 sender,523 Self::unique_collection_id(collection_id)?,524 nft_id.into(),525 [526 Self::rmrk_property(TokenType, &NftType::Resource)?,527 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,528 Self::rmrk_property(Src, &resource.src)?,529 Self::rmrk_property(Metadata, &resource.metadata)?,530 Self::rmrk_property(License, &resource.license)?,531 Self::rmrk_property(Thumb, &resource.thumb)?,532 ]533 .into_iter(),534 )?;535536 Self::deposit_event(Event::ResourceAdded {537 nft_id,538 resource_id,539 });540 Ok(())541 }542543 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]544 #[transactional]545 pub fn add_composable_resource(546 origin: OriginFor<T>,547 collection_id: RmrkCollectionId,548 nft_id: RmrkNftId,549 _resource_id: RmrkBoundedResource,550 resource: RmrkComposableResource,551 ) -> DispatchResult {552 let sender = ensure_signed(origin.clone())?;553554 let resource_id = Self::resource_add(555 sender,556 Self::unique_collection_id(collection_id)?,557 nft_id.into(),558 [559 Self::rmrk_property(TokenType, &NftType::Resource)?,560 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,561 Self::rmrk_property(Parts, &resource.parts)?,562 Self::rmrk_property(Base, &resource.base)?,563 Self::rmrk_property(Src, &resource.src)?,564 Self::rmrk_property(Metadata, &resource.metadata)?,565 Self::rmrk_property(License, &resource.license)?,566 Self::rmrk_property(Thumb, &resource.thumb)?,567 ]568 .into_iter(),569 )?;570571 Self::deposit_event(Event::ResourceAdded {572 nft_id,573 resource_id,574 });575 Ok(())576 }577578 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]579 #[transactional]580 pub fn add_slot_resource(581 origin: OriginFor<T>,582 collection_id: RmrkCollectionId,583 nft_id: RmrkNftId,584 resource: RmrkSlotResource,585 ) -> DispatchResult {586 let sender = ensure_signed(origin.clone())?;587588 let resource_id = Self::resource_add(589 sender,590 Self::unique_collection_id(collection_id)?,591 nft_id.into(),592 [593 Self::rmrk_property(TokenType, &NftType::Resource)?,594 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,595 Self::rmrk_property(Base, &resource.base)?,596 Self::rmrk_property(Src, &resource.src)?,597 Self::rmrk_property(Metadata, &resource.metadata)?,598 Self::rmrk_property(Slot, &resource.slot)?,599 Self::rmrk_property(License, &resource.license)?,600 Self::rmrk_property(Thumb, &resource.thumb)?,601 ]602 .into_iter(),603 )?;604605 Self::deposit_event(Event::ResourceAdded {606 nft_id,607 resource_id,608 });609 Ok(())610 }611612 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]613 #[transactional]614 pub fn remove_resource(615 origin: OriginFor<T>,616 collection_id: RmrkCollectionId,617 nft_id: RmrkNftId,618 resource_id: RmrkResourceId,619 ) -> DispatchResult {620 let sender = ensure_signed(origin.clone())?;621622 Self::resource_remove(623 sender,624 Self::unique_collection_id(collection_id)?,625 nft_id.into(),626 resource_id.into(),627 )?;628629 Self::deposit_event(Event::ResourceRemoval {630 nft_id,631 resource_id,632 });633 Ok(())634 }635 }636}637638impl<T: Config> Pallet<T> {639 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {640 let key = rmrk_key.to_key::<T>()?;641642 let scoped_key = PropertyScope::Rmrk643 .apply(key)644 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;645646 Ok(scoped_key)647 }648649 650 pub fn rmrk_property<E: Encode>(651 rmrk_key: RmrkProperty,652 value: &E,653 ) -> Result<Property, DispatchError> {654 let key = rmrk_key.to_key::<T>()?;655656 let value = value657 .encode()658 .try_into()659 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;660661 let property = Property { key, value };662663 Ok(property)664 }665666 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {667 vec.decode()668 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())669 }670671 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>672 where673 BoundedVec<u8, S>: TryFrom<Vec<u8>>,674 {675 vec.rebind()676 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())677 }678679 fn init_collection(680 sender: T::CrossAccountId,681 data: CreateCollectionData<T::AccountId>,682 properties: impl Iterator<Item = Property>,683 ) -> Result<CollectionId, DispatchError> {684 let collection_id = <PalletNft<T>>::init_collection(sender, data);685686 if let Err(DispatchError::Arithmetic(_)) = &collection_id {687 return Err(<Error<T>>::NoAvailableCollectionId.into());688 }689690 <PalletCommon<T>>::set_scoped_collection_properties(691 collection_id?,692 PropertyScope::Rmrk,693 properties,694 )?;695696 collection_id697 }698699 pub fn create_nft(700 sender: &T::CrossAccountId,701 owner: &T::CrossAccountId,702 collection: &NonfungibleHandle<T>,703 properties: impl Iterator<Item = Property>,704 ) -> Result<TokenId, DispatchError> {705 let data = CreateNftExData {706 properties: BoundedVec::default(),707 owner: owner.clone(),708 };709710 let budget = budget::Value::new(NESTING_BUDGET);711712 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;713714 let nft_id = <PalletNft<T>>::current_token_id(collection.id);715716 <PalletNft<T>>::set_scoped_token_properties(717 collection.id,718 nft_id,719 PropertyScope::Rmrk,720 properties,721 )?;722723 Ok(nft_id)724 }725726 fn destroy_nft(727 sender: T::CrossAccountId,728 collection_id: CollectionId,729 collection_type: misc::CollectionType,730 token_id: TokenId,731 ) -> DispatchResult {732 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;733734 <PalletNft<T>>::burn(&collection, &sender, token_id)735 .map_err(Self::map_unique_err_to_proxy)?;736737 Ok(())738 }739740 fn resource_add(741 sender: T::AccountId,742 collection_id: CollectionId,743 token_id: TokenId,744 resource_properties: impl Iterator<Item = Property>,745 ) -> Result<RmrkResourceId, DispatchError> {746 let collection =747 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;748 ensure!(collection.owner == sender, Error::<T>::NoPermission);749750 751 752753 let sender = T::CrossAccountId::from_sub(sender);754 let budget = budget::Value::new(NESTING_BUDGET);755 let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();756757 let resource_collection_id: CollectionId =758 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;759 let resource_collection =760 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;761762 763764 let resource_id = Self::create_nft(765 &sender, 766 &sender,767 &resource_collection,768 resource_properties.chain(769 [770 Self::rmrk_property(PendingResourceAccept, &pending)?,771 Self::rmrk_property(PendingResourceRemoval, &false)?,772 ]773 .into_iter(),774 ),775 )776 .map_err(|err| match err {777 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),778 err => Self::map_unique_err_to_proxy(err),779 })?;780781 Ok(resource_id.0)782 }783784 fn resource_remove(785 sender: T::AccountId,786 collection_id: CollectionId,787 nft_id: TokenId,788 resource_id: TokenId,789 ) -> DispatchResult {790 let collection =791 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;792 ensure!(collection.owner == sender, Error::<T>::NoPermission);793794 let resource_collection_id: CollectionId =795 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;796 let resource_collection =797 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;798 ensure!(799 <PalletNft<T>>::token_exists(&resource_collection, resource_id),800 Error::<T>::ResourceDoesntExist801 );802803 let budget = up_data_structs::budget::Value::new(10);804 let topmost_owner =805 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;806807 let sender = T::CrossAccountId::from_sub(sender);808 if topmost_owner == sender {809 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)810 .map_err(Self::map_unique_err_to_proxy)?;811 } else {812 <PalletNft<T>>::set_scoped_token_property(813 resource_collection_id,814 resource_id,815 PropertyScope::Rmrk,816 Self::rmrk_property(PendingResourceRemoval, &true)?,817 )?;818 }819820 Ok(())821 }822823 fn change_collection_owner(824 collection_id: CollectionId,825 collection_type: misc::CollectionType,826 sender: T::AccountId,827 new_owner: T::AccountId,828 ) -> DispatchResult {829 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;830 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;831832 let mut collection = collection.into_inner();833834 collection.owner = new_owner;835 collection.save()836 }837838 fn check_collection_owner(839 collection: &NonfungibleHandle<T>,840 account: &T::CrossAccountId,841 ) -> DispatchResult {842 collection843 .check_is_owner(account)844 .map_err(Self::map_unique_err_to_proxy)845 }846847 pub fn last_collection_idx() -> RmrkCollectionId {848 <CollectionIndex<T>>::get()849 }850851 pub fn unique_collection_id(852 rmrk_collection_id: RmrkCollectionId,853 ) -> Result<CollectionId, DispatchError> {854 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)855 .map_err(|_| <Error<T>>::CollectionUnknown.into())856 }857858 pub fn rmrk_collection_id(859 unique_collection_id: CollectionId860 ) -> Result<RmrkCollectionId, DispatchError> {861 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)862 .map_err(|_| <Error<T>>::CollectionUnknown.into())863 }864865 pub fn get_nft_collection(866 collection_id: CollectionId,867 ) -> Result<NonfungibleHandle<T>, DispatchError> {868 let collection = <CollectionHandle<T>>::try_get(collection_id)869 .map_err(|_| <Error<T>>::CollectionUnknown)?;870871 match collection.mode {872 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),873 _ => Err(<Error<T>>::CollectionUnknown.into()),874 }875 }876877 pub fn collection_exists(collection_id: CollectionId) -> bool {878 <CollectionHandle<T>>::try_get(collection_id).is_ok()879 }880881 pub fn get_collection_property(882 collection_id: CollectionId,883 key: RmrkProperty,884 ) -> Result<PropertyValue, DispatchError> {885 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)886 .get(&Self::rmrk_property_key(key)?)887 .ok_or(<Error<T>>::CollectionUnknown)?888 .clone();889890 Ok(collection_property)891 }892893 pub fn get_collection_property_decoded<V: Decode>(894 collection_id: CollectionId,895 key: RmrkProperty,896 ) -> Result<V, DispatchError> {897 Self::decode_property(Self::get_collection_property(collection_id, key)?)898 }899900 pub fn get_collection_type(901 collection_id: CollectionId,902 ) -> Result<misc::CollectionType, DispatchError> {903 Self::get_collection_property_decoded(collection_id, CollectionType)904 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())905 }906907 pub fn ensure_collection_type(908 collection_id: CollectionId,909 collection_type: misc::CollectionType,910 ) -> DispatchResult {911 let actual_type = Self::get_collection_type(collection_id)?;912 ensure!(913 actual_type == collection_type,914 <CommonError<T>>::NoPermission915 );916917 Ok(())918 }919920 pub fn get_typed_nft_collection(921 collection_id: CollectionId,922 collection_type: misc::CollectionType,923 ) -> Result<NonfungibleHandle<T>, DispatchError> {924 Self::ensure_collection_type(collection_id, collection_type)?;925926 Self::get_nft_collection(collection_id)927 }928929 pub fn get_typed_nft_collection_mapped(930 rmrk_collection_id: RmrkCollectionId,931 collection_type: misc::CollectionType,932 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {933 let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;934935 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;936937 Ok((collection, unique_collection_id))938 }939940 pub fn get_nft_property(941 collection_id: CollectionId,942 nft_id: TokenId,943 key: RmrkProperty,944 ) -> Result<PropertyValue, DispatchError> {945 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))946 .get(&Self::rmrk_property_key(key)?)947 .ok_or(<Error<T>>::NoAvailableNftId)? 948 .clone();949950 Ok(nft_property)951 }952953 pub fn get_nft_property_decoded<V: Decode>(954 collection_id: CollectionId,955 nft_id: TokenId,956 key: RmrkProperty,957 ) -> Result<V, DispatchError> {958 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)959 }960961 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {962 <TokenData<T>>::contains_key((collection_id, nft_id))963 }964965 pub fn get_nft_type(966 collection_id: CollectionId,967 token_id: TokenId,968 ) -> Result<NftType, DispatchError> {969 Self::get_nft_property_decoded(collection_id, token_id, TokenType)970 .map_err(|_| <Error<T>>::NftTypeEncodeError.into())971 }972973 pub fn ensure_nft_type(974 collection_id: CollectionId,975 token_id: TokenId,976 nft_type: NftType,977 ) -> DispatchResult {978 let actual_type = Self::get_nft_type(collection_id, token_id)?;979 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);980981 Ok(())982 }983984 pub fn ensure_nft_owner(985 collection_id: CollectionId,986 token_id: TokenId,987 possible_owner: &T::CrossAccountId,988 nesting_budget: &dyn budget::Budget989 ) -> DispatchResult {990 let is_owned = <PalletStructure<T>>::check_indirectly_owned(991 possible_owner.clone(),992 collection_id,993 token_id,994 None,995 nesting_budget,996 )?;997998 ensure!(999 is_owned,1000 <Error<T>>::NoPermission1001 );10021003 Ok(())1004 }10051006 pub fn filter_user_properties<Key, Value, R, Mapper>(1007 collection_id: CollectionId,1008 token_id: Option<TokenId>,1009 filter_keys: Option<Vec<RmrkPropertyKey>>,1010 mapper: Mapper,1011 ) -> Result<Vec<R>, DispatchError>1012 where1013 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1014 Value: Decode + Default,1015 Mapper: Fn(Key, Value) -> R,1016 {1017 filter_keys1018 .map(|keys| {1019 let properties = keys1020 .into_iter()1021 .filter_map(|key| {1022 let key: Key = key.try_into().ok()?;10231024 let value = match token_id {1025 Some(token_id) => Self::get_nft_property_decoded(1026 collection_id,1027 token_id,1028 UserProperty(key.as_ref()),1029 ),1030 None => Self::get_collection_property_decoded(1031 collection_id,1032 UserProperty(key.as_ref()),1033 ),1034 }1035 .ok()?;10361037 Some(mapper(key, value))1038 })1039 .collect();10401041 Ok(properties)1042 })1043 .unwrap_or_else(|| {1044 let properties =1045 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();10461047 Ok(properties)1048 })1049 }10501051 pub fn iterate_user_properties<Key, Value, R, Mapper>(1052 collection_id: CollectionId,1053 token_id: Option<TokenId>,1054 mapper: Mapper,1055 ) -> Result<impl Iterator<Item = R>, DispatchError>1056 where1057 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1058 Value: Decode + Default,1059 Mapper: Fn(Key, Value) -> R,1060 {1061 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;10621063 let properties = match token_id {1064 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1065 None => <PalletCommon<T>>::collection_properties(collection_id),1066 };10671068 let properties = properties.into_iter().filter_map(move |(key, value)| {1069 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;10701071 let key: Key = key.to_vec().try_into().ok()?;1072 let value: Value = value.decode().ok()?;10731074 Some(mapper(key, value))1075 });10761077 Ok(properties)1078 }10791080 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1081 map_unique_err_to_proxy! {1082 match err {1083 CommonError::NoPermission => NoPermission,1084 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1085 CommonError::PublicMintingNotAllowed => NoPermission,1086 CommonError::TokenNotFound => NoAvailableNftId,1087 CommonError::ApprovedValueTooLow => NoPermission,1088 StructureError::TokenNotFound => NoAvailableNftId,1089 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf1090 }1091 }1092 }1093}