--- a/pallets/proxy-rmrk-core/src/lib.rs +++ b/pallets/proxy-rmrk-core/src/lib.rs @@ -25,7 +25,7 @@ Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations, }; use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData}; -use pallet_structure::Pallet as PalletStructure; +use pallet_structure::{Pallet as PalletStructure, Error as StructureError}; use pallet_evm::account::CrossAccountId; use core::convert::AsRef; @@ -128,8 +128,10 @@ NoAvailableNftId, CollectionUnknown, NoPermission, + NonTransferable, CollectionFullOrLocked, ResourceDoesntExist, + CannotSendToDescendentOrSelf, } #[pallet::call] @@ -207,7 +209,7 @@ ); >::destroy_collection(collection, &cross_sender) - .map_err(Self::map_common_err_to_proxy)?; + .map_err(Self::map_unique_err_to_proxy)?; Self::deposit_event(Event::CollectionDestroyed { issuer: sender, @@ -283,6 +285,7 @@ recipient: Option, royalty_amount: Option, metadata: RmrkString, + transferable: bool, ) -> DispatchResult { let sender = ensure_signed(origin)?; let sender = T::CrossAccountId::from_sub(sender); @@ -304,6 +307,8 @@ &collection, [ Self::rmrk_property(TokenType, &NftType::Regular)?, + Self::rmrk_property(Transferable, &transferable)?, + Self::rmrk_property(PendingNftAccept, &false)?, Self::rmrk_property(RoyaltyInfo, &royalty_info)?, Self::rmrk_property(Metadata, &metadata)?, Self::rmrk_property(Equipped, &false)?, @@ -327,7 +332,7 @@ ) .map_err(|err| match err { DispatchError::Arithmetic(_) => >::NoAvailableNftId.into(), - err => Self::map_common_err_to_proxy(err), + err => Self::map_unique_err_to_proxy(err), })?; Self::deposit_event(Event::NftMinted { @@ -373,7 +378,7 @@ new_owner: RmrkAccountIdOrCollectionNftTuple, ) -> DispatchResult { let sender = ensure_signed(origin.clone())?; - let cross_sender = T::CrossAccountId::from_sub(sender.clone()); + let cross_sender = T::CrossAccountId::from_sub(sender); let collection_id = Self::unique_collection_id(rmrk_collection_id)?; let nft_id = rmrk_nft_id.into(); @@ -388,8 +393,14 @@ misc::CollectionType::Regular, )?; - let budget = budget::Value::new(NESTING_BUDGET); + if !Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)? { + return Err(>::NonTransferable.into()); + } + if Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)? { + return Err(>::NoPermission.into()); + } + let target_owner; match new_owner { @@ -399,43 +410,45 @@ RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => { let target_collection_id = Self::unique_collection_id(target_collection_id)?; - target_owner = T::CrossTokenAddressMapping::token_to_address( - target_collection_id, - target_nft_id.into(), - ); + let target_nft_budget = budget::Value::new(NESTING_BUDGET); - let spender = >::get_indirect_owner( + let target_nft_owner = >::get_checked_indirect_owner( target_collection_id, target_nft_id.into(), Some((collection_id, nft_id)), - &budget, - )?; + &target_nft_budget, + ).map_err(Self::map_unique_err_to_proxy)?; - let is_approval_required = cross_sender != spender; + let is_approval_required = cross_sender != target_nft_owner; if is_approval_required { - // FIXME - // >::set_allowance( - // &collection, - // &cross_sender, - // nft_id, - // Some(&spender), - // &budget - // ).map_err(Self::map_common_err_to_proxy)?; + target_owner = target_nft_owner; - return Ok(()); + >::set_scoped_token_property( + collection.id, + nft_id, + PropertyScope::Rmrk, + Self::rmrk_property(PendingNftAccept, &is_approval_required)?, + )?; + } else { + target_owner = T::CrossTokenAddressMapping::token_to_address( + target_collection_id, + target_nft_id.into(), + ); } } } + let src_nft_budget = budget::Value::new(NESTING_BUDGET); + >::transfer_from( &collection, &cross_sender, &from, &target_owner, nft_id, - &budget - ).map_err(Self::map_common_err_to_proxy)?; + &src_nft_budget + ).map_err(Self::map_unique_err_to_proxy)?; Ok(()) } @@ -719,7 +732,7 @@ let collection = Self::get_typed_nft_collection(collection_id, collection_type)?; >::burn(&collection, &sender, token_id) - .map_err(Self::map_common_err_to_proxy)?; + .map_err(Self::map_unique_err_to_proxy)?; Ok(()) } @@ -762,7 +775,7 @@ ) .map_err(|err| match err { DispatchError::Arithmetic(_) => >::NoAvailableNftId.into(), - err => Self::map_common_err_to_proxy(err), + err => Self::map_unique_err_to_proxy(err), })?; Ok(resource_id.0) @@ -794,7 +807,7 @@ let sender = T::CrossAccountId::from_sub(sender); if topmost_owner == sender { >::burn(&resource_collection, &sender, resource_id) - .map_err(Self::map_common_err_to_proxy)?; + .map_err(Self::map_unique_err_to_proxy)?; } else { >::set_scoped_token_property( resource_collection_id, @@ -828,7 +841,7 @@ ) -> DispatchResult { collection .check_is_owner(account) - .map_err(Self::map_common_err_to_proxy) + .map_err(Self::map_unique_err_to_proxy) } pub fn last_collection_idx() -> RmrkCollectionId { @@ -1064,14 +1077,16 @@ Ok(properties) } - fn map_common_err_to_proxy(err: DispatchError) -> DispatchError { - map_common_err_to_proxy! { + fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError { + map_unique_err_to_proxy! { match err { - NoPermission => NoPermission, - CollectionTokenLimitExceeded => CollectionFullOrLocked, - PublicMintingNotAllowed => NoPermission, - TokenNotFound => NoAvailableNftId, - ApprovedValueTooLow => NoPermission + CommonError::NoPermission => NoPermission, + CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked, + CommonError::PublicMintingNotAllowed => NoPermission, + CommonError::TokenNotFound => NoAvailableNftId, + CommonError::ApprovedValueTooLow => NoPermission, + StructureError::TokenNotFound => NoAvailableNftId, + StructureError::OuroborosDetected => CannotSendToDescendentOrSelf } } } --- 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 == >::$common_err.into() { + if $err == <$unique_err_ty>::$unique_err.into() { return >::$proxy_err.into() } else )+ { --- 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"), --- 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, --- 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::::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, 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, 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::::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| {