difftreelog
feat(rmrk) add reject_nft
in: master
1 file changed
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![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 /* Unique-specific events */120 CorruptedCollectionType,121 NftTypeEncodeError,122 RmrkPropertyKeyIsTooLong,123 RmrkPropertyValueIsTooLong,124125 /* RMRK compatible events */126 CollectionNotEmpty,127 NoAvailableCollectionId,128 NoAvailableNftId,129 CollectionUnknown,130 NoPermission,131 NonTransferable,132 CollectionFullOrLocked,133 ResourceDoesntExist,134 CannotSendToDescendentOrSelf,135 CannotAcceptNonOwnedNft,136 }137138 #[pallet::call]139 impl<T: Config> Pallet<T> {140 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]141 #[transactional]142 pub fn create_collection(143 origin: OriginFor<T>,144 metadata: RmrkString,145 max: Option<u32>,146 symbol: RmrkCollectionSymbol,147 ) -> DispatchResult {148 let sender = ensure_signed(origin)?;149150 let limits = CollectionLimits {151 owner_can_transfer: Some(false),152 token_limit: max,153 ..Default::default()154 };155156 let data = CreateCollectionData {157 limits: Some(limits),158 token_prefix: symbol159 .into_inner()160 .try_into()161 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,162 permissions: Some(CollectionPermissions {163 nesting: Some(NestingRule::Owner),164 ..Default::default()165 }),166 ..Default::default()167 };168169 let unique_collection_id = Self::init_collection(170 T::CrossAccountId::from_sub(sender.clone()),171 data,172 [173 Self::rmrk_property(Metadata, &metadata)?,174 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,175 ]176 .into_iter(),177 )?;178 let rmrk_collection_id = <CollectionIndex<T>>::get();179180 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);181 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);182183 <CollectionIndex<T>>::mutate(|n| *n += 1);184185 Self::deposit_event(Event::CollectionCreated {186 issuer: sender,187 collection_id: rmrk_collection_id,188 });189190 Ok(())191 }192193 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]194 #[transactional]195 pub fn destroy_collection(196 origin: OriginFor<T>,197 collection_id: RmrkCollectionId,198 ) -> DispatchResult {199 let sender = ensure_signed(origin)?;200 let cross_sender = T::CrossAccountId::from_sub(sender.clone());201202 let collection = Self::get_typed_nft_collection(203 Self::unique_collection_id(collection_id)?,204 misc::CollectionType::Regular,205 )?;206207 <PalletNft<T>>::destroy_collection(collection, &cross_sender)208 .map_err(Self::map_unique_err_to_proxy)?;209210 Self::deposit_event(Event::CollectionDestroyed {211 issuer: sender,212 collection_id,213 });214215 Ok(())216 }217218 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]219 #[transactional]220 pub fn change_collection_issuer(221 origin: OriginFor<T>,222 collection_id: RmrkCollectionId,223 new_issuer: <T::Lookup as StaticLookup>::Source,224 ) -> DispatchResult {225 let sender = ensure_signed(origin)?;226227 let new_issuer = T::Lookup::lookup(new_issuer)?;228229 Self::change_collection_owner(230 Self::unique_collection_id(collection_id)?,231 misc::CollectionType::Regular,232 sender.clone(),233 new_issuer.clone(),234 )?;235236 Self::deposit_event(Event::IssuerChanged {237 old_issuer: sender,238 new_issuer,239 collection_id,240 });241242 Ok(())243 }244245 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]246 #[transactional]247 pub fn lock_collection(248 origin: OriginFor<T>,249 collection_id: RmrkCollectionId,250 ) -> DispatchResult {251 let sender = ensure_signed(origin)?;252 let cross_sender = T::CrossAccountId::from_sub(sender.clone());253254 let collection = Self::get_typed_nft_collection(255 Self::unique_collection_id(collection_id)?,256 misc::CollectionType::Regular,257 )?;258259 Self::check_collection_owner(&collection, &cross_sender)?;260261 let token_count = collection.total_supply();262263 let mut collection = collection.into_inner();264 collection.limits.token_limit = Some(token_count);265 collection.save()?;266267 Self::deposit_event(Event::CollectionLocked {268 issuer: sender,269 collection_id,270 });271272 Ok(())273 }274275 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]276 #[transactional]277 pub fn mint_nft(278 origin: OriginFor<T>,279 owner: T::AccountId,280 collection_id: RmrkCollectionId,281 recipient: Option<T::AccountId>,282 royalty_amount: Option<Permill>,283 metadata: RmrkString,284 transferable: bool,285 ) -> DispatchResult {286 let sender = ensure_signed(origin)?;287 let sender = T::CrossAccountId::from_sub(sender);288 let cross_owner = T::CrossAccountId::from_sub(owner.clone());289290 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {291 recipient: recipient.unwrap_or_else(|| owner.clone()),292 amount,293 });294295 let collection = Self::get_typed_nft_collection(296 Self::unique_collection_id(collection_id)?,297 misc::CollectionType::Regular,298 )?;299300 let nft_id = Self::create_nft(301 &sender,302 &cross_owner,303 &collection,304 [305 Self::rmrk_property(TokenType, &NftType::Regular)?,306 Self::rmrk_property(Transferable, &transferable)?,307 Self::rmrk_property(PendingNftAccept, &false)?,308 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,309 Self::rmrk_property(Metadata, &metadata)?,310 Self::rmrk_property(Equipped, &false)?,311 Self::rmrk_property(312 ResourceCollection,313 &Self::init_collection(314 sender.clone(),315 CreateCollectionData {316 ..Default::default()317 },318 [Self::rmrk_property(319 CollectionType,320 &misc::CollectionType::Resource,321 )?]322 .into_iter(),323 )?,324 )?, // todo possibly add limits to the collection if rmrk warrants them325 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,326 ]327 .into_iter(),328 )329 .map_err(|err| match err {330 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),331 err => Self::map_unique_err_to_proxy(err),332 })?;333334 Self::deposit_event(Event::NftMinted {335 owner,336 collection_id,337 nft_id: nft_id.0,338 });339340 Ok(())341 }342343 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]344 #[transactional]345 pub fn burn_nft(346 origin: OriginFor<T>,347 collection_id: RmrkCollectionId,348 nft_id: RmrkNftId,349 ) -> DispatchResult {350 let sender = ensure_signed(origin)?;351 let cross_sender = T::CrossAccountId::from_sub(sender.clone());352353 Self::destroy_nft(354 cross_sender,355 Self::unique_collection_id(collection_id)?,356 misc::CollectionType::Regular,357 nft_id.into(),358 )?;359360 Self::deposit_event(Event::NFTBurned {361 owner: sender,362 nft_id,363 });364365 Ok(())366 }367368 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]369 #[transactional]370 pub fn send(371 origin: OriginFor<T>,372 rmrk_collection_id: RmrkCollectionId,373 rmrk_nft_id: RmrkNftId,374 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,375 ) -> DispatchResult {376 let sender = ensure_signed(origin.clone())?;377 let cross_sender = T::CrossAccountId::from_sub(sender);378379 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;380 let nft_id = rmrk_nft_id.into();381382 let token_data = <TokenData<T>>::get((collection_id, nft_id))383 .ok_or(<Error<T>>::NoAvailableNftId)?;384385 let from = token_data.owner;386387 let collection = Self::get_typed_nft_collection(388 collection_id,389 misc::CollectionType::Regular,390 )?;391392 if !Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)? {393 return Err(<Error<T>>::NonTransferable.into());394 }395396 if Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)? {397 return Err(<Error<T>>::NoPermission.into());398 }399400 let target_owner;401402 match new_owner {403 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {404 target_owner = T::CrossAccountId::from_sub(account_id);405 },406 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {407 let target_collection_id = Self::unique_collection_id(target_collection_id)?;408409 let target_nft_budget = budget::Value::new(NESTING_BUDGET);410411 let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(412 target_collection_id,413 target_nft_id.into(),414 Some((collection_id, nft_id)),415 &target_nft_budget,416 ).map_err(Self::map_unique_err_to_proxy)?;417418 let is_approval_required = cross_sender != target_nft_owner;419420 if is_approval_required {421 target_owner = target_nft_owner;422423 <PalletNft<T>>::set_scoped_token_property(424 collection.id,425 nft_id,426 PropertyScope::Rmrk,427 Self::rmrk_property(PendingNftAccept, &is_approval_required)?,428 )?;429 } else {430 target_owner = T::CrossTokenAddressMapping::token_to_address(431 target_collection_id,432 target_nft_id.into(),433 );434 }435 }436 }437438 let src_nft_budget = budget::Value::new(NESTING_BUDGET);439440 <PalletNft<T>>::transfer_from(441 &collection,442 &cross_sender,443 &from,444 &target_owner,445 nft_id,446 &src_nft_budget447 ).map_err(Self::map_unique_err_to_proxy)?;448449 Ok(())450 }451452 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]453 #[transactional]454 pub fn accept_nft(455 origin: OriginFor<T>,456 rmrk_collection_id: RmrkCollectionId,457 rmrk_nft_id: RmrkNftId,458 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,459 ) -> DispatchResult {460 let sender = ensure_signed(origin.clone())?;461 let cross_sender = T::CrossAccountId::from_sub(sender);462463 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;464 let nft_id = rmrk_nft_id.into();465466 let collection = Self::get_typed_nft_collection(467 collection_id,468 misc::CollectionType::Regular,469 )?;470471 let new_owner = match new_owner {472 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {473 T::CrossAccountId::from_sub(account_id)474 },475 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {476 let target_collection_id = Self::unique_collection_id(target_collection_id)?;477478 T::CrossTokenAddressMapping::token_to_address(target_collection_id, TokenId(target_nft_id))479 }480 };481482 let budget = budget::Value::new(NESTING_BUDGET);483484 <PalletNft<T>>::transfer(485 &collection,486 &cross_sender,487 &new_owner,488 nft_id,489 &budget,490 ).map_err(|err| {491 if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {492 <Error<T>>::CannotAcceptNonOwnedNft.into()493 } else {494 Self::map_unique_err_to_proxy(err)495 }496 })?;497498 <PalletNft<T>>::set_scoped_token_property(499 collection.id,500 nft_id,501 PropertyScope::Rmrk,502 Self::rmrk_property(PendingNftAccept, &false)?,503 )?;504505 Ok(())506 }507508 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]509 #[transactional]510 pub fn set_property(511 origin: OriginFor<T>,512 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,513 maybe_nft_id: Option<RmrkNftId>,514 key: RmrkKeyString,515 value: RmrkValueString,516 ) -> DispatchResult {517 let sender = ensure_signed(origin)?;518 let sender = T::CrossAccountId::from_sub(sender);519520 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;521 let budget = budget::Value::new(NESTING_BUDGET);522523 match maybe_nft_id {524 Some(nft_id) => {525 let token_id: TokenId = nft_id.into();526527 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;528 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;529530 <PalletNft<T>>::set_scoped_token_property(531 collection_id,532 token_id,533 PropertyScope::Rmrk,534 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,535 )?;536 }537 None => {538 let collection = Self::get_typed_nft_collection(539 collection_id,540 misc::CollectionType::Regular,541 )?;542543 Self::check_collection_owner(&collection, &sender)?;544545 <PalletCommon<T>>::set_scoped_collection_property(546 collection_id,547 PropertyScope::Rmrk,548 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,549 )?;550 }551 }552553 Self::deposit_event(Event::PropertySet {554 collection_id: rmrk_collection_id,555 maybe_nft_id,556 key,557 value,558 });559560 Ok(())561 }562563 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]564 #[transactional]565 pub fn add_basic_resource(566 origin: OriginFor<T>,567 collection_id: RmrkCollectionId,568 nft_id: RmrkNftId,569 resource: RmrkBasicResource,570 ) -> DispatchResult {571 let sender = ensure_signed(origin.clone())?;572573 let resource_id = Self::resource_add(574 sender,575 Self::unique_collection_id(collection_id)?,576 nft_id.into(),577 [578 Self::rmrk_property(TokenType, &NftType::Resource)?,579 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,580 Self::rmrk_property(Src, &resource.src)?,581 Self::rmrk_property(Metadata, &resource.metadata)?,582 Self::rmrk_property(License, &resource.license)?,583 Self::rmrk_property(Thumb, &resource.thumb)?,584 ]585 .into_iter(),586 )?;587588 Self::deposit_event(Event::ResourceAdded {589 nft_id,590 resource_id,591 });592 Ok(())593 }594595 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]596 #[transactional]597 pub fn add_composable_resource(598 origin: OriginFor<T>,599 collection_id: RmrkCollectionId,600 nft_id: RmrkNftId,601 _resource_id: RmrkBoundedResource,602 resource: RmrkComposableResource,603 ) -> DispatchResult {604 let sender = ensure_signed(origin.clone())?;605606 let resource_id = Self::resource_add(607 sender,608 Self::unique_collection_id(collection_id)?,609 nft_id.into(),610 [611 Self::rmrk_property(TokenType, &NftType::Resource)?,612 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,613 Self::rmrk_property(Parts, &resource.parts)?,614 Self::rmrk_property(Base, &resource.base)?,615 Self::rmrk_property(Src, &resource.src)?,616 Self::rmrk_property(Metadata, &resource.metadata)?,617 Self::rmrk_property(License, &resource.license)?,618 Self::rmrk_property(Thumb, &resource.thumb)?,619 ]620 .into_iter(),621 )?;622623 Self::deposit_event(Event::ResourceAdded {624 nft_id,625 resource_id,626 });627 Ok(())628 }629630 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]631 #[transactional]632 pub fn add_slot_resource(633 origin: OriginFor<T>,634 collection_id: RmrkCollectionId,635 nft_id: RmrkNftId,636 resource: RmrkSlotResource,637 ) -> DispatchResult {638 let sender = ensure_signed(origin.clone())?;639640 let resource_id = Self::resource_add(641 sender,642 Self::unique_collection_id(collection_id)?,643 nft_id.into(),644 [645 Self::rmrk_property(TokenType, &NftType::Resource)?,646 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,647 Self::rmrk_property(Base, &resource.base)?,648 Self::rmrk_property(Src, &resource.src)?,649 Self::rmrk_property(Metadata, &resource.metadata)?,650 Self::rmrk_property(Slot, &resource.slot)?,651 Self::rmrk_property(License, &resource.license)?,652 Self::rmrk_property(Thumb, &resource.thumb)?,653 ]654 .into_iter(),655 )?;656657 Self::deposit_event(Event::ResourceAdded {658 nft_id,659 resource_id,660 });661 Ok(())662 }663664 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]665 #[transactional]666 pub fn remove_resource(667 origin: OriginFor<T>,668 collection_id: RmrkCollectionId,669 nft_id: RmrkNftId,670 resource_id: RmrkResourceId,671 ) -> DispatchResult {672 let sender = ensure_signed(origin.clone())?;673674 Self::resource_remove(675 sender,676 Self::unique_collection_id(collection_id)?,677 nft_id.into(),678 resource_id.into(),679 )?;680681 Self::deposit_event(Event::ResourceRemoval {682 nft_id,683 resource_id,684 });685 Ok(())686 }687 }688}689690impl<T: Config> Pallet<T> {691 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {692 let key = rmrk_key.to_key::<T>()?;693694 let scoped_key = PropertyScope::Rmrk695 .apply(key)696 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;697698 Ok(scoped_key)699 }700701 // todo think about renaming these702 pub fn rmrk_property<E: Encode>(703 rmrk_key: RmrkProperty,704 value: &E,705 ) -> Result<Property, DispatchError> {706 let key = rmrk_key.to_key::<T>()?;707708 let value = value709 .encode()710 .try_into()711 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;712713 let property = Property { key, value };714715 Ok(property)716 }717718 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {719 vec.decode()720 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())721 }722723 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>724 where725 BoundedVec<u8, S>: TryFrom<Vec<u8>>,726 {727 vec.rebind()728 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())729 }730731 fn init_collection(732 sender: T::CrossAccountId,733 data: CreateCollectionData<T::AccountId>,734 properties: impl Iterator<Item = Property>,735 ) -> Result<CollectionId, DispatchError> {736 let collection_id = <PalletNft<T>>::init_collection(sender, data);737738 if let Err(DispatchError::Arithmetic(_)) = &collection_id {739 return Err(<Error<T>>::NoAvailableCollectionId.into());740 }741742 <PalletCommon<T>>::set_scoped_collection_properties(743 collection_id?,744 PropertyScope::Rmrk,745 properties,746 )?;747748 collection_id749 }750751 pub fn create_nft(752 sender: &T::CrossAccountId,753 owner: &T::CrossAccountId,754 collection: &NonfungibleHandle<T>,755 properties: impl Iterator<Item = Property>,756 ) -> Result<TokenId, DispatchError> {757 let data = CreateNftExData {758 properties: BoundedVec::default(),759 owner: owner.clone(),760 };761762 let budget = budget::Value::new(NESTING_BUDGET);763764 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;765766 let nft_id = <PalletNft<T>>::current_token_id(collection.id);767768 <PalletNft<T>>::set_scoped_token_properties(769 collection.id,770 nft_id,771 PropertyScope::Rmrk,772 properties,773 )?;774775 Ok(nft_id)776 }777778 fn destroy_nft(779 sender: T::CrossAccountId,780 collection_id: CollectionId,781 collection_type: misc::CollectionType,782 token_id: TokenId,783 ) -> DispatchResult {784 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;785786 <PalletNft<T>>::burn(&collection, &sender, token_id)787 .map_err(Self::map_unique_err_to_proxy)?;788789 Ok(())790 }791792 fn resource_add(793 sender: T::AccountId,794 collection_id: CollectionId,795 token_id: TokenId,796 resource_properties: impl Iterator<Item = Property>,797 ) -> Result<RmrkResourceId, DispatchError> {798 let collection =799 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;800 ensure!(collection.owner == sender, Error::<T>::NoPermission);801802 // Check NFT lock status // todo depends on market, maybe later803 //ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);804805 let sender = T::CrossAccountId::from_sub(sender);806 let budget = budget::Value::new(NESTING_BUDGET);807 let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();808809 let resource_collection_id: CollectionId =810 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;811 let resource_collection =812 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;813814 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them815816 let resource_id = Self::create_nft(817 &sender, // todo owner of the nft?818 &sender,819 &resource_collection,820 resource_properties.chain(821 [822 Self::rmrk_property(PendingResourceAccept, &pending)?,823 Self::rmrk_property(PendingResourceRemoval, &false)?,824 ]825 .into_iter(),826 ),827 )828 .map_err(|err| match err {829 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),830 err => Self::map_unique_err_to_proxy(err),831 })?;832833 Ok(resource_id.0)834 }835836 fn resource_remove(837 sender: T::AccountId,838 collection_id: CollectionId,839 nft_id: TokenId,840 resource_id: TokenId,841 ) -> DispatchResult {842 let collection =843 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;844 ensure!(collection.owner == sender, Error::<T>::NoPermission);845846 let resource_collection_id: CollectionId =847 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;848 let resource_collection =849 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;850 ensure!(851 <PalletNft<T>>::token_exists(&resource_collection, resource_id),852 Error::<T>::ResourceDoesntExist853 );854855 let budget = up_data_structs::budget::Value::new(10);856 let topmost_owner =857 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;858859 let sender = T::CrossAccountId::from_sub(sender);860 if topmost_owner == sender {861 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)862 .map_err(Self::map_unique_err_to_proxy)?;863 } else {864 <PalletNft<T>>::set_scoped_token_property(865 resource_collection_id,866 resource_id,867 PropertyScope::Rmrk,868 Self::rmrk_property(PendingResourceRemoval, &true)?,869 )?;870 }871872 Ok(())873 }874875 fn change_collection_owner(876 collection_id: CollectionId,877 collection_type: misc::CollectionType,878 sender: T::AccountId,879 new_owner: T::AccountId,880 ) -> DispatchResult {881 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;882 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;883884 let mut collection = collection.into_inner();885886 collection.owner = new_owner;887 collection.save()888 }889890 fn check_collection_owner(891 collection: &NonfungibleHandle<T>,892 account: &T::CrossAccountId,893 ) -> DispatchResult {894 collection895 .check_is_owner(account)896 .map_err(Self::map_unique_err_to_proxy)897 }898899 pub fn last_collection_idx() -> RmrkCollectionId {900 <CollectionIndex<T>>::get()901 }902903 pub fn unique_collection_id(904 rmrk_collection_id: RmrkCollectionId,905 ) -> Result<CollectionId, DispatchError> {906 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)907 .map_err(|_| <Error<T>>::CollectionUnknown.into())908 }909910 pub fn rmrk_collection_id(911 unique_collection_id: CollectionId912 ) -> Result<RmrkCollectionId, DispatchError> {913 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)914 .map_err(|_| <Error<T>>::CollectionUnknown.into())915 }916917 pub fn get_nft_collection(918 collection_id: CollectionId,919 ) -> Result<NonfungibleHandle<T>, DispatchError> {920 let collection = <CollectionHandle<T>>::try_get(collection_id)921 .map_err(|_| <Error<T>>::CollectionUnknown)?;922923 match collection.mode {924 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),925 _ => Err(<Error<T>>::CollectionUnknown.into()),926 }927 }928929 pub fn collection_exists(collection_id: CollectionId) -> bool {930 <CollectionHandle<T>>::try_get(collection_id).is_ok()931 }932933 pub fn get_collection_property(934 collection_id: CollectionId,935 key: RmrkProperty,936 ) -> Result<PropertyValue, DispatchError> {937 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)938 .get(&Self::rmrk_property_key(key)?)939 .ok_or(<Error<T>>::CollectionUnknown)?940 .clone();941942 Ok(collection_property)943 }944945 pub fn get_collection_property_decoded<V: Decode>(946 collection_id: CollectionId,947 key: RmrkProperty,948 ) -> Result<V, DispatchError> {949 Self::decode_property(Self::get_collection_property(collection_id, key)?)950 }951952 pub fn get_collection_type(953 collection_id: CollectionId,954 ) -> Result<misc::CollectionType, DispatchError> {955 Self::get_collection_property_decoded(collection_id, CollectionType)956 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())957 }958959 pub fn ensure_collection_type(960 collection_id: CollectionId,961 collection_type: misc::CollectionType,962 ) -> DispatchResult {963 let actual_type = Self::get_collection_type(collection_id)?;964 ensure!(965 actual_type == collection_type,966 <CommonError<T>>::NoPermission967 );968969 Ok(())970 }971972 pub fn get_typed_nft_collection(973 collection_id: CollectionId,974 collection_type: misc::CollectionType,975 ) -> Result<NonfungibleHandle<T>, DispatchError> {976 Self::ensure_collection_type(collection_id, collection_type)?;977978 Self::get_nft_collection(collection_id)979 }980981 pub fn get_typed_nft_collection_mapped(982 rmrk_collection_id: RmrkCollectionId,983 collection_type: misc::CollectionType,984 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {985 let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;986987 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;988989 Ok((collection, unique_collection_id))990 }991992 pub fn get_nft_property(993 collection_id: CollectionId,994 nft_id: TokenId,995 key: RmrkProperty,996 ) -> Result<PropertyValue, DispatchError> {997 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))998 .get(&Self::rmrk_property_key(key)?)999 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1000 .clone();10011002 Ok(nft_property)1003 }10041005 pub fn get_nft_property_decoded<V: Decode>(1006 collection_id: CollectionId,1007 nft_id: TokenId,1008 key: RmrkProperty,1009 ) -> Result<V, DispatchError> {1010 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1011 }10121013 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1014 <TokenData<T>>::contains_key((collection_id, nft_id))1015 }10161017 pub fn get_nft_type(1018 collection_id: CollectionId,1019 token_id: TokenId,1020 ) -> Result<NftType, DispatchError> {1021 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1022 .map_err(|_| <Error<T>>::NftTypeEncodeError.into())1023 }10241025 pub fn ensure_nft_type(1026 collection_id: CollectionId,1027 token_id: TokenId,1028 nft_type: NftType,1029 ) -> DispatchResult {1030 let actual_type = Self::get_nft_type(collection_id, token_id)?;1031 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);10321033 Ok(())1034 }10351036 pub fn ensure_nft_owner(1037 collection_id: CollectionId,1038 token_id: TokenId,1039 possible_owner: &T::CrossAccountId,1040 nesting_budget: &dyn budget::Budget1041 ) -> DispatchResult {1042 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1043 possible_owner.clone(),1044 collection_id,1045 token_id,1046 None,1047 nesting_budget,1048 )?;10491050 ensure!(1051 is_owned,1052 <Error<T>>::NoPermission1053 );10541055 Ok(())1056 }10571058 pub fn filter_user_properties<Key, Value, R, Mapper>(1059 collection_id: CollectionId,1060 token_id: Option<TokenId>,1061 filter_keys: Option<Vec<RmrkPropertyKey>>,1062 mapper: Mapper,1063 ) -> Result<Vec<R>, DispatchError>1064 where1065 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1066 Value: Decode + Default,1067 Mapper: Fn(Key, Value) -> R,1068 {1069 filter_keys1070 .map(|keys| {1071 let properties = keys1072 .into_iter()1073 .filter_map(|key| {1074 let key: Key = key.try_into().ok()?;10751076 let value = match token_id {1077 Some(token_id) => Self::get_nft_property_decoded(1078 collection_id,1079 token_id,1080 UserProperty(key.as_ref()),1081 ),1082 None => Self::get_collection_property_decoded(1083 collection_id,1084 UserProperty(key.as_ref()),1085 ),1086 }1087 .ok()?;10881089 Some(mapper(key, value))1090 })1091 .collect();10921093 Ok(properties)1094 })1095 .unwrap_or_else(|| {1096 let properties =1097 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();10981099 Ok(properties)1100 })1101 }11021103 pub fn iterate_user_properties<Key, Value, R, Mapper>(1104 collection_id: CollectionId,1105 token_id: Option<TokenId>,1106 mapper: Mapper,1107 ) -> Result<impl Iterator<Item = R>, DispatchError>1108 where1109 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1110 Value: Decode + Default,1111 Mapper: Fn(Key, Value) -> R,1112 {1113 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;11141115 let properties = match token_id {1116 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1117 None => <PalletCommon<T>>::collection_properties(collection_id),1118 };11191120 let properties = properties.into_iter().filter_map(move |(key, value)| {1121 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;11221123 let key: Key = key.to_vec().try_into().ok()?;1124 let value: Value = value.decode().ok()?;11251126 Some(mapper(key, value))1127 });11281129 Ok(properties)1130 }11311132 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1133 map_unique_err_to_proxy! {1134 match err {1135 CommonError::NoPermission => NoPermission,1136 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1137 CommonError::PublicMintingNotAllowed => NoPermission,1138 CommonError::TokenNotFound => NoAvailableNftId,1139 CommonError::ApprovedValueTooLow => NoPermission,1140 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1141 StructureError::TokenNotFound => NoAvailableNftId,1142 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1143 }1144 }1145 }1146}