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}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| {