git.delta.rocks / unique-network / refs/commits / dcd5f668cefb

difftreelog

fix(rmrk) sending nft

Daniel Shiposha2022-06-05parent: #7e808da.patch.diff
in: master

5 files changed

modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
--- 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 @@
 			);
 
 			<PalletNft<T>>::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<T::AccountId>,
 			royalty_amount: Option<Permill>,
 			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(_) => <Error<T>>::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<T::AccountId>,
 		) -> 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(<Error<T>>::NonTransferable.into());
+			}
 
+			if Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)? {
+				return Err(<Error<T>>::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 = <PalletStructure<T>>::get_indirect_owner(
+					let target_nft_owner = <PalletStructure<T>>::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
-						// <PalletNft<T>>::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(());
+						<PalletNft<T>>::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);
+
 			<PalletNft<T>>::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)?;
 
 		<PalletNft<T>>::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(_) => <Error<T>>::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 {
 			<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)
-				.map_err(Self::map_common_err_to_proxy)?;
+				.map_err(Self::map_unique_err_to_proxy)?;
 		} else {
 			<PalletNft<T>>::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
 			}
 		}
 	}
modifiedpallets/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
         )+ {
modifiedpallets/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"),
modifiedpallets/structure/src/lib.rsdiffbeforeafterboth
before · pallets/structure/src/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]23use pallet_common::CommonCollectionOperations;4use sp_std::collections::btree_set::BTreeSet;56use frame_support::dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo};7use frame_support::fail;8pub use pallet::*;9use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};10use up_data_structs::{CollectionId, TokenId, mapping::TokenAddressMapping, budget::Budget};1112#[cfg(feature = "runtime-benchmarks")]13pub mod benchmarking;14pub mod weights;1516pub type SelfWeightOf<T> = <T as crate::Config>::WeightInfo;1718#[frame_support::pallet]19pub mod pallet {20	use frame_support::Parameter;21	use frame_support::dispatch::{GetDispatchInfo, UnfilteredDispatchable};22	use frame_support::pallet_prelude::*;2324	use super::*;2526	#[pallet::error]27	pub enum Error<T> {28		/// While searched for owner, got already checked account29		OuroborosDetected,30		/// While searched for owner, encountered depth limit31		DepthLimit,32		/// While iterating over children, encountered breadth limit33		BreadthLimit,34		/// While searched for owner, found token owner by not-yet-existing token35		TokenNotFound,36	}3738	#[pallet::event]39	pub enum Event<T> {40		/// Executed call on behalf of token41		Executed(DispatchResult),42	}4344	#[pallet::config]45	pub trait Config: frame_system::Config + pallet_common::Config {46		type WeightInfo: weights::WeightInfo;47		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;48		type Call: Parameter + UnfilteredDispatchable<Origin = Self::Origin> + GetDispatchInfo;49	}5051	#[pallet::pallet]52	pub struct Pallet<T>(_);5354	#[pallet::call]55	impl<T: Config> Pallet<T> {56		// #[pallet::weight({57		// 	let dispatch_info = call.get_dispatch_info();5859		// 	(60		// 		dispatch_info.weight61		// 			// Cost of dereferencing parent62		// 			.saturating_add(T::DbWeight::get().reads(2 * *max_depth as Weight))63		// 			.saturating_add(4000 * *max_depth as Weight),64		// 		dispatch_info.class)65		// })]66		// pub fn execute(67		// 	origin: OriginFor<T>,68		// 	call: Box<<T as Config>::Call>,69		// 	max_depth: u32,70		// ) -> DispatchResult {71	}72}7374#[derive(PartialEq)]75pub enum Parent<CrossAccountId> {76	/// Token owned by normal account77	User(CrossAccountId),78	/// Passed token not found79	TokenNotFound,80	/// Token owner is another token (target token still may not exist)81	Token(CollectionId, TokenId),82}8384impl<T: Config> Pallet<T> {85	pub fn find_parent(86		collection: CollectionId,87		token: TokenId,88	) -> Result<Parent<T::CrossAccountId>, DispatchError> {89		// TODO: Reduce cost by not reading collection config90		let handle = match CollectionHandle::try_get(collection) {91			Ok(v) => v,92			Err(_) => return Ok(Parent::TokenNotFound),93		};94		let handle = T::CollectionDispatch::dispatch(handle);95		let handle = handle.as_dyn();9697		Ok(match handle.token_owner(token) {98			Some(owner) => match T::CrossTokenAddressMapping::address_to_token(&owner) {99				Some((collection, token)) => Parent::Token(collection, token),100				None => Parent::User(owner),101			},102			None => Parent::TokenNotFound,103		})104	}105106	pub fn parent_chain(107		mut collection: CollectionId,108		mut token: TokenId,109	) -> impl Iterator<Item = Result<Parent<T::CrossAccountId>, DispatchError>> {110		let mut finished = false;111		let mut visited = BTreeSet::new();112		visited.insert((collection, token));113		core::iter::from_fn(move || {114			if finished {115				return None;116			}117			let parent = Self::find_parent(collection, token);118			match parent {119				Ok(Parent::Token(new_collection, new_token)) => {120					collection = new_collection;121					token = new_token;122					if !visited.insert((new_collection, new_token)) {123						finished = true;124						return Some(Err(<Error<T>>::OuroborosDetected.into()));125					}126				}127				_ => finished = true,128			}129			Some(parent as Result<_, DispatchError>)130		})131	}132133	/// Try to dereference address, until finding top level owner134	///135	/// May return token address if parent token not yet exists136	pub fn find_topmost_owner(137		collection: CollectionId,138		token: TokenId,139		budget: &dyn Budget,140	) -> Result<T::CrossAccountId, DispatchError> {141		let owner = Self::parent_chain(collection, token)142			.take_while(|_| budget.consume())143			.find(|p| matches!(p, Ok(Parent::User(_) | Parent::TokenNotFound)))144			.ok_or(<Error<T>>::DepthLimit)??;145146		Ok(match owner {147			Parent::User(v) => v,148			_ => fail!(<Error<T>>::TokenNotFound),149		})150	}151152	pub fn get_indirect_owner(153		collection: CollectionId,154		token: TokenId,155		for_nest: Option<(CollectionId, TokenId)>,156		budget: &dyn Budget,157	) -> Result<T::CrossAccountId, DispatchError> {158		// Tried to nest token in itself159		if Some((collection, token)) == for_nest {160			return Err(<Error<T>>::OuroborosDetected.into());161		}162163		for parent in Self::parent_chain(collection, token).take_while(|_| budget.consume()) {164			match parent? {165				// Tried to nest token in chain, which has this token as one of parents166				Parent::Token(collection, token) if Some((collection, token)) == for_nest => {167					return Err(<Error<T>>::OuroborosDetected.into())168				}169				// Token is owned by other user170				Parent::User(user) => return Ok(user),171				Parent::TokenNotFound => return Err(<Error<T>>::TokenNotFound.into()),172				// Continue parent chain173				Parent::Token(_, _) => {}174			}175		}176177		Err(<Error<T>>::DepthLimit.into())178	}179180	pub fn burn_item_recursively(181		from: T::CrossAccountId,182		collection: CollectionId,183		token: TokenId,184		self_budget: &dyn Budget,185		breadth_budget: &dyn Budget,186	) -> DispatchResultWithPostInfo {187		let handle = <CollectionHandle<T>>::try_get(collection)?;188		let dispatch = T::CollectionDispatch::dispatch(handle);189		let dispatch = dispatch.as_dyn();190		dispatch.burn_item_recursively(from.clone(), token, self_budget, breadth_budget)191	}192193	/// Check if token indirectly owned by specified user194	pub fn check_indirectly_owned(195		user: T::CrossAccountId,196		collection: CollectionId,197		token: TokenId,198		for_nest: Option<(CollectionId, TokenId)>,199		budget: &dyn Budget,200	) -> Result<bool, DispatchError> {201		let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {202			Some((collection, token)) => Self::find_topmost_owner(collection, token, budget)?,203			None => user,204		};205206		Self::get_indirect_owner(207			collection,208			token,209			for_nest,210			budget211		).map(|indirect_owner| indirect_owner == target_parent)212	}213214	pub fn check_nesting(215		from: T::CrossAccountId,216		under: &T::CrossAccountId,217		collection_id: CollectionId,218		token_id: TokenId,219		nesting_budget: &dyn Budget,220	) -> DispatchResult {221		Self::try_exec_if_owner_is_valid_nft(under, |collection, parent_id| {222			collection.check_nesting(from, (collection_id, token_id), parent_id, nesting_budget)223		})224	}225226	pub fn nest_if_sent_to_token(227		from: T::CrossAccountId,228		under: &T::CrossAccountId,229		collection_id: CollectionId,230		token_id: TokenId,231		nesting_budget: &dyn Budget,232	) -> DispatchResult {233		Self::try_exec_if_owner_is_valid_nft(under, |collection, parent_id| {234			collection.check_nesting(from, (collection_id, token_id), parent_id, nesting_budget)?;235236			collection.nest(parent_id, (collection_id, token_id));237238			Ok(())239		})240	}241242	pub fn nest_if_sent_to_token_unchecked(243		owner: &T::CrossAccountId,244		collection_id: CollectionId,245		token_id: TokenId,246	) {247		Self::exec_if_owner_is_valid_nft(owner, |collection, parent_id| {248			collection.nest(parent_id, (collection_id, token_id))249		});250	}251252	pub fn unnest_if_nested(253		owner: &T::CrossAccountId,254		collection_id: CollectionId,255		token_id: TokenId,256	) {257		Self::exec_if_owner_is_valid_nft(owner, |collection, parent_id| {258			collection.unnest(parent_id, (collection_id, token_id))259		});260	}261262	fn exec_if_owner_is_valid_nft(263		account: &T::CrossAccountId,264		action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId),265	) {266		Self::try_exec_if_owner_is_valid_nft(account, |collection, id| {267			action(collection, id);268			Ok(())269		})270		.unwrap();271	}272273	fn try_exec_if_owner_is_valid_nft(274		account: &T::CrossAccountId,275		action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId) -> DispatchResult,276	) -> DispatchResult {277		let account = T::CrossTokenAddressMapping::address_to_token(account);278279		if account.is_none() {280			return Ok(());281		}282283		let account = account.unwrap();284285		let handle = <CollectionHandle<T>>::try_get(account.0);286287		if handle.is_err() {288			return Ok(());289		}290291		let handle = handle.unwrap();292293		let dispatch = T::CollectionDispatch::dispatch(handle);294		let dispatch = dispatch.as_dyn();295296		action(dispatch, account.1)297	}298}
modifiedruntime/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| {