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

difftreelog

feat add token children

Daniel Shiposha2022-05-26parent: #459d9af.patch.diff
in: master

6 files changed

modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -1237,6 +1237,18 @@
 		budget: &dyn Budget,
 	) -> DispatchResult;
 
+	fn nest(
+		&self,
+		_under: TokenId,
+		_to_nest: (CollectionId, TokenId)
+	) {}
+
+	fn unnest(
+		&self,
+		_under: TokenId,
+		_to_nest: (CollectionId, TokenId)
+	) {}
+
 	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;
 	fn collection_tokens(&self) -> Vec<TokenId>;
 	fn token_exists(&self, token: TokenId) -> bool;
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -25,8 +25,8 @@
 	budget::Budget,
 };
 use pallet_common::{
-	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, CollectionHandle,
-	dispatch::CollectionDispatch, eth::collection_id_to_address,
+	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,
+	eth::collection_id_to_address,
 };
 use pallet_evm::Pallet as PalletEvm;
 use pallet_structure::Pallet as PalletStructure;
@@ -176,6 +176,11 @@
 
 		if balance == 0 {
 			<Balance<T>>::remove((collection.id, owner));
+			<PalletStructure<T>>::unnest_if_nested(
+				owner,
+				collection.id,
+				TokenId::default()
+			);
 		} else {
 			<Balance<T>>::insert((collection.id, owner), balance);
 		}
@@ -228,22 +233,17 @@
 		} else {
 			None
 		};
-
-		if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {
-			let handle = <CollectionHandle<T>>::try_get(target.0)?;
-			let dispatch = T::CollectionDispatch::dispatch(handle);
-			let dispatch = dispatch.as_dyn();
 
-			dispatch.check_nesting(
-				from.clone(),
-				(collection.id, TokenId::default()),
-				target.1,
-				nesting_budget,
-			)?;
-		}
-
 		// =========
 
+		<PalletStructure<T>>::try_nest_if_sent_to_token(
+			from.clone(),
+			to,
+			collection.id,
+			TokenId::default(),
+			nesting_budget
+		)?;
+
 		if let Some(balance_to) = balance_to {
 			// from != to
 			if balance_from == 0 {
@@ -306,18 +306,13 @@
 		}
 
 		for (to, _) in balances.iter() {
-			if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {
-				let handle = <CollectionHandle<T>>::try_get(target.0)?;
-				let dispatch = T::CollectionDispatch::dispatch(handle);
-				let dispatch = dispatch.as_dyn();
-
-				dispatch.check_nesting(
-					sender.clone(),
-					(collection.id, TokenId::default()),
-					target.1,
-					nesting_budget,
-				)?;
-			}
+			<PalletStructure<T>>::check_nesting(
+				sender.clone(),
+				to,
+				collection.id,
+				TokenId::default(),
+				nesting_budget,
+			)?;
 		}
 
 		// =========
@@ -325,7 +320,7 @@
 		<TotalSupply<T>>::insert(collection.id, total_supply);
 		for (user, amount) in balances {
 			<Balance<T>>::insert((collection.id, &user), amount);
-
+			<PalletStructure<T>>::nest_if_sent_to_token(&user, collection.id, TokenId::default());
 			<PalletEvm<T>>::deposit_log(
 				ERC20Events::Transfer {
 					from: H160::default(),
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -353,6 +353,22 @@
 		<Pallet<T>>::check_nesting(self, sender, from, under, budget)
 	}
 
+	fn nest(
+		&self,
+		under: TokenId,
+		to_nest: (CollectionId, TokenId)
+	) {
+		<Pallet<T>>::nest((self.id, under), to_nest);
+	}
+
+	fn unnest(
+		&self,
+		under: TokenId,
+		to_unnest: (CollectionId, TokenId)
+	) {
+		<Pallet<T>>::unnest((self.id, under), to_unnest);
+	}
+
 	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {
 		<Owned<T>>::iter_prefix((self.id, account))
 			.map(|(id, _)| id)
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -27,7 +27,7 @@
 use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
 use pallet_common::{
 	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,
-	dispatch::CollectionDispatch, eth::collection_id_to_address,
+	eth::collection_id_to_address,
 };
 use pallet_structure::Pallet as PalletStructure;
 use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
@@ -77,6 +77,8 @@
 		NotNonfungibleDataUsedToMintFungibleCollectionToken,
 		/// Used amount > 1 with NFT
 		NonfungibleItemsHaveNoAmount,
+		/// Unable to burn NFT with children
+		CantBurnNftWithChildren,
 	}
 
 	#[pallet::config]
@@ -128,7 +130,20 @@
 		QueryKind = ValueQuery,
 	>;
 
+	/// Used to enumerate token's children
 	#[pallet::storage]
+	#[pallet::getter(fn token_children)]
+	pub type TokenChildren<T: Config> = StorageNMap<
+		Key = (
+			Key<Twox64Concat, CollectionId>,
+			Key<Twox64Concat, TokenId>,
+			Key<Twox64Concat, (CollectionId, TokenId)>,
+		),
+		Value = bool,
+		QueryKind = ValueQuery,
+	>;
+
+	#[pallet::storage]
 	pub type AccountBalance<T: Config> = StorageNMap<
 		Key = (
 			Key<Twox64Concat, CollectionId>,
@@ -283,6 +298,7 @@
 		PalletCommon::destroy_collection(collection.0, sender)?;
 
 		<TokenData<T>>::remove_prefix((id,), None);
+		<TokenChildren<T>>::remove_prefix((id,), None);
 		<Owned<T>>::remove_prefix((id,), None);
 		<TokensMinted<T>>::remove(id);
 		<TokensBurnt<T>>::remove(id);
@@ -308,6 +324,10 @@
 			collection.check_allowlist(sender)?;
 		}
 
+		if Self::token_has_children(collection.id, token) {
+			return Err(<Error<T>>::CantBurnNftWithChildren.into());
+		}
+
 		let burnt = <TokensBurnt<T>>::get(collection.id)
 			.checked_add(1)
 			.ok_or(ArithmeticError::Overflow)?;
@@ -321,6 +341,11 @@
 		} else {
 			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);
 		}
+
+		if let Some(owner) = T::CrossTokenAddressMapping::address_to_token(&token_data.owner) {
+			Self::unnest(owner, (collection.id, token));
+		}
+
 		// =========
 
 		<Owned<T>>::remove((collection.id, &token_data.owner, token));
@@ -554,18 +579,13 @@
 			None
 		};
 
-		if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {
-			let handle = <CollectionHandle<T>>::try_get(target.0)?;
-			let dispatch = T::CollectionDispatch::dispatch(handle);
-			let dispatch = dispatch.as_dyn();
-
-			dispatch.check_nesting(
-				from.clone(),
-				(collection.id, token),
-				target.1,
-				nesting_budget,
-			)?;
-		}
+		<PalletStructure<T>>::try_nest_if_sent_to_token(
+			from.clone(),
+			to,
+			collection.id,
+			token,
+			nesting_budget
+		)?;
 
 		// =========
 
@@ -654,17 +674,14 @@
 
 		for (i, data) in data.iter().enumerate() {
 			let token = TokenId(first_token + i as u32 + 1);
-			if let Some(target) = T::CrossTokenAddressMapping::address_to_token(&data.owner) {
-				let handle = <CollectionHandle<T>>::try_get(target.0)?;
-				let dispatch = T::CollectionDispatch::dispatch(handle);
-				let dispatch = dispatch.as_dyn();
-				dispatch.check_nesting(
-					sender.clone(),
-					(collection.id, token),
-					target.1,
-					nesting_budget,
-				)?;
-			}
+
+			<PalletStructure<T>>::check_nesting(
+				sender.clone(),
+				&data.owner,
+				collection.id,
+				token,
+				nesting_budget,
+			)?;
 		}
 
 		// =========
@@ -681,6 +698,8 @@
 					},
 				);
 
+				<PalletStructure<T>>::nest_if_sent_to_token(&data.owner, collection.id, TokenId(token));
+
 				if let Err(e) = Self::set_token_properties(
 					collection,
 					sender,
@@ -928,6 +947,29 @@
 		Ok(())
 	}
 
+	fn nest(
+		under: (CollectionId, TokenId),
+		to_nest: (CollectionId, TokenId),
+	) {
+		<TokenChildren<T>>::insert(
+			(under.0, under.1, (to_nest.0, to_nest.1)),
+			true
+		);
+	}
+
+	fn unnest(
+		under: (CollectionId, TokenId),
+		to_unnest: (CollectionId, TokenId),
+	) {
+		<TokenChildren<T>>::remove(
+			(under.0, under.1, to_unnest)
+		);
+	}
+
+	fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {
+		<TokenChildren<T>>::iter_prefix((collection_id, token_id)).next().is_some()
+	}
+
 	/// Delegated to `create_multiple_items`
 	pub fn create_item(
 		collection: &NonfungibleHandle<T>,
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -23,8 +23,7 @@
 };
 use pallet_evm::account::CrossAccountId;
 use pallet_common::{
-	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, CollectionHandle,
-	dispatch::CollectionDispatch,
+	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,
 };
 use pallet_structure::Pallet as PalletStructure;
 use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
@@ -265,6 +264,7 @@
 			// =========
 
 			<Owned<T>>::remove((collection.id, owner, token));
+			<PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);
 			<AccountBalance<T>>::insert((collection.id, owner), account_balance);
 			Self::burn_token(collection, token)?;
 			<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(
@@ -292,6 +292,7 @@
 
 		if balance == 0 {
 			<Owned<T>>::remove((collection.id, owner, token));
+			<PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);
 			<Balance<T>>::remove((collection.id, token, owner));
 			<AccountBalance<T>>::insert((collection.id, owner), account_balance);
 		} else {
@@ -371,22 +372,17 @@
 		} else {
 			None
 		};
-
-		if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {
-			let handle = <CollectionHandle<T>>::try_get(target.0)?;
-			let dispatch = T::CollectionDispatch::dispatch(handle);
-			let dispatch = dispatch.as_dyn();
-
-			dispatch.check_nesting(
-				from.clone(),
-				(collection.id, token),
-				target.1,
-				nesting_budget,
-			)?;
-		}
 
 		// =========
 
+		<PalletStructure<T>>::try_nest_if_sent_to_token(
+			from.clone(),
+			to,
+			collection.id,
+			token,
+			nesting_budget
+		)?;
+
 		if let Some(balance_to) = balance_to {
 			// from != to
 			if balance_from == 0 {
@@ -488,18 +484,14 @@
 		for (i, token) in data.iter().enumerate() {
 			let token_id = TokenId(first_token_id + i as u32 + 1);
 			for (to, _) in token.users.iter() {
-				if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {
-					let handle = <CollectionHandle<T>>::try_get(target.0)?;
-					let dispatch = T::CollectionDispatch::dispatch(handle);
-					let dispatch = dispatch.as_dyn();
 
-					dispatch.check_nesting(
-						sender.clone(),
-						(collection.id, token_id),
-						target.1,
-						nesting_budget,
-					)?;
-				}
+				<PalletStructure<T>>::check_nesting(
+					sender.clone(),
+					to,
+					collection.id,
+					token_id,
+					nesting_budget,
+				)?;
 			}
 		}
 
@@ -519,12 +511,15 @@
 					const_data: token.const_data,
 				},
 			);
+
 			for (user, amount) in token.users.into_iter() {
 				if amount == 0 {
 					continue;
 				}
 				<Balance<T>>::insert((collection.id, token_id, &user), amount);
 				<Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);
+				<PalletStructure<T>>::nest_if_sent_to_token(&user, collection.id, TokenId(token_id));
+
 				// TODO: ERC20 transfer event
 				<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(
 					collection.id,
modifiedpallets/structure/src/lib.rsdiffbeforeafterboth
before · pallets/structure/src/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]23use sp_std::collections::btree_set::BTreeSet;45use frame_support::dispatch::DispatchError;6use frame_support::fail;7pub use pallet::*;8use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};9use up_data_structs::{CollectionId, TokenId, mapping::TokenAddressMapping, budget::Budget};1011#[cfg(feature = "runtime-benchmarks")]12pub mod benchmarking;13pub mod weights;1415pub type SelfWeightOf<T> = <T as crate::Config>::WeightInfo;1617#[frame_support::pallet]18pub mod pallet {19	use frame_support::Parameter;20	use frame_support::dispatch::{GetDispatchInfo, UnfilteredDispatchable};21	use frame_support::pallet_prelude::*;2223	use super::*;2425	#[pallet::error]26	pub enum Error<T> {27		/// While searched for owner, got already checked account28		OuroborosDetected,29		/// While searched for owner, encountered depth limit30		DepthLimit,31		/// While searched for owner, found token owner by not-yet-existing token32		TokenNotFound,33	}3435	#[pallet::event]36	pub enum Event<T> {37		/// Executed call on behalf of token38		Executed(DispatchResult),39	}4041	#[pallet::config]42	pub trait Config: frame_system::Config + pallet_common::Config {43		type WeightInfo: weights::WeightInfo;44		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;45		type Call: Parameter + UnfilteredDispatchable<Origin = Self::Origin> + GetDispatchInfo;46	}4748	#[pallet::pallet]49	pub struct Pallet<T>(_);5051	#[pallet::call]52	impl<T: Config> Pallet<T> {53		// #[pallet::weight({54		// 	let dispatch_info = call.get_dispatch_info();5556		// 	(57		// 		dispatch_info.weight58		// 			// Cost of dereferencing parent59		// 			.saturating_add(T::DbWeight::get().reads(2 * *max_depth as Weight))60		// 			.saturating_add(4000 * *max_depth as Weight),61		// 		dispatch_info.class)62		// })]63		// pub fn execute(64		// 	origin: OriginFor<T>,65		// 	call: Box<<T as Config>::Call>,66		// 	max_depth: u32,67		// ) -> DispatchResult {68	}69}7071#[derive(PartialEq)]72pub enum Parent<CrossAccountId> {73	/// Token owned by normal account74	User(CrossAccountId),75	/// Passed token not found76	TokenNotFound,77	/// Token owner is another token (target token still may not exist)78	Token(CollectionId, TokenId),79}8081impl<T: Config> Pallet<T> {82	pub fn find_parent(83		collection: CollectionId,84		token: TokenId,85	) -> Result<Parent<T::CrossAccountId>, DispatchError> {86		// TODO: Reduce cost by not reading collection config87		let handle = match CollectionHandle::try_get(collection) {88			Ok(v) => v,89			Err(_) => return Ok(Parent::TokenNotFound),90		};91		let handle = T::CollectionDispatch::dispatch(handle);92		let handle = handle.as_dyn();9394		Ok(match handle.token_owner(token) {95			Some(owner) => match T::CrossTokenAddressMapping::address_to_token(&owner) {96				Some((collection, token)) => Parent::Token(collection, token),97				None => Parent::User(owner),98			},99			None => Parent::TokenNotFound,100		})101	}102103	pub fn parent_chain(104		mut collection: CollectionId,105		mut token: TokenId,106	) -> impl Iterator<Item = Result<Parent<T::CrossAccountId>, DispatchError>> {107		let mut finished = false;108		let mut visited = BTreeSet::new();109		visited.insert((collection, token));110		core::iter::from_fn(move || {111			if finished {112				return None;113			}114			let parent = Self::find_parent(collection, token);115			match parent {116				Ok(Parent::Token(new_collection, new_token)) => {117					collection = new_collection;118					token = new_token;119					if !visited.insert((new_collection, new_token)) {120						finished = true;121						return Some(Err(<Error<T>>::OuroborosDetected.into()));122					}123				}124				_ => finished = true,125			}126			Some(parent as Result<_, DispatchError>)127		})128	}129130	/// Try to dereference address, until finding top level owner131	///132	/// May return token address if parent token not yet exists133	pub fn find_topmost_owner(134		collection: CollectionId,135		token: TokenId,136		budget: &dyn Budget,137	) -> Result<T::CrossAccountId, DispatchError> {138		let owner = Self::parent_chain(collection, token)139			.take_while(|_| budget.consume())140			.find(|p| matches!(p, Ok(Parent::User(_) | Parent::TokenNotFound)))141			.ok_or(<Error<T>>::DepthLimit)??;142143		Ok(match owner {144			Parent::User(v) => v,145			_ => fail!(<Error<T>>::TokenNotFound),146		})147	}148149	/// Check if token indirectly owned by specified user150	pub fn check_indirectly_owned(151		user: T::CrossAccountId,152		collection: CollectionId,153		token: TokenId,154		for_nest: Option<(CollectionId, TokenId)>,155		budget: &dyn Budget,156	) -> Result<bool, DispatchError> {157		let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {158			Some((collection, token)) => Parent::Token(collection, token),159			None => Parent::User(user),160		};161162		// Tried to nest token in itself163		if Some((collection, token)) == for_nest {164			return Err(<Error<T>>::OuroborosDetected.into());165		}166167		for parent in Self::parent_chain(collection, token).take_while(|_| budget.consume()) {168			match parent? {169				// Tried to nest token in chain, which has this token as one of parents170				Parent::Token(collection, token) if Some((collection, token)) == for_nest => {171					return Err(<Error<T>>::OuroborosDetected.into())172				}173				// Found needed parent, token is indirecty owned174				v if v == target_parent => return Ok(true),175				// Token is owned by other user176				Parent::User(_) => return Ok(false),177				Parent::TokenNotFound => return Ok(false),178				// Continue parent chain179				Parent::Token(_, _) => {}180			}181		}182183		Err(<Error<T>>::DepthLimit.into())184	}185}
after · 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};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 searched for owner, found token owner by not-yet-existing token33		TokenNotFound,34	}3536	#[pallet::event]37	pub enum Event<T> {38		/// Executed call on behalf of token39		Executed(DispatchResult),40	}4142	#[pallet::config]43	pub trait Config: frame_system::Config + pallet_common::Config {44		type WeightInfo: weights::WeightInfo;45		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;46		type Call: Parameter + UnfilteredDispatchable<Origin = Self::Origin> + GetDispatchInfo;47	}4849	#[pallet::pallet]50	pub struct Pallet<T>(_);5152	#[pallet::call]53	impl<T: Config> Pallet<T> {54		// #[pallet::weight({55		// 	let dispatch_info = call.get_dispatch_info();5657		// 	(58		// 		dispatch_info.weight59		// 			// Cost of dereferencing parent60		// 			.saturating_add(T::DbWeight::get().reads(2 * *max_depth as Weight))61		// 			.saturating_add(4000 * *max_depth as Weight),62		// 		dispatch_info.class)63		// })]64		// pub fn execute(65		// 	origin: OriginFor<T>,66		// 	call: Box<<T as Config>::Call>,67		// 	max_depth: u32,68		// ) -> DispatchResult {69	}70}7172#[derive(PartialEq)]73pub enum Parent<CrossAccountId> {74	/// Token owned by normal account75	User(CrossAccountId),76	/// Passed token not found77	TokenNotFound,78	/// Token owner is another token (target token still may not exist)79	Token(CollectionId, TokenId),80}8182impl<T: Config> Pallet<T> {83	pub fn find_parent(84		collection: CollectionId,85		token: TokenId,86	) -> Result<Parent<T::CrossAccountId>, DispatchError> {87		// TODO: Reduce cost by not reading collection config88		let handle = match CollectionHandle::try_get(collection) {89			Ok(v) => v,90			Err(_) => return Ok(Parent::TokenNotFound),91		};92		let handle = T::CollectionDispatch::dispatch(handle);93		let handle = handle.as_dyn();9495		Ok(match handle.token_owner(token) {96			Some(owner) => match T::CrossTokenAddressMapping::address_to_token(&owner) {97				Some((collection, token)) => Parent::Token(collection, token),98				None => Parent::User(owner),99			},100			None => Parent::TokenNotFound,101		})102	}103104	pub fn parent_chain(105		mut collection: CollectionId,106		mut token: TokenId,107	) -> impl Iterator<Item = Result<Parent<T::CrossAccountId>, DispatchError>> {108		let mut finished = false;109		let mut visited = BTreeSet::new();110		visited.insert((collection, token));111		core::iter::from_fn(move || {112			if finished {113				return None;114			}115			let parent = Self::find_parent(collection, token);116			match parent {117				Ok(Parent::Token(new_collection, new_token)) => {118					collection = new_collection;119					token = new_token;120					if !visited.insert((new_collection, new_token)) {121						finished = true;122						return Some(Err(<Error<T>>::OuroborosDetected.into()));123					}124				}125				_ => finished = true,126			}127			Some(parent as Result<_, DispatchError>)128		})129	}130131	/// Try to dereference address, until finding top level owner132	///133	/// May return token address if parent token not yet exists134	pub fn find_topmost_owner(135		collection: CollectionId,136		token: TokenId,137		budget: &dyn Budget,138	) -> Result<T::CrossAccountId, DispatchError> {139		let owner = Self::parent_chain(collection, token)140			.take_while(|_| budget.consume())141			.find(|p| matches!(p, Ok(Parent::User(_) | Parent::TokenNotFound)))142			.ok_or(<Error<T>>::DepthLimit)??;143144		Ok(match owner {145			Parent::User(v) => v,146			_ => fail!(<Error<T>>::TokenNotFound),147		})148	}149150	/// Check if token indirectly owned by specified user151	pub fn check_indirectly_owned(152		user: T::CrossAccountId,153		collection: CollectionId,154		token: TokenId,155		for_nest: Option<(CollectionId, TokenId)>,156		budget: &dyn Budget,157	) -> Result<bool, DispatchError> {158		let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {159			Some((collection, token)) => Parent::Token(collection, token),160			None => Parent::User(user),161		};162163		// Tried to nest token in itself164		if Some((collection, token)) == for_nest {165			return Err(<Error<T>>::OuroborosDetected.into());166		}167168		for parent in Self::parent_chain(collection, token).take_while(|_| budget.consume()) {169			match parent? {170				// Tried to nest token in chain, which has this token as one of parents171				Parent::Token(collection, token) if Some((collection, token)) == for_nest => {172					return Err(<Error<T>>::OuroborosDetected.into())173				}174				// Found needed parent, token is indirecty owned175				v if v == target_parent => return Ok(true),176				// Token is owned by other user177				Parent::User(_) => return Ok(false),178				Parent::TokenNotFound => return Err(<Error<T>>::TokenNotFound.into()),179				// Continue parent chain180				Parent::Token(_, _) => {}181			}182		}183184		Err(<Error<T>>::DepthLimit.into())185	}186187	pub fn check_nesting(188		from: T::CrossAccountId,189		under: &T::CrossAccountId,190		collection_id: CollectionId,191		token_id: TokenId,192		nesting_budget: &dyn Budget193	) -> DispatchResult {194		Self::try_dispatched(195			under,196			|d, parent_id| d.check_nesting(197				from,198				(collection_id, token_id),199				parent_id,200				nesting_budget201			)202		)203	}204205	pub fn try_nest_if_sent_to_token(206		from: T::CrossAccountId,207		under: &T::CrossAccountId,208		collection_id: CollectionId,209		token_id: TokenId,210		nesting_budget: &dyn Budget211	) -> DispatchResult {212		Self::try_dispatched(213			under,214			|d, parent_id| {215				d.check_nesting(216					from,217					(collection_id, token_id),218					parent_id,219					nesting_budget220				)?;221222				d.nest(parent_id, (collection_id, token_id));223224				Ok(())225			}226		)227	}228229	pub fn nest_if_sent_to_token(230		owner: &T::CrossAccountId,231		collection_id: CollectionId,232		token_id: TokenId233	) {234		Self::dispatched(235			owner,236			|d, parent_id| d.nest(237				parent_id,238				(collection_id, token_id)239			)240		);241	}242243	pub fn unnest_if_nested(244		owner: &T::CrossAccountId,245		collection_id: CollectionId,246		token_id: TokenId247	) {248		Self::dispatched(249			owner,250			|d, parent_id| d.unnest(251			parent_id,252			(collection_id, token_id)253			)254		);255	}256257	fn dispatched(258		account: &T::CrossAccountId,259		action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId)260	) {261		Self::try_dispatched(262			account,263			|d, id| {264				action(d, id);265				Ok(())266			}267		).unwrap();268	}269270	fn try_dispatched(271		account: &T::CrossAccountId,272		action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId) -> DispatchResult273	) -> DispatchResult {274		let account = T::CrossTokenAddressMapping::address_to_token(account);275276		if account.is_none() {277			return Ok(());278		}279280		let account = account.unwrap();281282		let handle = <CollectionHandle<T>>::try_get(account.0);283284		if handle.is_err() {285			return Ok(());286		}287288		let handle = handle.unwrap();289290		let dispatch = T::CollectionDispatch::dispatch(handle);291		let dispatch = dispatch.as_dyn();292293		action(dispatch, account.1)294	}295}