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

difftreelog

fix find_parent

Daniel Shiposha2023-01-19parent: #030bc0d.patch.diff
in: master

8 files changed

modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -155,6 +155,11 @@
 }
 
 impl<T: Config> CollectionHandle<T> {
+	/// Get the mode of the collection: NFT/FT/RFT.
+	pub fn mode(&self) -> CollectionMode {
+		self.mode
+	}
+
 	/// Same as [CollectionHandle::new] but with an explicit gas limit.
 	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {
 		<CollectionById<T>>::get(id).map(|collection| Self {
@@ -1872,6 +1877,9 @@
 /// It wraps methods in Fungible, Nonfungible and Refungible pallets
 /// and adds weight info.
 pub trait CommonCollectionOperations<T: Config> {
+	/// Get the mode of the collection: NFT/FT/RFT.
+	fn mode(&self) -> CollectionMode;
+
 	/// Create token.
 	///
 	/// * `sender` - The user who mint the token and pays for the transaction.
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -125,6 +125,10 @@
 /// Implementation of `CommonCollectionOperations` for `FungibleHandle`. It wraps FungibleHandle Pallete
 /// methods and adds weight info.
 impl<T: Config> CommonCollectionOperations<T> for FungibleHandle<T> {
+	fn mode(&self) -> up_data_structs::CollectionMode {
+		self.0.mode()
+	}
+
 	fn create_item(
 		&self,
 		sender: T::CrossAccountId,
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
after · pallets/nonfungible/src/common.rs
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/>.1617use core::marker::PhantomData;1819use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};20use up_data_structs::{21	TokenId, CreateItemExData, CollectionId, budget::Budget, Property, PropertyKey,22	PropertyKeyPermission, PropertyValue,23};24use pallet_common::{25	CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight,26	weights::WeightInfo as _,27};28use sp_runtime::DispatchError;29use sp_std::{vec::Vec, vec};3031use crate::{32	AccountBalance, Allowance, Config, CreateItemData, Error, NonfungibleHandle, Owned, Pallet,33	SelfWeightOf, TokenData, weights::WeightInfo, TokensMinted,34};3536pub struct CommonWeights<T: Config>(PhantomData<T>);37impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {38	fn create_item() -> Weight {39		<SelfWeightOf<T>>::create_item()40	}4142	fn create_multiple_items_ex(data: &CreateItemExData<T::CrossAccountId>) -> Weight {43		match data {44			CreateItemExData::NFT(t) => {45				<SelfWeightOf<T>>::create_multiple_items_ex(t.len() as u32)46					+ t.iter()47						.filter_map(|t| {48							if t.properties.len() > 0 {49								Some(Self::set_token_properties(t.properties.len() as u32))50							} else {51								None52							}53						})54						.fold(Weight::zero(), |a, b| a.saturating_add(b))55			}56			_ => Weight::zero(),57		}58	}5960	fn create_multiple_items(data: &[up_data_structs::CreateItemData]) -> Weight {61		<SelfWeightOf<T>>::create_multiple_items(data.len() as u32)62			+ data63				.iter()64				.filter_map(|t| match t {65					up_data_structs::CreateItemData::NFT(n) if n.properties.len() > 0 => {66						Some(Self::set_token_properties(n.properties.len() as u32))67					}68					_ => None,69				})70				.fold(Weight::zero(), |a, b| a.saturating_add(b))71	}7273	fn burn_item() -> Weight {74		<SelfWeightOf<T>>::burn_item()75	}7677	fn set_collection_properties(amount: u32) -> Weight {78		<pallet_common::SelfWeightOf<T>>::set_collection_properties(amount)79	}8081	fn delete_collection_properties(amount: u32) -> Weight {82		<pallet_common::SelfWeightOf<T>>::delete_collection_properties(amount)83	}8485	fn set_token_properties(amount: u32) -> Weight {86		<SelfWeightOf<T>>::set_token_properties(amount)87	}8889	fn delete_token_properties(amount: u32) -> Weight {90		<SelfWeightOf<T>>::delete_token_properties(amount)91	}9293	fn set_token_property_permissions(amount: u32) -> Weight {94		<SelfWeightOf<T>>::set_token_property_permissions(amount)95	}9697	fn transfer() -> Weight {98		<SelfWeightOf<T>>::transfer()99	}100101	fn approve() -> Weight {102		<SelfWeightOf<T>>::approve()103	}104105	fn approve_from() -> Weight {106		<SelfWeightOf<T>>::approve_from()107	}108109	fn transfer_from() -> Weight {110		<SelfWeightOf<T>>::transfer_from()111	}112113	fn burn_from() -> Weight {114		<SelfWeightOf<T>>::burn_from()115	}116117	fn burn_recursively_self_raw() -> Weight {118		<SelfWeightOf<T>>::burn_recursively_self_raw()119	}120121	fn burn_recursively_breadth_raw(amount: u32) -> Weight {122		<SelfWeightOf<T>>::burn_recursively_breadth_plus_self_plus_self_per_each_raw(amount)123			.saturating_sub(Self::burn_recursively_self_raw().saturating_mul(amount as u64 + 1))124	}125126	fn token_owner() -> Weight {127		<SelfWeightOf<T>>::token_owner()128	}129130	fn set_allowance_for_all() -> Weight {131		<SelfWeightOf<T>>::set_allowance_for_all()132	}133134	fn force_repair_item() -> Weight {135		<SelfWeightOf<T>>::repair_item()136	}137}138139fn map_create_data<T: Config>(140	data: up_data_structs::CreateItemData,141	to: &T::CrossAccountId,142) -> Result<CreateItemData<T>, DispatchError> {143	match data {144		up_data_structs::CreateItemData::NFT(data) => Ok(CreateItemData::<T> {145			properties: data.properties,146			owner: to.clone(),147		}),148		_ => fail!(<Error<T>>::NotNonfungibleDataUsedToMintFungibleCollectionToken),149	}150}151152/// Implementation of `CommonCollectionOperations` for `NonfungibleHandle`. It wraps Nonfungible Pallete153/// methods and adds weight info.154impl<T: Config> CommonCollectionOperations<T> for NonfungibleHandle<T> {155	fn mode(&self) -> up_data_structs::CollectionMode {156		self.0.mode()157	}158159	fn create_item(160		&self,161		sender: T::CrossAccountId,162		to: T::CrossAccountId,163		data: up_data_structs::CreateItemData,164		nesting_budget: &dyn Budget,165	) -> DispatchResultWithPostInfo {166		with_weight(167			<Pallet<T>>::create_item(168				self,169				&sender,170				map_create_data::<T>(data, &to)?,171				nesting_budget,172			),173			<CommonWeights<T>>::create_item(),174		)175	}176177	fn create_multiple_items(178		&self,179		sender: T::CrossAccountId,180		to: T::CrossAccountId,181		data: Vec<up_data_structs::CreateItemData>,182		nesting_budget: &dyn Budget,183	) -> DispatchResultWithPostInfo {184		let weight = <CommonWeights<T>>::create_multiple_items(&data);185		let data = data186			.into_iter()187			.map(|d| map_create_data::<T>(d, &to))188			.collect::<Result<Vec<_>, DispatchError>>()?;189190		with_weight(191			<Pallet<T>>::create_multiple_items(self, &sender, data, nesting_budget),192			weight,193		)194	}195196	fn create_multiple_items_ex(197		&self,198		sender: <T>::CrossAccountId,199		data: up_data_structs::CreateItemExData<<T>::CrossAccountId>,200		nesting_budget: &dyn Budget,201	) -> DispatchResultWithPostInfo {202		let weight = <CommonWeights<T>>::create_multiple_items_ex(&data);203		let data = match data {204			up_data_structs::CreateItemExData::NFT(nft) => nft,205			_ => fail!(Error::<T>::NotNonfungibleDataUsedToMintFungibleCollectionToken),206		};207208		with_weight(209			<Pallet<T>>::create_multiple_items(self, &sender, data.into_inner(), nesting_budget),210			weight,211		)212	}213214	fn set_collection_properties(215		&self,216		sender: T::CrossAccountId,217		properties: Vec<Property>,218	) -> DispatchResultWithPostInfo {219		let weight = <CommonWeights<T>>::set_collection_properties(properties.len() as u32);220221		with_weight(222			<Pallet<T>>::set_collection_properties(self, &sender, properties),223			weight,224		)225	}226227	fn delete_collection_properties(228		&self,229		sender: &T::CrossAccountId,230		property_keys: Vec<PropertyKey>,231	) -> DispatchResultWithPostInfo {232		let weight = <CommonWeights<T>>::delete_collection_properties(property_keys.len() as u32);233234		with_weight(235			<Pallet<T>>::delete_collection_properties(self, sender, property_keys),236			weight,237		)238	}239240	fn set_token_properties(241		&self,242		sender: T::CrossAccountId,243		token_id: TokenId,244		properties: Vec<Property>,245		nesting_budget: &dyn Budget,246	) -> DispatchResultWithPostInfo {247		let weight = <CommonWeights<T>>::set_token_properties(properties.len() as u32);248249		with_weight(250			<Pallet<T>>::set_token_properties(251				self,252				&sender,253				token_id,254				properties.into_iter(),255				false,256				nesting_budget,257			),258			weight,259		)260	}261262	fn delete_token_properties(263		&self,264		sender: T::CrossAccountId,265		token_id: TokenId,266		property_keys: Vec<PropertyKey>,267		nesting_budget: &dyn Budget,268	) -> DispatchResultWithPostInfo {269		let weight = <CommonWeights<T>>::delete_token_properties(property_keys.len() as u32);270271		with_weight(272			<Pallet<T>>::delete_token_properties(273				self,274				&sender,275				token_id,276				property_keys.into_iter(),277				nesting_budget,278			),279			weight,280		)281	}282283	fn set_token_property_permissions(284		&self,285		sender: &T::CrossAccountId,286		property_permissions: Vec<PropertyKeyPermission>,287	) -> DispatchResultWithPostInfo {288		let weight =289			<CommonWeights<T>>::set_token_property_permissions(property_permissions.len() as u32);290291		with_weight(292			<Pallet<T>>::set_token_property_permissions(self, sender, property_permissions),293			weight,294		)295	}296297	fn burn_item(298		&self,299		sender: T::CrossAccountId,300		token: TokenId,301		amount: u128,302	) -> DispatchResultWithPostInfo {303		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);304		if amount == 1 {305			with_weight(306				<Pallet<T>>::burn(self, &sender, token),307				<CommonWeights<T>>::burn_item(),308			)309		} else {310			<Pallet<T>>::check_token_immediate_ownership(self, token, &sender)?;311			Ok(().into())312		}313	}314315	fn burn_item_recursively(316		&self,317		sender: T::CrossAccountId,318		token: TokenId,319		self_budget: &dyn Budget,320		breadth_budget: &dyn Budget,321	) -> DispatchResultWithPostInfo {322		<Pallet<T>>::burn_recursively(self, &sender, token, self_budget, breadth_budget)323	}324325	fn transfer(326		&self,327		from: T::CrossAccountId,328		to: T::CrossAccountId,329		token: TokenId,330		amount: u128,331		nesting_budget: &dyn Budget,332	) -> DispatchResultWithPostInfo {333		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);334		if amount == 1 {335			with_weight(336				<Pallet<T>>::transfer(self, &from, &to, token, nesting_budget),337				<CommonWeights<T>>::transfer(),338			)339		} else {340			<Pallet<T>>::check_token_immediate_ownership(self, token, &from)?;341			Ok(().into())342		}343	}344345	fn approve(346		&self,347		sender: T::CrossAccountId,348		spender: T::CrossAccountId,349		token: TokenId,350		amount: u128,351	) -> DispatchResultWithPostInfo {352		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);353354		with_weight(355			if amount == 1 {356				<Pallet<T>>::set_allowance(self, &sender, token, Some(&spender))357			} else {358				<Pallet<T>>::set_allowance(self, &sender, token, None)359			},360			<CommonWeights<T>>::approve(),361		)362	}363364	fn approve_from(365		&self,366		sender: T::CrossAccountId,367		from: T::CrossAccountId,368		to: T::CrossAccountId,369		token: TokenId,370		amount: u128,371	) -> DispatchResultWithPostInfo {372		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);373374		with_weight(375			if amount == 1 {376				<Pallet<T>>::set_allowance_from(self, &sender, &from, token, Some(&to))377			} else {378				<Pallet<T>>::set_allowance_from(self, &sender, &from, token, None)379			},380			<CommonWeights<T>>::approve_from(),381		)382	}383384	fn transfer_from(385		&self,386		sender: T::CrossAccountId,387		from: T::CrossAccountId,388		to: T::CrossAccountId,389		token: TokenId,390		amount: u128,391		nesting_budget: &dyn Budget,392	) -> DispatchResultWithPostInfo {393		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);394395		if amount == 1 {396			with_weight(397				<Pallet<T>>::transfer_from(self, &sender, &from, &to, token, nesting_budget),398				<CommonWeights<T>>::transfer_from(),399			)400		} else {401			<Pallet<T>>::check_allowed(self, &sender, &from, token, nesting_budget)?;402403			Ok(().into())404		}405	}406407	fn burn_from(408		&self,409		sender: T::CrossAccountId,410		from: T::CrossAccountId,411		token: TokenId,412		amount: u128,413		nesting_budget: &dyn Budget,414	) -> DispatchResultWithPostInfo {415		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);416417		if amount == 1 {418			with_weight(419				<Pallet<T>>::burn_from(self, &sender, &from, token, nesting_budget),420				<CommonWeights<T>>::burn_from(),421			)422		} else {423			<Pallet<T>>::check_allowed(self, &sender, &from, token, nesting_budget)?;424425			Ok(().into())426		}427	}428429	fn check_nesting(430		&self,431		sender: T::CrossAccountId,432		from: (CollectionId, TokenId),433		under: TokenId,434		nesting_budget: &dyn Budget,435	) -> sp_runtime::DispatchResult {436		<Pallet<T>>::check_nesting(self, sender, from, under, nesting_budget)437	}438439	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId)) {440		<Pallet<T>>::nest((self.id, under), to_nest);441	}442443	fn unnest(&self, under: TokenId, to_unnest: (CollectionId, TokenId)) {444		<Pallet<T>>::unnest((self.id, under), to_unnest);445	}446447	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {448		<Owned<T>>::iter_prefix((self.id, account))449			.map(|(id, _)| id)450			.collect()451	}452453	fn collection_tokens(&self) -> Vec<TokenId> {454		<TokenData<T>>::iter_prefix((self.id,))455			.map(|(id, _)| id)456			.collect()457	}458459	fn token_exists(&self, token: TokenId) -> bool {460		<Pallet<T>>::token_exists(self, token)461	}462463	fn last_token_id(&self) -> TokenId {464		TokenId(<TokensMinted<T>>::get(self.id))465	}466467	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId> {468		<TokenData<T>>::get((self.id, token)).map(|t| t.owner)469	}470471	/// Returns token owners.472	fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId> {473		self.token_owner(token).map_or_else(|| vec![], |t| vec![t])474	}475476	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue> {477		<Pallet<T>>::token_properties((self.id, token_id))478			.get(key)479			.cloned()480	}481482	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property> {483		let properties = <Pallet<T>>::token_properties((self.id, token_id));484485		keys.map(|keys| {486			keys.into_iter()487				.filter_map(|key| {488					properties.get(&key).map(|value| Property {489						key,490						value: value.clone(),491					})492				})493				.collect()494		})495		.unwrap_or_else(|| {496			properties497				.into_iter()498				.map(|(key, value)| Property { key, value })499				.collect()500		})501	}502503	fn total_supply(&self) -> u32 {504		<Pallet<T>>::total_supply(self)505	}506507	fn account_balance(&self, account: T::CrossAccountId) -> u32 {508		<AccountBalance<T>>::get((self.id, account))509	}510511	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128 {512		if <TokenData<T>>::get((self.id, token))513			.map(|a| a.owner == account)514			.unwrap_or(false)515		{516			1517		} else {518			0519		}520	}521522	fn allowance(523		&self,524		sender: T::CrossAccountId,525		spender: T::CrossAccountId,526		token: TokenId,527	) -> u128 {528		if <TokenData<T>>::get((self.id, token))529			.map(|a| a.owner != sender)530			.unwrap_or(true)531		{532			0533		} else if <Allowance<T>>::get((self.id, token)) == Some(spender) {534			1535		} else {536			0537		}538	}539540	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>> {541		None542	}543544	fn total_pieces(&self, token: TokenId) -> Option<u128> {545		if <TokenData<T>>::contains_key((self.id, token)) {546			Some(1)547		} else {548			None549		}550	}551552	fn set_allowance_for_all(553		&self,554		owner: T::CrossAccountId,555		operator: T::CrossAccountId,556		approve: bool,557	) -> DispatchResultWithPostInfo {558		with_weight(559			<Pallet<T>>::set_allowance_for_all(self, &owner, &operator, approve),560			<CommonWeights<T>>::set_allowance_for_all(),561		)562	}563564	fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool {565		<Pallet<T>>::allowance_for_all(self, &owner, &operator)566	}567568	fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo {569		with_weight(570			<Pallet<T>>::repair_item(self, token),571			<CommonWeights<T>>::force_repair_item(),572		)573	}574}
modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/lib.rs
+++ b/pallets/proxy-rmrk-core/src/lib.rs
@@ -741,7 +741,8 @@
 						Some((collection_id, nft_id)),
 						&target_nft_budget,
 					)
-					.map_err(Self::map_unique_err_to_proxy)?;
+					.map_err(Self::map_unique_err_to_proxy)?
+					.ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
 
 					approval_required = cross_sender != target_nft_owner;
 
@@ -989,7 +990,8 @@
 
 			let nft_owner =
 				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)
-					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;
+					.map_err(|_| <Error<T>>::ResourceDoesntExist)?
+					.ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
 
 			Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {
 				ensure!(res.pending, <Error<T>>::ResourceNotPending);
@@ -1044,7 +1046,8 @@
 
 			let nft_owner =
 				<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)
-					.map_err(|_| <Error<T>>::ResourceDoesntExist)?;
+					.map_err(|_| <Error<T>>::ResourceDoesntExist)?
+					.ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
 
 			ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);
 
@@ -1666,7 +1669,8 @@
 		let budget = budget::Value::new(NESTING_BUDGET);
 
 		let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)
-			.map_err(Self::map_unique_err_to_proxy)?;
+			.map_err(Self::map_unique_err_to_proxy)?
+			.ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
 
 		let pending = sender != nft_owner;
 
@@ -1720,7 +1724,8 @@
 
 		let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);
 		let topmost_owner =
-			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;
+			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?
+				.ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
 
 		let sender = T::CrossAccountId::from_sub(sender);
 		if topmost_owner == sender {
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -186,6 +186,10 @@
 /// Implementation of `CommonCollectionOperations` for `RefungibleHandle`. It wraps Refungible Pallete
 /// methods and adds weight info.
 impl<T: Config> CommonCollectionOperations<T> for RefungibleHandle<T> {
+	fn mode(&self) -> up_data_structs::CollectionMode {
+		self.0.mode()
+	}
+
 	fn create_item(
 		&self,
 		sender: T::CrossAccountId,
modifiedpallets/structure/src/lib.rsdiffbeforeafterboth
--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -61,6 +61,7 @@
 use frame_support::fail;
 pub use pallet::*;
 use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};
+use up_data_structs::CollectionMode;
 use up_data_structs::{CollectionId, TokenId, mapping::TokenAddressMapping, budget::Budget};
 
 #[cfg(feature = "runtime-benchmarks")]
@@ -135,6 +136,8 @@
 	User(CrossAccountId),
 	/// Could not find the token provided as the owner.
 	TokenNotFound,
+	/// Nested token has multiple owners.
+	MultipleOwners,
 	/// Token owner is another token (still, the target token may not exist).
 	Token(CollectionId, TokenId),
 }
@@ -163,6 +166,10 @@
 				Some((collection, token)) => Parent::Token(collection, token),
 				None => Parent::User(owner),
 			},
+			None if handle.mode() == CollectionMode::ReFungible => handle
+				.total_pieces(token)
+				.map(|_| Parent::MultipleOwners)
+				.unwrap_or(Parent::TokenNotFound),
 			None => Parent::TokenNotFound,
 		})
 	}
@@ -203,19 +210,27 @@
 	///
 	/// May return token address if parent token not yet exists
 	///
+	/// Returns `None` if the token has multiple owners.
+	///
 	/// - `budget`: Limit for searching parents in depth.
 	pub fn find_topmost_owner(
 		collection: CollectionId,
 		token: TokenId,
 		budget: &dyn Budget,
-	) -> Result<T::CrossAccountId, DispatchError> {
+	) -> Result<Option<T::CrossAccountId>, DispatchError> {
 		let owner = Self::parent_chain(collection, token)
 			.take_while(|_| budget.consume())
-			.find(|p| matches!(p, Ok(Parent::User(_) | Parent::TokenNotFound)))
+			.find(|p| {
+				matches!(
+					p,
+					Ok(Parent::User(_) | Parent::TokenNotFound | Parent::MultipleOwners)
+				)
+			})
 			.ok_or(<Error<T>>::DepthLimit)??;
 
 		Ok(match owner {
-			Parent::User(v) => v,
+			Parent::User(v) => Some(v),
+			Parent::MultipleOwners => None,
 			_ => fail!(<Error<T>>::TokenNotFound),
 		})
 	}
@@ -223,13 +238,15 @@
 	/// Find the topmost parent and check that assigning `for_nest` token as a child for
 	/// `token` wouldn't create a cycle.
 	///
+	/// Returns `None` if the token has multiple owners.
+	///
 	/// - `budget`: Limit for searching parents in depth.
 	pub fn get_checked_topmost_owner(
 		collection: CollectionId,
 		token: TokenId,
 		for_nest: Option<(CollectionId, TokenId)>,
 		budget: &dyn Budget,
-	) -> Result<T::CrossAccountId, DispatchError> {
+	) -> Result<Option<T::CrossAccountId>, DispatchError> {
 		// Tried to nest token in itself
 		if Some((collection, token)) == for_nest {
 			return Err(<Error<T>>::OuroborosDetected.into());
@@ -242,8 +259,9 @@
 					return Err(<Error<T>>::OuroborosDetected.into())
 				}
 				// Token is owned by other user
-				Parent::User(user) => return Ok(user),
+				Parent::User(user) => return Ok(Some(user)),
 				Parent::TokenNotFound => return Err(<Error<T>>::TokenNotFound.into()),
+				Parent::MultipleOwners => return Ok(None),
 				// Continue parent chain
 				Parent::Token(_, _) => {}
 			}
@@ -284,12 +302,17 @@
 		budget: &dyn Budget,
 	) -> Result<bool, DispatchError> {
 		let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {
-			Some((collection, token)) => Self::find_topmost_owner(collection, token, budget)?,
+			Some((collection, token)) => match Self::find_topmost_owner(collection, token, budget)?
+			{
+				Some(topmost_owner) => topmost_owner,
+				None => return Ok(false),
+			},
 			None => user,
 		};
 
-		Self::get_checked_topmost_owner(collection, token, for_nest, budget)
-			.map(|indirect_owner| indirect_owner == target_parent)
+		Self::get_checked_topmost_owner(collection, token, for_nest, budget).map(|indirect_owner| {
+			indirect_owner.map_or(false, |indirect_owner| indirect_owner == target_parent)
+		})
 	}
 
 	/// Checks that `under` is valid token and that `token_id` could be nested under it
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -252,7 +252,7 @@
 /// Collection can represent various types of tokens.
 /// Each collection can contain only one type of tokens at a time.
 /// This type helps to understand which tokens the collection contains.
-#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
+#[derive(Encode, Decode, Eq, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 pub enum CollectionMode {
 	/// Non fungible tokens.
modifiedruntime/common/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -83,7 +83,7 @@
                 fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
                     let budget = up_data_structs::budget::Value::new(10);
 
-                    Ok(Some(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?))
+                    Ok(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?)
                 }
                 fn token_children(collection: CollectionId, token: TokenId) -> Result<Vec<TokenChild>, DispatchError> {
                     Ok(<pallet_nonfungible::Pallet<Runtime>>::token_children_ids(collection, token))