difftreelog
fix(rmrk) sending nft
in: master
5 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;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 CollectionFullOrLocked,132 ResourceDoesntExist,133 }134135 #[pallet::call]136 impl<T: Config> Pallet<T> {137 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]138 #[transactional]139 pub fn create_collection(140 origin: OriginFor<T>,141 metadata: RmrkString,142 max: Option<u32>,143 symbol: RmrkCollectionSymbol,144 ) -> DispatchResult {145 let sender = ensure_signed(origin)?;146147 let limits = CollectionLimits {148 owner_can_transfer: Some(false),149 token_limit: max,150 ..Default::default()151 };152153 let data = CreateCollectionData {154 limits: Some(limits),155 token_prefix: symbol156 .into_inner()157 .try_into()158 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,159 permissions: Some(CollectionPermissions {160 nesting: Some(NestingRule::Owner),161 ..Default::default()162 }),163 ..Default::default()164 };165166 let unique_collection_id = Self::init_collection(167 T::CrossAccountId::from_sub(sender.clone()),168 data,169 [170 Self::rmrk_property(Metadata, &metadata)?,171 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,172 ]173 .into_iter(),174 )?;175 let rmrk_collection_id = <CollectionIndex<T>>::get();176177 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);178 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);179180 <CollectionIndex<T>>::mutate(|n| *n += 1);181182 Self::deposit_event(Event::CollectionCreated {183 issuer: sender,184 collection_id: rmrk_collection_id,185 });186187 Ok(())188 }189190 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]191 #[transactional]192 pub fn destroy_collection(193 origin: OriginFor<T>,194 collection_id: RmrkCollectionId,195 ) -> DispatchResult {196 let sender = ensure_signed(origin)?;197 let cross_sender = T::CrossAccountId::from_sub(sender.clone());198199 let collection = Self::get_typed_nft_collection(200 Self::unique_collection_id(collection_id)?,201 misc::CollectionType::Regular,202 )?;203204 ensure!(205 collection.total_supply() == 0,206 <Error<T>>::CollectionNotEmpty207 );208209 <PalletNft<T>>::destroy_collection(collection, &cross_sender)210 .map_err(Self::map_common_err_to_proxy)?;211212 Self::deposit_event(Event::CollectionDestroyed {213 issuer: sender,214 collection_id,215 });216217 Ok(())218 }219220 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]221 #[transactional]222 pub fn change_collection_issuer(223 origin: OriginFor<T>,224 collection_id: RmrkCollectionId,225 new_issuer: <T::Lookup as StaticLookup>::Source,226 ) -> DispatchResult {227 let sender = ensure_signed(origin)?;228229 let new_issuer = T::Lookup::lookup(new_issuer)?;230231 Self::change_collection_owner(232 Self::unique_collection_id(collection_id)?,233 misc::CollectionType::Regular,234 sender.clone(),235 new_issuer.clone(),236 )?;237238 Self::deposit_event(Event::IssuerChanged {239 old_issuer: sender,240 new_issuer,241 collection_id,242 });243244 Ok(())245 }246247 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]248 #[transactional]249 pub fn lock_collection(250 origin: OriginFor<T>,251 collection_id: RmrkCollectionId,252 ) -> DispatchResult {253 let sender = ensure_signed(origin)?;254 let cross_sender = T::CrossAccountId::from_sub(sender.clone());255256 let collection = Self::get_typed_nft_collection(257 Self::unique_collection_id(collection_id)?,258 misc::CollectionType::Regular,259 )?;260261 Self::check_collection_owner(&collection, &cross_sender)?;262263 let token_count = collection.total_supply();264265 let mut collection = collection.into_inner();266 collection.limits.token_limit = Some(token_count);267 collection.save()?;268269 Self::deposit_event(Event::CollectionLocked {270 issuer: sender,271 collection_id,272 });273274 Ok(())275 }276277 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]278 #[transactional]279 pub fn mint_nft(280 origin: OriginFor<T>,281 owner: T::AccountId,282 collection_id: RmrkCollectionId,283 recipient: Option<T::AccountId>,284 royalty_amount: Option<Permill>,285 metadata: RmrkString,286 ) -> DispatchResult {287 let sender = ensure_signed(origin)?;288 let sender = T::CrossAccountId::from_sub(sender);289 let cross_owner = T::CrossAccountId::from_sub(owner.clone());290291 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {292 recipient: recipient.unwrap_or_else(|| owner.clone()),293 amount,294 });295296 let collection = Self::get_typed_nft_collection(297 Self::unique_collection_id(collection_id)?,298 misc::CollectionType::Regular,299 )?;300301 let nft_id = Self::create_nft(302 &sender,303 &cross_owner,304 &collection,305 [306 Self::rmrk_property(TokenType, &NftType::Regular)?,307 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,308 Self::rmrk_property(Metadata, &metadata)?,309 Self::rmrk_property(Equipped, &false)?,310 Self::rmrk_property(311 ResourceCollection,312 &Self::init_collection(313 sender.clone(),314 CreateCollectionData {315 ..Default::default()316 },317 [Self::rmrk_property(318 CollectionType,319 &misc::CollectionType::Resource,320 )?]321 .into_iter(),322 )?,323 )?, // todo possibly add limits to the collection if rmrk warrants them324 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,325 ]326 .into_iter(),327 )328 .map_err(|err| match err {329 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),330 err => Self::map_common_err_to_proxy(err),331 })?;332333 Self::deposit_event(Event::NftMinted {334 owner,335 collection_id,336 nft_id: nft_id.0,337 });338339 Ok(())340 }341342 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]343 #[transactional]344 pub fn burn_nft(345 origin: OriginFor<T>,346 collection_id: RmrkCollectionId,347 nft_id: RmrkNftId,348 ) -> DispatchResult {349 let sender = ensure_signed(origin)?;350 let cross_sender = T::CrossAccountId::from_sub(sender.clone());351352 Self::destroy_nft(353 cross_sender,354 Self::unique_collection_id(collection_id)?,355 misc::CollectionType::Regular,356 nft_id.into(),357 )?;358359 Self::deposit_event(Event::NFTBurned {360 owner: sender,361 nft_id,362 });363364 Ok(())365 }366367 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]368 #[transactional]369 pub fn send(370 origin: OriginFor<T>,371 rmrk_collection_id: RmrkCollectionId,372 rmrk_nft_id: RmrkNftId,373 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,374 ) -> DispatchResult {375 let sender = ensure_signed(origin.clone())?;376 let cross_sender = T::CrossAccountId::from_sub(sender.clone());377378 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;379 let nft_id = rmrk_nft_id.into();380381 let token_data = <TokenData<T>>::get((collection_id, nft_id))382 .ok_or(<Error<T>>::NoAvailableNftId)?;383384 let from = token_data.owner;385386 let collection = Self::get_typed_nft_collection(387 collection_id,388 misc::CollectionType::Regular,389 )?;390391 let budget = budget::Value::new(NESTING_BUDGET);392393 let target_owner;394395 match new_owner {396 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {397 target_owner = T::CrossAccountId::from_sub(account_id);398 },399 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {400 let target_collection_id = Self::unique_collection_id(target_collection_id)?;401402 target_owner = T::CrossTokenAddressMapping::token_to_address(403 target_collection_id,404 target_nft_id.into(),405 );406407 let spender = <PalletStructure<T>>::get_indirect_owner(408 target_collection_id,409 target_nft_id.into(),410 Some((collection_id, nft_id)),411 &budget,412 )?;413414 let is_approval_required = cross_sender != spender;415416 if is_approval_required {417 // FIXME418 // <PalletNft<T>>::set_allowance(419 // &collection,420 // &cross_sender,421 // nft_id,422 // Some(&spender),423 // &budget424 // ).map_err(Self::map_common_err_to_proxy)?;425426 return Ok(());427 }428 }429 }430431 <PalletNft<T>>::transfer_from(432 &collection,433 &cross_sender,434 &from,435 &target_owner,436 nft_id,437 &budget438 ).map_err(Self::map_common_err_to_proxy)?;439440 Ok(())441 }442443 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]444 #[transactional]445 pub fn set_property(446 origin: OriginFor<T>,447 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,448 maybe_nft_id: Option<RmrkNftId>,449 key: RmrkKeyString,450 value: RmrkValueString,451 ) -> DispatchResult {452 let sender = ensure_signed(origin)?;453 let sender = T::CrossAccountId::from_sub(sender);454455 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;456 let budget = budget::Value::new(NESTING_BUDGET);457458 match maybe_nft_id {459 Some(nft_id) => {460 let token_id: TokenId = nft_id.into();461462 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;463 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;464465 <PalletNft<T>>::set_scoped_token_property(466 collection_id,467 token_id,468 PropertyScope::Rmrk,469 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,470 )?;471 }472 None => {473 let collection = Self::get_typed_nft_collection(474 collection_id,475 misc::CollectionType::Regular,476 )?;477478 Self::check_collection_owner(&collection, &sender)?;479480 <PalletCommon<T>>::set_scoped_collection_property(481 collection_id,482 PropertyScope::Rmrk,483 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,484 )?;485 }486 }487488 Self::deposit_event(Event::PropertySet {489 collection_id: rmrk_collection_id,490 maybe_nft_id,491 key,492 value,493 });494495 Ok(())496 }497498 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]499 #[transactional]500 pub fn add_basic_resource(501 origin: OriginFor<T>,502 collection_id: RmrkCollectionId,503 nft_id: RmrkNftId,504 resource: RmrkBasicResource,505 ) -> DispatchResult {506 let sender = ensure_signed(origin.clone())?;507508 let resource_id = Self::resource_add(509 sender,510 Self::unique_collection_id(collection_id)?,511 nft_id.into(),512 [513 Self::rmrk_property(TokenType, &NftType::Resource)?,514 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,515 Self::rmrk_property(Src, &resource.src)?,516 Self::rmrk_property(Metadata, &resource.metadata)?,517 Self::rmrk_property(License, &resource.license)?,518 Self::rmrk_property(Thumb, &resource.thumb)?,519 ]520 .into_iter(),521 )?;522523 Self::deposit_event(Event::ResourceAdded {524 nft_id,525 resource_id,526 });527 Ok(())528 }529530 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]531 #[transactional]532 pub fn add_composable_resource(533 origin: OriginFor<T>,534 collection_id: RmrkCollectionId,535 nft_id: RmrkNftId,536 _resource_id: RmrkBoundedResource,537 resource: RmrkComposableResource,538 ) -> DispatchResult {539 let sender = ensure_signed(origin.clone())?;540541 let resource_id = Self::resource_add(542 sender,543 Self::unique_collection_id(collection_id)?,544 nft_id.into(),545 [546 Self::rmrk_property(TokenType, &NftType::Resource)?,547 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,548 Self::rmrk_property(Parts, &resource.parts)?,549 Self::rmrk_property(Base, &resource.base)?,550 Self::rmrk_property(Src, &resource.src)?,551 Self::rmrk_property(Metadata, &resource.metadata)?,552 Self::rmrk_property(License, &resource.license)?,553 Self::rmrk_property(Thumb, &resource.thumb)?,554 ]555 .into_iter(),556 )?;557558 Self::deposit_event(Event::ResourceAdded {559 nft_id,560 resource_id,561 });562 Ok(())563 }564565 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]566 #[transactional]567 pub fn add_slot_resource(568 origin: OriginFor<T>,569 collection_id: RmrkCollectionId,570 nft_id: RmrkNftId,571 resource: RmrkSlotResource,572 ) -> DispatchResult {573 let sender = ensure_signed(origin.clone())?;574575 let resource_id = Self::resource_add(576 sender,577 Self::unique_collection_id(collection_id)?,578 nft_id.into(),579 [580 Self::rmrk_property(TokenType, &NftType::Resource)?,581 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,582 Self::rmrk_property(Base, &resource.base)?,583 Self::rmrk_property(Src, &resource.src)?,584 Self::rmrk_property(Metadata, &resource.metadata)?,585 Self::rmrk_property(Slot, &resource.slot)?,586 Self::rmrk_property(License, &resource.license)?,587 Self::rmrk_property(Thumb, &resource.thumb)?,588 ]589 .into_iter(),590 )?;591592 Self::deposit_event(Event::ResourceAdded {593 nft_id,594 resource_id,595 });596 Ok(())597 }598599 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]600 #[transactional]601 pub fn remove_resource(602 origin: OriginFor<T>,603 collection_id: RmrkCollectionId,604 nft_id: RmrkNftId,605 resource_id: RmrkResourceId,606 ) -> DispatchResult {607 let sender = ensure_signed(origin.clone())?;608609 Self::resource_remove(610 sender,611 Self::unique_collection_id(collection_id)?,612 nft_id.into(),613 resource_id.into(),614 )?;615616 Self::deposit_event(Event::ResourceRemoval {617 nft_id,618 resource_id,619 });620 Ok(())621 }622 }623}624625impl<T: Config> Pallet<T> {626 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {627 let key = rmrk_key.to_key::<T>()?;628629 let scoped_key = PropertyScope::Rmrk630 .apply(key)631 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;632633 Ok(scoped_key)634 }635636 // todo think about renaming these637 pub fn rmrk_property<E: Encode>(638 rmrk_key: RmrkProperty,639 value: &E,640 ) -> Result<Property, DispatchError> {641 let key = rmrk_key.to_key::<T>()?;642643 let value = value644 .encode()645 .try_into()646 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;647648 let property = Property { key, value };649650 Ok(property)651 }652653 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {654 vec.decode()655 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())656 }657658 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>659 where660 BoundedVec<u8, S>: TryFrom<Vec<u8>>,661 {662 vec.rebind()663 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())664 }665666 fn init_collection(667 sender: T::CrossAccountId,668 data: CreateCollectionData<T::AccountId>,669 properties: impl Iterator<Item = Property>,670 ) -> Result<CollectionId, DispatchError> {671 let collection_id = <PalletNft<T>>::init_collection(sender, data);672673 if let Err(DispatchError::Arithmetic(_)) = &collection_id {674 return Err(<Error<T>>::NoAvailableCollectionId.into());675 }676677 <PalletCommon<T>>::set_scoped_collection_properties(678 collection_id?,679 PropertyScope::Rmrk,680 properties,681 )?;682683 collection_id684 }685686 pub fn create_nft(687 sender: &T::CrossAccountId,688 owner: &T::CrossAccountId,689 collection: &NonfungibleHandle<T>,690 properties: impl Iterator<Item = Property>,691 ) -> Result<TokenId, DispatchError> {692 let data = CreateNftExData {693 properties: BoundedVec::default(),694 owner: owner.clone(),695 };696697 let budget = budget::Value::new(NESTING_BUDGET);698699 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;700701 let nft_id = <PalletNft<T>>::current_token_id(collection.id);702703 <PalletNft<T>>::set_scoped_token_properties(704 collection.id,705 nft_id,706 PropertyScope::Rmrk,707 properties,708 )?;709710 Ok(nft_id)711 }712713 fn destroy_nft(714 sender: T::CrossAccountId,715 collection_id: CollectionId,716 collection_type: misc::CollectionType,717 token_id: TokenId,718 ) -> DispatchResult {719 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;720721 <PalletNft<T>>::burn(&collection, &sender, token_id)722 .map_err(Self::map_common_err_to_proxy)?;723724 Ok(())725 }726727 fn resource_add(728 sender: T::AccountId,729 collection_id: CollectionId,730 token_id: TokenId,731 resource_properties: impl Iterator<Item = Property>,732 ) -> Result<RmrkResourceId, DispatchError> {733 let collection =734 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;735 ensure!(collection.owner == sender, Error::<T>::NoPermission);736737 // Check NFT lock status // todo depends on market, maybe later738 //ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);739740 let sender = T::CrossAccountId::from_sub(sender);741 let budget = budget::Value::new(NESTING_BUDGET);742 let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();743744 let resource_collection_id: CollectionId =745 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;746 let resource_collection =747 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;748749 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them750751 let resource_id = Self::create_nft(752 &sender, // todo owner of the nft?753 &sender,754 &resource_collection,755 resource_properties.chain(756 [757 Self::rmrk_property(PendingResourceAccept, &pending)?,758 Self::rmrk_property(PendingResourceRemoval, &false)?,759 ]760 .into_iter(),761 ),762 )763 .map_err(|err| match err {764 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),765 err => Self::map_common_err_to_proxy(err),766 })?;767768 Ok(resource_id.0)769 }770771 fn resource_remove(772 sender: T::AccountId,773 collection_id: CollectionId,774 nft_id: TokenId,775 resource_id: TokenId,776 ) -> DispatchResult {777 let collection =778 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;779 ensure!(collection.owner == sender, Error::<T>::NoPermission);780781 let resource_collection_id: CollectionId =782 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;783 let resource_collection =784 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;785 ensure!(786 <PalletNft<T>>::token_exists(&resource_collection, resource_id),787 Error::<T>::ResourceDoesntExist788 );789790 let budget = up_data_structs::budget::Value::new(10);791 let topmost_owner =792 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;793794 let sender = T::CrossAccountId::from_sub(sender);795 if topmost_owner == sender {796 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)797 .map_err(Self::map_common_err_to_proxy)?;798 } else {799 <PalletNft<T>>::set_scoped_token_property(800 resource_collection_id,801 resource_id,802 PropertyScope::Rmrk,803 Self::rmrk_property(PendingResourceRemoval, &true)?,804 )?;805 }806807 Ok(())808 }809810 fn change_collection_owner(811 collection_id: CollectionId,812 collection_type: misc::CollectionType,813 sender: T::AccountId,814 new_owner: T::AccountId,815 ) -> DispatchResult {816 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;817 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;818819 let mut collection = collection.into_inner();820821 collection.owner = new_owner;822 collection.save()823 }824825 fn check_collection_owner(826 collection: &NonfungibleHandle<T>,827 account: &T::CrossAccountId,828 ) -> DispatchResult {829 collection830 .check_is_owner(account)831 .map_err(Self::map_common_err_to_proxy)832 }833834 pub fn last_collection_idx() -> RmrkCollectionId {835 <CollectionIndex<T>>::get()836 }837838 pub fn unique_collection_id(839 rmrk_collection_id: RmrkCollectionId,840 ) -> Result<CollectionId, DispatchError> {841 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)842 .map_err(|_| <Error<T>>::CollectionUnknown.into())843 }844845 pub fn rmrk_collection_id(846 unique_collection_id: CollectionId847 ) -> Result<RmrkCollectionId, DispatchError> {848 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)849 .map_err(|_| <Error<T>>::CollectionUnknown.into())850 }851852 pub fn get_nft_collection(853 collection_id: CollectionId,854 ) -> Result<NonfungibleHandle<T>, DispatchError> {855 let collection = <CollectionHandle<T>>::try_get(collection_id)856 .map_err(|_| <Error<T>>::CollectionUnknown)?;857858 match collection.mode {859 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),860 _ => Err(<Error<T>>::CollectionUnknown.into()),861 }862 }863864 pub fn collection_exists(collection_id: CollectionId) -> bool {865 <CollectionHandle<T>>::try_get(collection_id).is_ok()866 }867868 pub fn get_collection_property(869 collection_id: CollectionId,870 key: RmrkProperty,871 ) -> Result<PropertyValue, DispatchError> {872 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)873 .get(&Self::rmrk_property_key(key)?)874 .ok_or(<Error<T>>::CollectionUnknown)?875 .clone();876877 Ok(collection_property)878 }879880 pub fn get_collection_property_decoded<V: Decode>(881 collection_id: CollectionId,882 key: RmrkProperty,883 ) -> Result<V, DispatchError> {884 Self::decode_property(Self::get_collection_property(collection_id, key)?)885 }886887 pub fn get_collection_type(888 collection_id: CollectionId,889 ) -> Result<misc::CollectionType, DispatchError> {890 Self::get_collection_property_decoded(collection_id, CollectionType)891 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())892 }893894 pub fn ensure_collection_type(895 collection_id: CollectionId,896 collection_type: misc::CollectionType,897 ) -> DispatchResult {898 let actual_type = Self::get_collection_type(collection_id)?;899 ensure!(900 actual_type == collection_type,901 <CommonError<T>>::NoPermission902 );903904 Ok(())905 }906907 pub fn get_typed_nft_collection(908 collection_id: CollectionId,909 collection_type: misc::CollectionType,910 ) -> Result<NonfungibleHandle<T>, DispatchError> {911 Self::ensure_collection_type(collection_id, collection_type)?;912913 Self::get_nft_collection(collection_id)914 }915916 pub fn get_typed_nft_collection_mapped(917 rmrk_collection_id: RmrkCollectionId,918 collection_type: misc::CollectionType,919 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {920 let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;921922 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;923924 Ok((collection, unique_collection_id))925 }926927 pub fn get_nft_property(928 collection_id: CollectionId,929 nft_id: TokenId,930 key: RmrkProperty,931 ) -> Result<PropertyValue, DispatchError> {932 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))933 .get(&Self::rmrk_property_key(key)?)934 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?935 .clone();936937 Ok(nft_property)938 }939940 pub fn get_nft_property_decoded<V: Decode>(941 collection_id: CollectionId,942 nft_id: TokenId,943 key: RmrkProperty,944 ) -> Result<V, DispatchError> {945 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)946 }947948 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {949 <TokenData<T>>::contains_key((collection_id, nft_id))950 }951952 pub fn get_nft_type(953 collection_id: CollectionId,954 token_id: TokenId,955 ) -> Result<NftType, DispatchError> {956 Self::get_nft_property_decoded(collection_id, token_id, TokenType)957 .map_err(|_| <Error<T>>::NftTypeEncodeError.into())958 }959960 pub fn ensure_nft_type(961 collection_id: CollectionId,962 token_id: TokenId,963 nft_type: NftType,964 ) -> DispatchResult {965 let actual_type = Self::get_nft_type(collection_id, token_id)?;966 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);967968 Ok(())969 }970971 pub fn ensure_nft_owner(972 collection_id: CollectionId,973 token_id: TokenId,974 possible_owner: &T::CrossAccountId,975 nesting_budget: &dyn budget::Budget976 ) -> DispatchResult {977 let is_owned = <PalletStructure<T>>::check_indirectly_owned(978 possible_owner.clone(),979 collection_id,980 token_id,981 None,982 nesting_budget,983 )?;984985 ensure!(986 is_owned,987 <Error<T>>::NoPermission988 );989990 Ok(())991 }992993 pub fn filter_user_properties<Key, Value, R, Mapper>(994 collection_id: CollectionId,995 token_id: Option<TokenId>,996 filter_keys: Option<Vec<RmrkPropertyKey>>,997 mapper: Mapper,998 ) -> Result<Vec<R>, DispatchError>999 where1000 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1001 Value: Decode + Default,1002 Mapper: Fn(Key, Value) -> R,1003 {1004 filter_keys1005 .map(|keys| {1006 let properties = keys1007 .into_iter()1008 .filter_map(|key| {1009 let key: Key = key.try_into().ok()?;10101011 let value = match token_id {1012 Some(token_id) => Self::get_nft_property_decoded(1013 collection_id,1014 token_id,1015 UserProperty(key.as_ref()),1016 ),1017 None => Self::get_collection_property_decoded(1018 collection_id,1019 UserProperty(key.as_ref()),1020 ),1021 }1022 .ok()?;10231024 Some(mapper(key, value))1025 })1026 .collect();10271028 Ok(properties)1029 })1030 .unwrap_or_else(|| {1031 let properties =1032 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();10331034 Ok(properties)1035 })1036 }10371038 pub fn iterate_user_properties<Key, Value, R, Mapper>(1039 collection_id: CollectionId,1040 token_id: Option<TokenId>,1041 mapper: Mapper,1042 ) -> Result<impl Iterator<Item = R>, DispatchError>1043 where1044 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1045 Value: Decode + Default,1046 Mapper: Fn(Key, Value) -> R,1047 {1048 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;10491050 let properties = match token_id {1051 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1052 None => <PalletCommon<T>>::collection_properties(collection_id),1053 };10541055 let properties = properties.into_iter().filter_map(move |(key, value)| {1056 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;10571058 let key: Key = key.to_vec().try_into().ok()?;1059 let value: Value = value.decode().ok()?;10601061 Some(mapper(key, value))1062 });10631064 Ok(properties)1065 }10661067 fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {1068 map_common_err_to_proxy! {1069 match err {1070 NoPermission => NoPermission,1071 CollectionTokenLimitExceeded => CollectionFullOrLocked,1072 PublicMintingNotAllowed => NoPermission,1073 TokenNotFound => NoAvailableNftId,1074 ApprovedValueTooLow => NoPermission1075 }1076 }1077 }1078}1// 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 }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 )?, // todo possibly add limits to the collection if rmrk warrants them329 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 // todo think about renaming these650 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 // Check NFT lock status // todo depends on market, maybe later751 //ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);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 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them763764 let resource_id = Self::create_nft(765 &sender, // todo owner of the nft?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)? // todo replace with better error?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}pallets/proxy-rmrk-core/src/misc.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/misc.rs
+++ b/pallets/proxy-rmrk-core/src/misc.rs
@@ -2,10 +2,10 @@
use codec::{Encode, Decode, Error};
#[macro_export]
-macro_rules! map_common_err_to_proxy {
- (match $err:ident { $($common_err:ident => $proxy_err:ident),+ }) => {
+macro_rules! map_unique_err_to_proxy {
+ (match $err:ident { $($unique_err_ty:ident :: $unique_err:ident => $proxy_err:ident),+ }) => {
$(
- if $err == <CommonError<T>>::$common_err.into() {
+ if $err == <$unique_err_ty<T>>::$unique_err.into() {
return <Error<T>>::$proxy_err.into()
} else
)+ {
pallets/proxy-rmrk-core/src/property.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/property.rs
+++ b/pallets/proxy-rmrk-core/src/property.rs
@@ -5,11 +5,13 @@
Metadata,
CollectionType,
TokenType,
+ Transferable,
RoyaltyInfo,
Equipped,
ResourceCollection,
ResourcePriorities,
ResourceType,
+ PendingNftAccept,
PendingResourceAccept,
PendingResourceRemoval,
Parts,
@@ -49,13 +51,15 @@
Self::Metadata => key!("metadata"),
Self::CollectionType => key!("collection-type"),
Self::TokenType => key!("token-type"),
+ Self::Transferable => key!("transferable"),
Self::RoyaltyInfo => key!("royalty-info"),
Self::Equipped => key!("equipped"),
Self::ResourceCollection => key!("resource-collection"),
Self::ResourcePriorities => key!("resource-priorities"),
Self::ResourceType => key!("resource-type"),
- Self::PendingResourceAccept => key!("pending-accept"),
- Self::PendingResourceRemoval => key!("pending-removal"),
+ Self::PendingNftAccept => key!("pending-nft-accept"),
+ Self::PendingResourceAccept => key!("pending-resource-accept"),
+ Self::PendingResourceRemoval => key!("pending-resource-removal"),
Self::Parts => key!("parts"),
Self::Base => key!("base"),
Self::Src => key!("src"),
pallets/structure/src/lib.rsdiffbeforeafterboth--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -149,7 +149,7 @@
})
}
- pub fn get_indirect_owner(
+ pub fn get_checked_indirect_owner(
collection: CollectionId,
token: TokenId,
for_nest: Option<(CollectionId, TokenId)>,
@@ -203,7 +203,7 @@
None => user,
};
- Self::get_indirect_owner(
+ Self::get_checked_indirect_owner(
collection,
token,
for_nest,
runtime/common/src/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -188,19 +188,17 @@
None => return Ok(None)
};
- let allowance = pallet_nonfungible::Allowance::<Runtime>::get((collection_id, nft_id));
-
Ok(Some(RmrkInstanceInfo {
owner: owner,
royalty: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::RoyaltyInfo)?,
metadata: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Metadata)?,
equipped: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Equipped)?,
- pending: allowance.is_some(),
+ pending: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)?,
}))
}
fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result<Vec<RmrkNftId>, DispatchError> {
- use pallet_proxy_rmrk_core::misc::CollectionType;
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
use pallet_common::CommonCollectionOperations;
let cross_account_id = CrossAccountId::from_sub(account_id);
@@ -210,15 +208,26 @@
Err(_) => return Ok(Vec::new()),
};
- Ok(
- collection.account_tokens(cross_account_id)
- .into_iter()
- .map(|token| token.0)
- .collect()
- )
+ let tokens = collection.account_tokens(cross_account_id)
+ .into_iter()
+ .filter(|token| {
+ let is_pending = RmrkCore::get_nft_property_decoded(
+ collection_id,
+ *token,
+ RmrkProperty::PendingNftAccept
+ ).unwrap_or(true);
+
+ !is_pending
+ })
+ .map(|token| token.0)
+ .collect();
+
+ Ok(tokens)
}
fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
+ use pallet_proxy_rmrk_core::RmrkProperty;
+
let collection_id = match RmrkCore::unique_collection_id(collection_id) {
Ok(id) => id,
Err(_) => return Ok(Vec::new())
@@ -229,6 +238,16 @@
Ok(
pallet_nonfungible::TokenChildren::<Runtime>::iter_prefix((collection_id, nft_id))
.filter_map(|((child_collection, child_token), _)| {
+ let is_pending = RmrkCore::get_nft_property_decoded(
+ child_collection,
+ child_token,
+ RmrkProperty::PendingNftAccept
+ ).ok()?;
+
+ if is_pending {
+ return None;
+ }
+
let rmrk_child_collection = RmrkCore::rmrk_collection_id(
child_collection
).ok()?;
@@ -398,7 +417,7 @@
Ok(c) => c,
Err(_) => return Ok(Vec::new()),
};
-
+
let parts = collection.collection_tokens()
.into_iter()
.filter_map(|token_id| {