difftreelog
fix(rmrk) map CollectionNotEmpty error
in: master
2 files 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 ensure!(208 collection.total_supply() == 0,209 <Error<T>>::CollectionNotEmpty210 );211212 <PalletNft<T>>::destroy_collection(collection, &cross_sender)213 .map_err(Self::map_unique_err_to_proxy)?;214215 Self::deposit_event(Event::CollectionDestroyed {216 issuer: sender,217 collection_id,218 });219220 Ok(())221 }222223 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]224 #[transactional]225 pub fn change_collection_issuer(226 origin: OriginFor<T>,227 collection_id: RmrkCollectionId,228 new_issuer: <T::Lookup as StaticLookup>::Source,229 ) -> DispatchResult {230 let sender = ensure_signed(origin)?;231232 let new_issuer = T::Lookup::lookup(new_issuer)?;233234 Self::change_collection_owner(235 Self::unique_collection_id(collection_id)?,236 misc::CollectionType::Regular,237 sender.clone(),238 new_issuer.clone(),239 )?;240241 Self::deposit_event(Event::IssuerChanged {242 old_issuer: sender,243 new_issuer,244 collection_id,245 });246247 Ok(())248 }249250 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]251 #[transactional]252 pub fn lock_collection(253 origin: OriginFor<T>,254 collection_id: RmrkCollectionId,255 ) -> DispatchResult {256 let sender = ensure_signed(origin)?;257 let cross_sender = T::CrossAccountId::from_sub(sender.clone());258259 let collection = Self::get_typed_nft_collection(260 Self::unique_collection_id(collection_id)?,261 misc::CollectionType::Regular,262 )?;263264 Self::check_collection_owner(&collection, &cross_sender)?;265266 let token_count = collection.total_supply();267268 let mut collection = collection.into_inner();269 collection.limits.token_limit = Some(token_count);270 collection.save()?;271272 Self::deposit_event(Event::CollectionLocked {273 issuer: sender,274 collection_id,275 });276277 Ok(())278 }279280 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]281 #[transactional]282 pub fn mint_nft(283 origin: OriginFor<T>,284 owner: T::AccountId,285 collection_id: RmrkCollectionId,286 recipient: Option<T::AccountId>,287 royalty_amount: Option<Permill>,288 metadata: RmrkString,289 transferable: bool,290 ) -> DispatchResult {291 let sender = ensure_signed(origin)?;292 let sender = T::CrossAccountId::from_sub(sender);293 let cross_owner = T::CrossAccountId::from_sub(owner.clone());294295 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {296 recipient: recipient.unwrap_or_else(|| owner.clone()),297 amount,298 });299300 let collection = Self::get_typed_nft_collection(301 Self::unique_collection_id(collection_id)?,302 misc::CollectionType::Regular,303 )?;304305 let nft_id = Self::create_nft(306 &sender,307 &cross_owner,308 &collection,309 [310 Self::rmrk_property(TokenType, &NftType::Regular)?,311 Self::rmrk_property(Transferable, &transferable)?,312 Self::rmrk_property(PendingNftAccept, &false)?,313 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,314 Self::rmrk_property(Metadata, &metadata)?,315 Self::rmrk_property(Equipped, &false)?,316 Self::rmrk_property(317 ResourceCollection,318 &Self::init_collection(319 sender.clone(),320 CreateCollectionData {321 ..Default::default()322 },323 [Self::rmrk_property(324 CollectionType,325 &misc::CollectionType::Resource,326 )?]327 .into_iter(),328 )?,329 )?, // todo possibly add limits to the collection if rmrk warrants them330 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,331 ]332 .into_iter(),333 )334 .map_err(|err| match err {335 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),336 err => Self::map_unique_err_to_proxy(err),337 })?;338339 Self::deposit_event(Event::NftMinted {340 owner,341 collection_id,342 nft_id: nft_id.0,343 });344345 Ok(())346 }347348 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]349 #[transactional]350 pub fn burn_nft(351 origin: OriginFor<T>,352 collection_id: RmrkCollectionId,353 nft_id: RmrkNftId,354 ) -> DispatchResult {355 let sender = ensure_signed(origin)?;356 let cross_sender = T::CrossAccountId::from_sub(sender.clone());357358 Self::destroy_nft(359 cross_sender,360 Self::unique_collection_id(collection_id)?,361 misc::CollectionType::Regular,362 nft_id.into(),363 )?;364365 Self::deposit_event(Event::NFTBurned {366 owner: sender,367 nft_id,368 });369370 Ok(())371 }372373 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]374 #[transactional]375 pub fn send(376 origin: OriginFor<T>,377 rmrk_collection_id: RmrkCollectionId,378 rmrk_nft_id: RmrkNftId,379 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,380 ) -> DispatchResult {381 let sender = ensure_signed(origin.clone())?;382 let cross_sender = T::CrossAccountId::from_sub(sender);383384 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;385 let nft_id = rmrk_nft_id.into();386387 let token_data = <TokenData<T>>::get((collection_id, nft_id))388 .ok_or(<Error<T>>::NoAvailableNftId)?;389390 let from = token_data.owner;391392 let collection = Self::get_typed_nft_collection(393 collection_id,394 misc::CollectionType::Regular,395 )?;396397 if !Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)? {398 return Err(<Error<T>>::NonTransferable.into());399 }400401 if Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)? {402 return Err(<Error<T>>::NoPermission.into());403 }404405 let target_owner;406407 match new_owner {408 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {409 target_owner = T::CrossAccountId::from_sub(account_id);410 },411 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {412 let target_collection_id = Self::unique_collection_id(target_collection_id)?;413414 let target_nft_budget = budget::Value::new(NESTING_BUDGET);415416 let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(417 target_collection_id,418 target_nft_id.into(),419 Some((collection_id, nft_id)),420 &target_nft_budget,421 ).map_err(Self::map_unique_err_to_proxy)?;422423 let is_approval_required = cross_sender != target_nft_owner;424425 if is_approval_required {426 target_owner = target_nft_owner;427428 <PalletNft<T>>::set_scoped_token_property(429 collection.id,430 nft_id,431 PropertyScope::Rmrk,432 Self::rmrk_property(PendingNftAccept, &is_approval_required)?,433 )?;434 } else {435 target_owner = T::CrossTokenAddressMapping::token_to_address(436 target_collection_id,437 target_nft_id.into(),438 );439 }440 }441 }442443 let src_nft_budget = budget::Value::new(NESTING_BUDGET);444445 <PalletNft<T>>::transfer_from(446 &collection,447 &cross_sender,448 &from,449 &target_owner,450 nft_id,451 &src_nft_budget452 ).map_err(Self::map_unique_err_to_proxy)?;453454 Ok(())455 }456457 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]458 #[transactional]459 pub fn accept_nft(460 origin: OriginFor<T>,461 rmrk_collection_id: RmrkCollectionId,462 rmrk_nft_id: RmrkNftId,463 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,464 ) -> DispatchResult {465 let sender = ensure_signed(origin.clone())?;466 let cross_sender = T::CrossAccountId::from_sub(sender);467468 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;469 let nft_id = rmrk_nft_id.into();470471 let collection = Self::get_typed_nft_collection(472 collection_id,473 misc::CollectionType::Regular,474 )?;475476 let new_owner = match new_owner {477 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {478 T::CrossAccountId::from_sub(account_id)479 },480 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {481 let target_collection_id = Self::unique_collection_id(target_collection_id)?;482483 T::CrossTokenAddressMapping::token_to_address(target_collection_id, TokenId(target_nft_id))484 }485 };486487 let budget = budget::Value::new(NESTING_BUDGET);488489 <PalletNft<T>>::transfer(490 &collection,491 &cross_sender,492 &new_owner,493 nft_id,494 &budget,495 ).map_err(|err| {496 if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {497 <Error<T>>::CannotAcceptNonOwnedNft.into()498 } else {499 Self::map_unique_err_to_proxy(err)500 }501 })?;502503 <PalletNft<T>>::set_scoped_token_property(504 collection.id,505 nft_id,506 PropertyScope::Rmrk,507 Self::rmrk_property(PendingNftAccept, &false)?,508 )?;509510 Ok(())511 }512513 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]514 #[transactional]515 pub fn set_property(516 origin: OriginFor<T>,517 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,518 maybe_nft_id: Option<RmrkNftId>,519 key: RmrkKeyString,520 value: RmrkValueString,521 ) -> DispatchResult {522 let sender = ensure_signed(origin)?;523 let sender = T::CrossAccountId::from_sub(sender);524525 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;526 let budget = budget::Value::new(NESTING_BUDGET);527528 match maybe_nft_id {529 Some(nft_id) => {530 let token_id: TokenId = nft_id.into();531532 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;533 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;534535 <PalletNft<T>>::set_scoped_token_property(536 collection_id,537 token_id,538 PropertyScope::Rmrk,539 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,540 )?;541 }542 None => {543 let collection = Self::get_typed_nft_collection(544 collection_id,545 misc::CollectionType::Regular,546 )?;547548 Self::check_collection_owner(&collection, &sender)?;549550 <PalletCommon<T>>::set_scoped_collection_property(551 collection_id,552 PropertyScope::Rmrk,553 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,554 )?;555 }556 }557558 Self::deposit_event(Event::PropertySet {559 collection_id: rmrk_collection_id,560 maybe_nft_id,561 key,562 value,563 });564565 Ok(())566 }567568 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]569 #[transactional]570 pub fn add_basic_resource(571 origin: OriginFor<T>,572 collection_id: RmrkCollectionId,573 nft_id: RmrkNftId,574 resource: RmrkBasicResource,575 ) -> DispatchResult {576 let sender = ensure_signed(origin.clone())?;577578 let resource_id = Self::resource_add(579 sender,580 Self::unique_collection_id(collection_id)?,581 nft_id.into(),582 [583 Self::rmrk_property(TokenType, &NftType::Resource)?,584 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,585 Self::rmrk_property(Src, &resource.src)?,586 Self::rmrk_property(Metadata, &resource.metadata)?,587 Self::rmrk_property(License, &resource.license)?,588 Self::rmrk_property(Thumb, &resource.thumb)?,589 ]590 .into_iter(),591 )?;592593 Self::deposit_event(Event::ResourceAdded {594 nft_id,595 resource_id,596 });597 Ok(())598 }599600 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]601 #[transactional]602 pub fn add_composable_resource(603 origin: OriginFor<T>,604 collection_id: RmrkCollectionId,605 nft_id: RmrkNftId,606 _resource_id: RmrkBoundedResource,607 resource: RmrkComposableResource,608 ) -> DispatchResult {609 let sender = ensure_signed(origin.clone())?;610611 let resource_id = Self::resource_add(612 sender,613 Self::unique_collection_id(collection_id)?,614 nft_id.into(),615 [616 Self::rmrk_property(TokenType, &NftType::Resource)?,617 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,618 Self::rmrk_property(Parts, &resource.parts)?,619 Self::rmrk_property(Base, &resource.base)?,620 Self::rmrk_property(Src, &resource.src)?,621 Self::rmrk_property(Metadata, &resource.metadata)?,622 Self::rmrk_property(License, &resource.license)?,623 Self::rmrk_property(Thumb, &resource.thumb)?,624 ]625 .into_iter(),626 )?;627628 Self::deposit_event(Event::ResourceAdded {629 nft_id,630 resource_id,631 });632 Ok(())633 }634635 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]636 #[transactional]637 pub fn add_slot_resource(638 origin: OriginFor<T>,639 collection_id: RmrkCollectionId,640 nft_id: RmrkNftId,641 resource: RmrkSlotResource,642 ) -> DispatchResult {643 let sender = ensure_signed(origin.clone())?;644645 let resource_id = Self::resource_add(646 sender,647 Self::unique_collection_id(collection_id)?,648 nft_id.into(),649 [650 Self::rmrk_property(TokenType, &NftType::Resource)?,651 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,652 Self::rmrk_property(Base, &resource.base)?,653 Self::rmrk_property(Src, &resource.src)?,654 Self::rmrk_property(Metadata, &resource.metadata)?,655 Self::rmrk_property(Slot, &resource.slot)?,656 Self::rmrk_property(License, &resource.license)?,657 Self::rmrk_property(Thumb, &resource.thumb)?,658 ]659 .into_iter(),660 )?;661662 Self::deposit_event(Event::ResourceAdded {663 nft_id,664 resource_id,665 });666 Ok(())667 }668669 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]670 #[transactional]671 pub fn remove_resource(672 origin: OriginFor<T>,673 collection_id: RmrkCollectionId,674 nft_id: RmrkNftId,675 resource_id: RmrkResourceId,676 ) -> DispatchResult {677 let sender = ensure_signed(origin.clone())?;678679 Self::resource_remove(680 sender,681 Self::unique_collection_id(collection_id)?,682 nft_id.into(),683 resource_id.into(),684 )?;685686 Self::deposit_event(Event::ResourceRemoval {687 nft_id,688 resource_id,689 });690 Ok(())691 }692 }693}694695impl<T: Config> Pallet<T> {696 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {697 let key = rmrk_key.to_key::<T>()?;698699 let scoped_key = PropertyScope::Rmrk700 .apply(key)701 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;702703 Ok(scoped_key)704 }705706 // todo think about renaming these707 pub fn rmrk_property<E: Encode>(708 rmrk_key: RmrkProperty,709 value: &E,710 ) -> Result<Property, DispatchError> {711 let key = rmrk_key.to_key::<T>()?;712713 let value = value714 .encode()715 .try_into()716 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;717718 let property = Property { key, value };719720 Ok(property)721 }722723 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {724 vec.decode()725 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())726 }727728 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>729 where730 BoundedVec<u8, S>: TryFrom<Vec<u8>>,731 {732 vec.rebind()733 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())734 }735736 fn init_collection(737 sender: T::CrossAccountId,738 data: CreateCollectionData<T::AccountId>,739 properties: impl Iterator<Item = Property>,740 ) -> Result<CollectionId, DispatchError> {741 let collection_id = <PalletNft<T>>::init_collection(sender, data);742743 if let Err(DispatchError::Arithmetic(_)) = &collection_id {744 return Err(<Error<T>>::NoAvailableCollectionId.into());745 }746747 <PalletCommon<T>>::set_scoped_collection_properties(748 collection_id?,749 PropertyScope::Rmrk,750 properties,751 )?;752753 collection_id754 }755756 pub fn create_nft(757 sender: &T::CrossAccountId,758 owner: &T::CrossAccountId,759 collection: &NonfungibleHandle<T>,760 properties: impl Iterator<Item = Property>,761 ) -> Result<TokenId, DispatchError> {762 let data = CreateNftExData {763 properties: BoundedVec::default(),764 owner: owner.clone(),765 };766767 let budget = budget::Value::new(NESTING_BUDGET);768769 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;770771 let nft_id = <PalletNft<T>>::current_token_id(collection.id);772773 <PalletNft<T>>::set_scoped_token_properties(774 collection.id,775 nft_id,776 PropertyScope::Rmrk,777 properties,778 )?;779780 Ok(nft_id)781 }782783 fn destroy_nft(784 sender: T::CrossAccountId,785 collection_id: CollectionId,786 collection_type: misc::CollectionType,787 token_id: TokenId,788 ) -> DispatchResult {789 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;790791 <PalletNft<T>>::burn(&collection, &sender, token_id)792 .map_err(Self::map_unique_err_to_proxy)?;793794 Ok(())795 }796797 fn resource_add(798 sender: T::AccountId,799 collection_id: CollectionId,800 token_id: TokenId,801 resource_properties: impl Iterator<Item = Property>,802 ) -> Result<RmrkResourceId, DispatchError> {803 let collection =804 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;805 ensure!(collection.owner == sender, Error::<T>::NoPermission);806807 // Check NFT lock status // todo depends on market, maybe later808 //ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);809810 let sender = T::CrossAccountId::from_sub(sender);811 let budget = budget::Value::new(NESTING_BUDGET);812 let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();813814 let resource_collection_id: CollectionId =815 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;816 let resource_collection =817 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;818819 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them820821 let resource_id = Self::create_nft(822 &sender, // todo owner of the nft?823 &sender,824 &resource_collection,825 resource_properties.chain(826 [827 Self::rmrk_property(PendingResourceAccept, &pending)?,828 Self::rmrk_property(PendingResourceRemoval, &false)?,829 ]830 .into_iter(),831 ),832 )833 .map_err(|err| match err {834 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),835 err => Self::map_unique_err_to_proxy(err),836 })?;837838 Ok(resource_id.0)839 }840841 fn resource_remove(842 sender: T::AccountId,843 collection_id: CollectionId,844 nft_id: TokenId,845 resource_id: TokenId,846 ) -> DispatchResult {847 let collection =848 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;849 ensure!(collection.owner == sender, Error::<T>::NoPermission);850851 let resource_collection_id: CollectionId =852 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;853 let resource_collection =854 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;855 ensure!(856 <PalletNft<T>>::token_exists(&resource_collection, resource_id),857 Error::<T>::ResourceDoesntExist858 );859860 let budget = up_data_structs::budget::Value::new(10);861 let topmost_owner =862 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;863864 let sender = T::CrossAccountId::from_sub(sender);865 if topmost_owner == sender {866 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)867 .map_err(Self::map_unique_err_to_proxy)?;868 } else {869 <PalletNft<T>>::set_scoped_token_property(870 resource_collection_id,871 resource_id,872 PropertyScope::Rmrk,873 Self::rmrk_property(PendingResourceRemoval, &true)?,874 )?;875 }876877 Ok(())878 }879880 fn change_collection_owner(881 collection_id: CollectionId,882 collection_type: misc::CollectionType,883 sender: T::AccountId,884 new_owner: T::AccountId,885 ) -> DispatchResult {886 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;887 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;888889 let mut collection = collection.into_inner();890891 collection.owner = new_owner;892 collection.save()893 }894895 fn check_collection_owner(896 collection: &NonfungibleHandle<T>,897 account: &T::CrossAccountId,898 ) -> DispatchResult {899 collection900 .check_is_owner(account)901 .map_err(Self::map_unique_err_to_proxy)902 }903904 pub fn last_collection_idx() -> RmrkCollectionId {905 <CollectionIndex<T>>::get()906 }907908 pub fn unique_collection_id(909 rmrk_collection_id: RmrkCollectionId,910 ) -> Result<CollectionId, DispatchError> {911 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)912 .map_err(|_| <Error<T>>::CollectionUnknown.into())913 }914915 pub fn rmrk_collection_id(916 unique_collection_id: CollectionId917 ) -> Result<RmrkCollectionId, DispatchError> {918 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)919 .map_err(|_| <Error<T>>::CollectionUnknown.into())920 }921922 pub fn get_nft_collection(923 collection_id: CollectionId,924 ) -> Result<NonfungibleHandle<T>, DispatchError> {925 let collection = <CollectionHandle<T>>::try_get(collection_id)926 .map_err(|_| <Error<T>>::CollectionUnknown)?;927928 match collection.mode {929 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),930 _ => Err(<Error<T>>::CollectionUnknown.into()),931 }932 }933934 pub fn collection_exists(collection_id: CollectionId) -> bool {935 <CollectionHandle<T>>::try_get(collection_id).is_ok()936 }937938 pub fn get_collection_property(939 collection_id: CollectionId,940 key: RmrkProperty,941 ) -> Result<PropertyValue, DispatchError> {942 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)943 .get(&Self::rmrk_property_key(key)?)944 .ok_or(<Error<T>>::CollectionUnknown)?945 .clone();946947 Ok(collection_property)948 }949950 pub fn get_collection_property_decoded<V: Decode>(951 collection_id: CollectionId,952 key: RmrkProperty,953 ) -> Result<V, DispatchError> {954 Self::decode_property(Self::get_collection_property(collection_id, key)?)955 }956957 pub fn get_collection_type(958 collection_id: CollectionId,959 ) -> Result<misc::CollectionType, DispatchError> {960 Self::get_collection_property_decoded(collection_id, CollectionType)961 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())962 }963964 pub fn ensure_collection_type(965 collection_id: CollectionId,966 collection_type: misc::CollectionType,967 ) -> DispatchResult {968 let actual_type = Self::get_collection_type(collection_id)?;969 ensure!(970 actual_type == collection_type,971 <CommonError<T>>::NoPermission972 );973974 Ok(())975 }976977 pub fn get_typed_nft_collection(978 collection_id: CollectionId,979 collection_type: misc::CollectionType,980 ) -> Result<NonfungibleHandle<T>, DispatchError> {981 Self::ensure_collection_type(collection_id, collection_type)?;982983 Self::get_nft_collection(collection_id)984 }985986 pub fn get_typed_nft_collection_mapped(987 rmrk_collection_id: RmrkCollectionId,988 collection_type: misc::CollectionType,989 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {990 let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;991992 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;993994 Ok((collection, unique_collection_id))995 }996997 pub fn get_nft_property(998 collection_id: CollectionId,999 nft_id: TokenId,1000 key: RmrkProperty,1001 ) -> Result<PropertyValue, DispatchError> {1002 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1003 .get(&Self::rmrk_property_key(key)?)1004 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1005 .clone();10061007 Ok(nft_property)1008 }10091010 pub fn get_nft_property_decoded<V: Decode>(1011 collection_id: CollectionId,1012 nft_id: TokenId,1013 key: RmrkProperty,1014 ) -> Result<V, DispatchError> {1015 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1016 }10171018 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1019 <TokenData<T>>::contains_key((collection_id, nft_id))1020 }10211022 pub fn get_nft_type(1023 collection_id: CollectionId,1024 token_id: TokenId,1025 ) -> Result<NftType, DispatchError> {1026 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1027 .map_err(|_| <Error<T>>::NftTypeEncodeError.into())1028 }10291030 pub fn ensure_nft_type(1031 collection_id: CollectionId,1032 token_id: TokenId,1033 nft_type: NftType,1034 ) -> DispatchResult {1035 let actual_type = Self::get_nft_type(collection_id, token_id)?;1036 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);10371038 Ok(())1039 }10401041 pub fn ensure_nft_owner(1042 collection_id: CollectionId,1043 token_id: TokenId,1044 possible_owner: &T::CrossAccountId,1045 nesting_budget: &dyn budget::Budget1046 ) -> DispatchResult {1047 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1048 possible_owner.clone(),1049 collection_id,1050 token_id,1051 None,1052 nesting_budget,1053 )?;10541055 ensure!(1056 is_owned,1057 <Error<T>>::NoPermission1058 );10591060 Ok(())1061 }10621063 pub fn filter_user_properties<Key, Value, R, Mapper>(1064 collection_id: CollectionId,1065 token_id: Option<TokenId>,1066 filter_keys: Option<Vec<RmrkPropertyKey>>,1067 mapper: Mapper,1068 ) -> Result<Vec<R>, DispatchError>1069 where1070 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1071 Value: Decode + Default,1072 Mapper: Fn(Key, Value) -> R,1073 {1074 filter_keys1075 .map(|keys| {1076 let properties = keys1077 .into_iter()1078 .filter_map(|key| {1079 let key: Key = key.try_into().ok()?;10801081 let value = match token_id {1082 Some(token_id) => Self::get_nft_property_decoded(1083 collection_id,1084 token_id,1085 UserProperty(key.as_ref()),1086 ),1087 None => Self::get_collection_property_decoded(1088 collection_id,1089 UserProperty(key.as_ref()),1090 ),1091 }1092 .ok()?;10931094 Some(mapper(key, value))1095 })1096 .collect();10971098 Ok(properties)1099 })1100 .unwrap_or_else(|| {1101 let properties =1102 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();11031104 Ok(properties)1105 })1106 }11071108 pub fn iterate_user_properties<Key, Value, R, Mapper>(1109 collection_id: CollectionId,1110 token_id: Option<TokenId>,1111 mapper: Mapper,1112 ) -> Result<impl Iterator<Item = R>, DispatchError>1113 where1114 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1115 Value: Decode + Default,1116 Mapper: Fn(Key, Value) -> R,1117 {1118 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;11191120 let properties = match token_id {1121 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1122 None => <PalletCommon<T>>::collection_properties(collection_id),1123 };11241125 let properties = properties.into_iter().filter_map(move |(key, value)| {1126 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;11271128 let key: Key = key.to_vec().try_into().ok()?;1129 let value: Value = value.decode().ok()?;11301131 Some(mapper(key, value))1132 });11331134 Ok(properties)1135 }11361137 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1138 map_unique_err_to_proxy! {1139 match err {1140 CommonError::NoPermission => NoPermission,1141 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1142 CommonError::PublicMintingNotAllowed => NoPermission,1143 CommonError::TokenNotFound => NoAvailableNftId,1144 CommonError::ApprovedValueTooLow => NoPermission,1145 StructureError::TokenNotFound => NoAvailableNftId,1146 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf1147 }1148 }1149 }1150}pallets/proxy-rmrk-core/src/misc.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/misc.rs
+++ b/pallets/proxy-rmrk-core/src/misc.rs
@@ -3,7 +3,7 @@
#[macro_export]
macro_rules! map_unique_err_to_proxy {
- (match $err:ident { $($unique_err_ty:ident :: $unique_err:ident => $proxy_err:ident),+ }) => {
+ (match $err:ident { $($unique_err_ty:ident :: $unique_err:ident => $proxy_err:ident),+ $(,)? }) => {
$(
if $err == <$unique_err_ty<T>>::$unique_err.into() {
return <Error<T>>::$proxy_err.into()