git.delta.rocks / unique-network / refs/commits / 00b9fd625e6d

difftreelog

feat initial rmrk send impl

Daniel Shiposha2022-06-03parent: #70623cc.patch.diff
in: master

10 files changed

modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -1298,6 +1298,7 @@
 		spender: T::CrossAccountId,
 		token: TokenId,
 		amount: u128,
+		nesting_budget: &dyn Budget,
 	) -> DispatchResultWithPostInfo;
 	fn transfer_from(
 		&self,
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -226,6 +226,7 @@
 		spender: T::CrossAccountId,
 		token: TokenId,
 		amount: u128,
+		_nesting_budget: &dyn Budget,
 	) -> DispatchResultWithPostInfo {
 		ensure!(
 			token == TokenId::default(),
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -308,14 +308,15 @@
 		spender: T::CrossAccountId,
 		token: TokenId,
 		amount: u128,
+		nesting_budget: &dyn Budget,
 	) -> DispatchResultWithPostInfo {
 		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
 
 		with_weight(
 			if amount == 1 {
-				<Pallet<T>>::set_allowance(self, &sender, token, Some(&spender))
+				<Pallet<T>>::set_allowance(self, &sender, token, Some(&spender), nesting_budget)
 			} else {
-				<Pallet<T>>::set_allowance(self, &sender, token, None)
+				<Pallet<T>>::set_allowance(self, &sender, token, None, nesting_budget)
 			},
 			<CommonWeights<T>>::approve(),
 		)
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -269,8 +269,11 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let approved = T::CrossAccountId::from_eth(approved);
 		let token = token_id.try_into()?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))
+		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved), &budget)
 			.map_err(dispatch_to_evm::<T>)?;
 		Ok(())
 	}
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -878,6 +878,7 @@
 		sender: &T::CrossAccountId,
 		token: TokenId,
 		spender: Option<&T::CrossAccountId>,
+		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
 		if collection.permissions.access() == AccessMode::AllowList {
 			collection.check_allowlist(sender)?;
@@ -889,9 +890,16 @@
 		if let Some(spender) = spender {
 			<PalletCommon<T>>::ensure_correct_receiver(spender)?;
 		}
-		let token_data =
-			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;
-		if &token_data.owner != sender {
+
+		let is_owned = <PalletStructure<T>>::check_indirectly_owned(
+			sender.clone(),
+			collection.id,
+			token,
+			None,
+			nesting_budget
+		)?;
+
+		if !is_owned {
 			ensure!(
 				collection.ignores_owned_amount(sender),
 				<CommonError<T>>::CantApproveMoreThanOwned
@@ -918,6 +926,9 @@
 			// `from`, `to` checked in [`transfer`]
 			collection.check_allowlist(spender)?;
 		}
+		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {
+			return Ok(());
+		}
 		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {
 			// TODO: should collection owner be allowed to perform this transfer?
 			ensure!(
@@ -930,9 +941,6 @@
 				)?,
 				<CommonError<T>>::ApprovedValueTooLow,
 			);
-			return Ok(());
-		}
-		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {
 			return Ok(());
 		}
 		ensure!(
modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/lib.rs
+++ b/pallets/proxy-rmrk-core/src/lib.rs
@@ -20,7 +20,7 @@
 use frame_system::{pallet_prelude::*, ensure_signed};
 use sp_runtime::{DispatchError, Permill, traits::StaticLookup};
 use sp_std::vec::Vec;
-use up_data_structs::*;
+use up_data_structs::{*, mapping::TokenAddressMapping};
 use pallet_common::{
 	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,
 };
@@ -39,6 +39,8 @@
 
 use RmrkProperty::*;
 
+const NESTING_BUDGET: u32 = 5;
+
 #[frame_support::pallet]
 pub mod pallet {
 	use super::*;
@@ -56,10 +58,13 @@
 	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;
 
 	#[pallet::storage]
-	#[pallet::getter(fn collection_index_map)]
-	pub type CollectionIndexMap<T: Config> =
+	pub type UniqueCollectionId<T: Config> =
 		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;
 
+	#[pallet::storage]
+	pub type RmrkInernalCollectionId<T: Config> =
+		StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;
+
 	#[pallet::pallet]
 	#[pallet::generate_store(pub(super) trait Store)]
 	pub struct Pallet<T>(_);
@@ -151,10 +156,12 @@
 					.into_inner()
 					.try_into()
 					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,
+				permissions: Some(CollectionPermissions {
+					nesting: Some(NestingRule::Owner),
+					..Default::default()
+				}),
 				..Default::default()
 			};
-
-			<CollectionIndex<T>>::mutate(|n| *n += 1);
 
 			let unique_collection_id = Self::init_collection(
 				T::CrossAccountId::from_sub(sender.clone()),
@@ -167,7 +174,10 @@
 			)?;
 			let rmrk_collection_id = <CollectionIndex<T>>::get();
 
-			<CollectionIndexMap<T>>::insert(rmrk_collection_id, unique_collection_id);
+			<UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);
+			<RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);
+
+			<CollectionIndex<T>>::mutate(|n| *n += 1);
 
 			Self::deposit_event(Event::CollectionCreated {
 				issuer: sender,
@@ -356,6 +366,81 @@
 
 		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]
 		#[transactional]
+		pub fn send(
+			origin: OriginFor<T>,
+			rmrk_collection_id: RmrkCollectionId,
+			rmrk_nft_id: RmrkNftId,
+			new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,
+		) -> DispatchResult {
+			let sender = ensure_signed(origin.clone())?;
+			let cross_sender = T::CrossAccountId::from_sub(sender.clone());
+
+			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;
+			let nft_id = rmrk_nft_id.into();
+
+			let token_data = <TokenData<T>>::get((collection_id, nft_id))
+				.ok_or(<Error<T>>::NoAvailableNftId)?;
+
+			let from = token_data.owner;
+
+			let collection = Self::get_typed_nft_collection(
+				collection_id,
+				misc::CollectionType::Regular,
+			)?;
+
+			let budget = budget::Value::new(NESTING_BUDGET);
+
+			let target_owner;
+
+			match new_owner {
+				RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {
+					target_owner = T::CrossAccountId::from_sub(account_id);
+				},
+				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 spender = <PalletStructure<T>>::get_indirect_owner(
+						target_collection_id,
+						target_nft_id.into(),
+						Some((collection_id, nft_id)),
+						&budget,
+					)?;
+
+					let is_approval_required = cross_sender != spender;
+
+					if is_approval_required {
+						<PalletNft<T>>::set_allowance(
+							&collection,
+							&cross_sender,
+							nft_id,
+							Some(&spender),
+							&budget
+						).map_err(Self::map_common_err_to_proxy)?;
+
+						return Ok(());
+					}
+				}
+			}
+
+			<PalletNft<T>>::transfer_from(
+				&collection,
+				&cross_sender,
+				&from,
+				&target_owner,
+				nft_id,
+				&budget
+			).map_err(Self::map_common_err_to_proxy)?;
+
+			Ok(())
+		}
+
+		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]
+		#[transactional]
 		pub fn set_property(
 			origin: OriginFor<T>,
 			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,
@@ -367,12 +452,13 @@
 			let sender = T::CrossAccountId::from_sub(sender);
 
 			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;
+			let budget = budget::Value::new(NESTING_BUDGET);
 
 			match maybe_nft_id {
 				Some(nft_id) => {
 					let token_id: TokenId = nft_id.into();
 
-					Self::ensure_nft_owner(collection_id, token_id, &sender)?;
+					Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;
 					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;
 
 					<PalletNft<T>>::set_scoped_token_property(
@@ -607,7 +693,7 @@
 			owner: owner.clone(),
 		};
 
-		let budget = budget::Value::new(2);
+		let budget = budget::Value::new(NESTING_BUDGET);
 
 		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;
 
@@ -651,14 +737,8 @@
 		//ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);
 
 		let sender = T::CrossAccountId::from_sub(sender);
-		let budget = budget::Value::new(10);
-		let pending = !<PalletStructure<T>>::check_indirectly_owned(
-			sender.clone(),
-			collection_id,
-			token_id,
-			None,
-			&budget,
-		)?;
+		let budget = budget::Value::new(NESTING_BUDGET);
+		let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();
 
 		let resource_collection_id: CollectionId =
 			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;
@@ -757,7 +837,14 @@
 	pub fn unique_collection_id(
 		rmrk_collection_id: RmrkCollectionId,
 	) -> Result<CollectionId, DispatchError> {
-		<CollectionIndexMap<T>>::try_get(rmrk_collection_id)
+		<UniqueCollectionId<T>>::try_get(rmrk_collection_id)
+			.map_err(|_| <Error<T>>::CollectionUnknown.into())
+	}
+
+	pub fn rmrk_collection_id(
+		unique_collection_id: CollectionId
+	) -> Result<RmrkCollectionId, DispatchError> {
+		<RmrkInernalCollectionId<T>>::try_get(unique_collection_id)
 			.map_err(|_| <Error<T>>::CollectionUnknown.into())
 	}
 
@@ -873,12 +960,18 @@
 		collection_id: CollectionId,
 		token_id: TokenId,
 		possible_owner: &T::CrossAccountId,
+		nesting_budget: &dyn budget::Budget
 	) -> DispatchResult {
-		let token_data =
-			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;
+		let is_owned = <PalletStructure<T>>::check_indirectly_owned(
+			possible_owner.clone(),
+			collection_id,
+			token_id,
+			None,
+			nesting_budget,
+		)?;
 
 		ensure!(
-			token_data.owner == *possible_owner,
+			is_owned,
 			<Error<T>>::NoPermission
 		);
 
@@ -965,7 +1058,8 @@
 				NoPermission => NoPermission,
 				CollectionTokenLimitExceeded => CollectionFullOrLocked,
 				PublicMintingNotAllowed => NoPermission,
-				TokenNotFound => NoAvailableNftId
+				TokenNotFound => NoAvailableNftId,
+				ApprovedValueTooLow => NoPermission
 			}
 		}
 	}
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
before · pallets/refungible/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 sp_std::collections::btree_map::BTreeMap;20use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, traits::Get};21use up_data_structs::{22	CollectionId, TokenId, CreateItemExData, CreateRefungibleExData, budget::Budget, Property,23	PropertyKey, PropertyValue, PropertyKeyPermission, CreateItemData,24};25use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};26use pallet_structure::Error as StructureError;27use sp_runtime::DispatchError;28use sp_std::{vec::Vec, vec};2930use crate::{31	AccountBalance, Allowance, Balance, Config, Error, Owned, Pallet, RefungibleHandle,32	SelfWeightOf, TokenData, weights::WeightInfo, TokensMinted,33};3435macro_rules! max_weight_of {36	($($method:ident ($($args:tt)*)),*) => {37		038		$(39			.max(<SelfWeightOf<T>>::$method($($args)*))40		)*41	};42}4344pub struct CommonWeights<T: Config>(PhantomData<T>);45impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {46	fn create_item() -> Weight {47		<SelfWeightOf<T>>::create_item()48	}4950	fn create_multiple_items(data: &[CreateItemData]) -> Weight {51		<SelfWeightOf<T>>::create_multiple_items(data.len() as u32)52	}5354	fn create_multiple_items_ex(call: &CreateItemExData<T::CrossAccountId>) -> Weight {55		match call {56			CreateItemExData::RefungibleMultipleOwners(i) => {57				<SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(i.users.len() as u32)58			}59			CreateItemExData::RefungibleMultipleItems(i) => {60				<SelfWeightOf<T>>::create_multiple_items_ex_multiple_items(i.len() as u32)61			}62			_ => 0,63		}64	}6566	fn burn_item() -> Weight {67		max_weight_of!(burn_item_partial(), burn_item_fully())68	}6970	fn set_collection_properties(_amount: u32) -> Weight {71		// Error72		073	}7475	fn delete_collection_properties(_amount: u32) -> Weight {76		// Error77		078	}7980	fn set_token_properties(amount: u32) -> Weight {81		<SelfWeightOf<T>>::set_token_properties(amount)82	}8384	fn delete_token_properties(amount: u32) -> Weight {85		<SelfWeightOf<T>>::delete_token_properties(amount)86	}8788	fn set_property_permissions(amount: u32) -> Weight {89		<SelfWeightOf<T>>::set_property_permissions(amount)90	}9192	fn transfer() -> Weight {93		max_weight_of!(94			transfer_normal(),95			transfer_creating(),96			transfer_removing(),97			transfer_creating_removing()98		)99	}100101	fn approve() -> Weight {102		<SelfWeightOf<T>>::approve()103	}104105	fn transfer_from() -> Weight {106		max_weight_of!(107			transfer_from_normal(),108			transfer_from_creating(),109			transfer_from_removing(),110			transfer_from_creating_removing()111		)112	}113114	fn burn_from() -> Weight {115		<SelfWeightOf<T>>::burn_from()116	}117118	fn burn_recursively_self_raw() -> Weight {119		// Read to get total balance120		Self::burn_item() + T::DbWeight::get().reads(1)121	}122	fn burn_recursively_breadth_raw(_amount: u32) -> Weight {123		// Refungible token can't have children124		0125	}126}127128fn map_create_data<T: Config>(129	data: up_data_structs::CreateItemData,130	to: &T::CrossAccountId,131) -> Result<CreateRefungibleExData<T::CrossAccountId>, DispatchError> {132	match data {133		up_data_structs::CreateItemData::ReFungible(data) => Ok(CreateRefungibleExData {134			const_data: data.const_data,135			users: {136				let mut out = BTreeMap::new();137				out.insert(to.clone(), data.pieces);138				out.try_into().expect("limit > 0")139			},140		}),141		_ => fail!(<Error<T>>::NotRefungibleDataUsedToMintFungibleCollectionToken),142	}143}144145impl<T: Config> CommonCollectionOperations<T> for RefungibleHandle<T> {146	fn create_item(147		&self,148		sender: T::CrossAccountId,149		to: T::CrossAccountId,150		data: up_data_structs::CreateItemData,151		nesting_budget: &dyn Budget,152	) -> DispatchResultWithPostInfo {153		with_weight(154			<Pallet<T>>::create_item(155				self,156				&sender,157				map_create_data::<T>(data, &to)?,158				nesting_budget,159			),160			<CommonWeights<T>>::create_item(),161		)162	}163164	fn create_multiple_items(165		&self,166		sender: T::CrossAccountId,167		to: T::CrossAccountId,168		data: Vec<up_data_structs::CreateItemData>,169		nesting_budget: &dyn Budget,170	) -> DispatchResultWithPostInfo {171		let weight = <CommonWeights<T>>::create_multiple_items(&data);172		let data = data173			.into_iter()174			.map(|d| map_create_data::<T>(d, &to))175			.collect::<Result<Vec<_>, DispatchError>>()?;176177		with_weight(178			<Pallet<T>>::create_multiple_items(self, &sender, data, nesting_budget),179			weight,180		)181	}182183	fn create_multiple_items_ex(184		&self,185		sender: <T>::CrossAccountId,186		data: CreateItemExData<T::CrossAccountId>,187		nesting_budget: &dyn Budget,188	) -> DispatchResultWithPostInfo {189		let weight = <CommonWeights<T>>::create_multiple_items_ex(&data);190		let data = match data {191			CreateItemExData::RefungibleMultipleOwners(r) => vec![r],192			CreateItemExData::RefungibleMultipleItems(r)193				if r.iter().all(|i| i.users.len() == 1) =>194			{195				r.into_inner()196			}197			_ => fail!(<Error<T>>::NotRefungibleDataUsedToMintFungibleCollectionToken),198		};199200		with_weight(201			<Pallet<T>>::create_multiple_items(self, &sender, data, nesting_budget),202			weight,203		)204	}205206	fn burn_item(207		&self,208		sender: T::CrossAccountId,209		token: TokenId,210		amount: u128,211	) -> DispatchResultWithPostInfo {212		with_weight(213			<Pallet<T>>::burn(self, &sender, token, amount),214			<CommonWeights<T>>::burn_item(),215		)216	}217218	fn burn_item_recursively(219		&self,220		sender: T::CrossAccountId,221		token: TokenId,222		self_budget: &dyn Budget,223		_breadth_budget: &dyn Budget,224	) -> DispatchResultWithPostInfo {225		ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);226		with_weight(227			<Pallet<T>>::burn(228				self,229				&sender,230				token,231				<Balance<T>>::get((self.id, token, &sender)),232			),233			<CommonWeights<T>>::burn_recursively_self_raw(),234		)235	}236237	fn transfer(238		&self,239		from: T::CrossAccountId,240		to: T::CrossAccountId,241		token: TokenId,242		amount: u128,243		nesting_budget: &dyn Budget,244	) -> DispatchResultWithPostInfo {245		with_weight(246			<Pallet<T>>::transfer(self, &from, &to, token, amount, nesting_budget),247			<CommonWeights<T>>::transfer(),248		)249	}250251	fn approve(252		&self,253		sender: T::CrossAccountId,254		spender: T::CrossAccountId,255		token: TokenId,256		amount: u128,257	) -> DispatchResultWithPostInfo {258		with_weight(259			<Pallet<T>>::set_allowance(self, &sender, &spender, token, amount),260			<CommonWeights<T>>::approve(),261		)262	}263264	fn transfer_from(265		&self,266		sender: T::CrossAccountId,267		from: T::CrossAccountId,268		to: T::CrossAccountId,269		token: TokenId,270		amount: u128,271		nesting_budget: &dyn Budget,272	) -> DispatchResultWithPostInfo {273		with_weight(274			<Pallet<T>>::transfer_from(self, &sender, &from, &to, token, amount, nesting_budget),275			<CommonWeights<T>>::transfer_from(),276		)277	}278279	fn burn_from(280		&self,281		sender: T::CrossAccountId,282		from: T::CrossAccountId,283		token: TokenId,284		amount: u128,285		nesting_budget: &dyn Budget,286	) -> DispatchResultWithPostInfo {287		with_weight(288			<Pallet<T>>::burn_from(self, &sender, &from, token, amount, nesting_budget),289			<CommonWeights<T>>::burn_from(),290		)291	}292293	fn set_collection_properties(294		&self,295		_sender: T::CrossAccountId,296		_property: Vec<Property>,297	) -> DispatchResultWithPostInfo {298		fail!(<Error<T>>::SettingPropertiesNotAllowed)299	}300301	fn delete_collection_properties(302		&self,303		_sender: &T::CrossAccountId,304		_property_keys: Vec<PropertyKey>,305	) -> DispatchResultWithPostInfo {306		fail!(<Error<T>>::SettingPropertiesNotAllowed)307	}308309	fn set_token_properties(310		&self,311		_sender: T::CrossAccountId,312		_token_id: TokenId,313		_property: Vec<Property>,314	) -> DispatchResultWithPostInfo {315		fail!(<Error<T>>::SettingPropertiesNotAllowed)316	}317318	fn set_property_permissions(319		&self,320		_sender: &T::CrossAccountId,321		_property_permissions: Vec<PropertyKeyPermission>,322	) -> DispatchResultWithPostInfo {323		fail!(<Error<T>>::SettingPropertiesNotAllowed)324	}325326	fn delete_token_properties(327		&self,328		_sender: T::CrossAccountId,329		_token_id: TokenId,330		_property_keys: Vec<PropertyKey>,331	) -> DispatchResultWithPostInfo {332		fail!(<Error<T>>::SettingPropertiesNotAllowed)333	}334335	fn check_nesting(336		&self,337		_sender: <T>::CrossAccountId,338		_from: (CollectionId, TokenId),339		_under: TokenId,340		_budget: &dyn Budget,341	) -> sp_runtime::DispatchResult {342		fail!(<Error<T>>::RefungibleDisallowsNesting)343	}344345	fn nest(&self, _under: TokenId, _to_nest: (CollectionId, TokenId)) {}346347	fn unnest(&self, _under: TokenId, _to_nest: (CollectionId, TokenId)) {}348349	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {350		<Owned<T>>::iter_prefix((self.id, account))351			.map(|(id, _)| id)352			.collect()353	}354355	fn collection_tokens(&self) -> Vec<TokenId> {356		<TokenData<T>>::iter_prefix((self.id,))357			.map(|(id, _)| id)358			.collect()359	}360361	fn token_exists(&self, token: TokenId) -> bool {362		<Pallet<T>>::token_exists(self, token)363	}364365	fn last_token_id(&self) -> TokenId {366		TokenId(<TokensMinted<T>>::get(self.id))367	}368369	fn token_owner(&self, _token: TokenId) -> Option<T::CrossAccountId> {370		None371	}372373	fn token_property(&self, _token_id: TokenId, _key: &PropertyKey) -> Option<PropertyValue> {374		None375	}376377	fn token_properties(378		&self,379		_token_id: TokenId,380		_keys: Option<Vec<PropertyKey>>,381	) -> Vec<Property> {382		Vec::new()383	}384385	fn total_supply(&self) -> u32 {386		<Pallet<T>>::total_supply(self)387	}388389	fn account_balance(&self, account: T::CrossAccountId) -> u32 {390		<AccountBalance<T>>::get((self.id, account))391	}392393	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128 {394		<Balance<T>>::get((self.id, token, account))395	}396397	fn allowance(398		&self,399		sender: T::CrossAccountId,400		spender: T::CrossAccountId,401		token: TokenId,402	) -> u128 {403		<Allowance<T>>::get((self.id, token, sender, spender))404	}405}
modifiedpallets/structure/src/lib.rsdiffbeforeafterboth
--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -149,19 +149,12 @@
 		})
 	}
 
-	/// Check if token indirectly owned by specified user
-	pub fn check_indirectly_owned(
-		user: T::CrossAccountId,
+	pub fn get_indirect_owner(
 		collection: CollectionId,
 		token: TokenId,
 		for_nest: Option<(CollectionId, TokenId)>,
 		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)?,
-			None => user,
-		};
-
+	) -> Result<T::CrossAccountId, DispatchError> {
 		// Tried to nest token in itself
 		if Some((collection, token)) == for_nest {
 			return Err(<Error<T>>::OuroborosDetected.into());
@@ -173,10 +166,8 @@
 				Parent::Token(collection, token) if Some((collection, token)) == for_nest => {
 					return Err(<Error<T>>::OuroborosDetected.into())
 				}
-				// Found needed parent, token is indirecty owned
-				Parent::User(user) if user == target_parent => return Ok(true),
 				// Token is owned by other user
-				Parent::User(_) => return Ok(false),
+				Parent::User(user) => return Ok(user),
 				Parent::TokenNotFound => return Err(<Error<T>>::TokenNotFound.into()),
 				// Continue parent chain
 				Parent::Token(_, _) => {}
@@ -199,6 +190,27 @@
 		dispatch.burn_item_recursively(from.clone(), token, self_budget, breadth_budget)
 	}
 
+	/// Check if token indirectly owned by specified user
+	pub fn check_indirectly_owned(
+		user: T::CrossAccountId,
+		collection: CollectionId,
+		token: TokenId,
+		for_nest: Option<(CollectionId, TokenId)>,
+		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)?,
+			None => user,
+		};
+
+		Self::get_indirect_owner(
+			collection,
+			token,
+			for_nest,
+			budget
+		).map(|indirect_owner| indirect_owner == target_parent)
+	}
+
 	pub fn check_nesting(
 		from: T::CrossAccountId,
 		under: &T::CrossAccountId,
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -815,8 +815,9 @@
 		#[transactional]
 		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
+			let budget = budget::Value::new(NESTING_BUDGET);
 
-			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))
+			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount, &budget))
 		}
 
 		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.
modifiedruntime/common/src/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -147,7 +147,11 @@
                     use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
                     use pallet_common::CommonCollectionOperations;
 
-                    let collection_id = RmrkCore::unique_collection_id(collection_id)?;
+                    let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+                        Ok(id) => id,
+                        Err(_) => return Ok(None)
+                    };
+
                     let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Regular) {
                         Ok(c) => c,
                         Err(_) => return Ok(None),
@@ -169,7 +173,10 @@
                     use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
                     use pallet_common::CommonCollectionOperations;
 
-                    let collection_id = RmrkCore::unique_collection_id(collection_id)?;
+                    let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+                        Ok(id) => id,
+                        Err(_) => return Ok(None)
+                    };
                     let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Regular) {
                         Ok(c) => c,
                         Err(_) => return Ok(None),
@@ -180,7 +187,11 @@
 
                     let owner = match collection.token_owner(nft_id) {
                         Some(owner) => match <Runtime as pallet_common::Config>::CrossTokenAddressMapping::address_to_token(&owner) {
-                            Some((col, tok)) => RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(col.0, tok.0),
+                            Some((col, tok)) => {
+                                let rmrk_collection = RmrkCore::rmrk_collection_id(col)?;
+
+                                RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(rmrk_collection, tok.0)
+                            }
                             None => RmrkAccountIdOrCollectionNftTuple::AccountId(owner.as_sub().clone())
                         },
                         None => return Ok(None)
@@ -202,11 +213,11 @@
                     use pallet_common::CommonCollectionOperations;
 
                     let cross_account_id = CrossAccountId::from_sub(account_id);
-                    let collection_id = RmrkCore::unique_collection_id(collection_id)?;
-                    let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Regular) {
-                        Ok(c) => c,
-                        Err(_) => return Ok(Vec::new()),
+                    let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+                        Ok(id) => id,
+                        Err(_) => return Ok(Vec::new())
                     };
+                    if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(Vec::new()); }
 
                     Ok(
                         collection.account_tokens(cross_account_id)
@@ -217,28 +228,35 @@
                 }
 
                 fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
-                    let collection_id = RmrkCore::unique_collection_id(collection_id)?;
+                    let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+                        Ok(id) => id,
+                        Err(_) => return Ok(Vec::new())
+                    };
                     let nft_id = TokenId(nft_id);
                     if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(Vec::new()); }
 
                     Ok(
                         pallet_nonfungible::TokenChildren::<Runtime>::iter_prefix((collection_id, nft_id))
-                            .filter_map(|(child_id, is_child)|
-                                match is_child {
-                                    true => Some(RmrkNftChild {
-                                        collection_id: child_id.0.0,
-                                        nft_id: child_id.1.0,
-                                    }),
-                                    false => None,
-                                }
-                            ).collect()
+                            .filter_map(|((child_collection, child_token), _)| {
+                                let rmrk_child_collection = RmrkCore::rmrk_collection_id(
+                                    child_collection
+                                ).ok()?;
+
+                                Some(RmrkNftChild {
+                                    collection_id: rmrk_child_collection,
+                                    nft_id: child_token.0,
+                                })
+                            }).collect()
                     )
                 }
 
                 fn collection_properties(collection_id: RmrkCollectionId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
                     use pallet_proxy_rmrk_core::misc::CollectionType;
 
-                    let collection_id = RmrkCore::unique_collection_id(collection_id)?;
+                    let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+                        Ok(id) => id,
+                        Err(_) => return Ok(Vec::new())
+                    };
                     if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() {
                         return Ok(Vec::new());
                     }
@@ -259,7 +277,10 @@
                 fn nft_properties(collection_id: RmrkCollectionId, nft_id: RmrkNftId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
                     use pallet_proxy_rmrk_core::misc::NftType;
 
-                    let collection_id = RmrkCore::unique_collection_id(collection_id)?;
+                    let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+                        Ok(id) => id,
+                        Err(_) => return Ok(Vec::new())
+                    };
                     let token_id = TokenId(nft_id);
 
                     if RmrkCore::ensure_nft_type(collection_id, token_id, NftType::Regular).is_err() {
@@ -283,7 +304,10 @@
                     use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType, ResourceType}};
                     use pallet_common::CommonCollectionOperations;
 
-                    let collection_id = RmrkCore::unique_collection_id(collection_id)?;
+                    let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+                        Ok(id) => id,
+                        Err(_) => return Ok(Vec::new())
+                    };
                     if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(Vec::new()); }
 
                     let nft_id = TokenId(nft_id);
@@ -332,7 +356,10 @@
                 fn nft_resource_priorities(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkResourceId>, DispatchError> {
                     use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType}};
 
-                    let collection_id = RmrkCore::unique_collection_id(collection_id)?;
+                    let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+                        Ok(id) => id,
+                        Err(_) => return Ok(Vec::new())
+                    };
                     if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(Vec::new()); }
 
                     let nft_id = TokenId(nft_id);
@@ -360,7 +387,10 @@
                         RmrkProperty, misc::{CollectionType},
                     };
 
-                    let collection_id = RmrkCore::unique_collection_id(base_id)?;
+                    let collection_id = match RmrkCore::unique_collection_id(base_id) {
+                        Ok(id) => id,
+                        Err(_) => return Ok(None)
+                    };
                     let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Base) {
                         Ok(c) => c,
                         Err(_) => return Ok(None),
@@ -377,12 +407,11 @@
                     use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType}};
                     use pallet_common::CommonCollectionOperations;
 
-                    let collection_id = RmrkCore::unique_collection_id(base_id)?;
-                    let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Base) {
-                        Ok(c) => c,
-                        Err(_) => return Ok(Vec::new()),
+                    let collection_id = match RmrkCore::unique_collection_id(base_id) {
+                        Ok(id) => id,
+                        Err(_) => return Ok(Vec::new())
                     };
-
+                    if RmrkCore::ensure_collection_type(collection_id, CollectionType::Base).is_err() { return Ok(Vec::new()); }
 
                     let parts = collection.collection_tokens()
                         .into_iter()
@@ -413,11 +442,13 @@
                     use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
                     use pallet_common::CommonCollectionOperations;
 
-                    let collection_id = RmrkCore::unique_collection_id(base_id)?;
-                    let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Base) {
-                        Ok(c) => c,
-                        Err(_) => return Ok(Vec::new()),
+                    let collection_id = match RmrkCore::unique_collection_id(base_id) {
+                        Ok(id) => id,
+                        Err(_) => return Ok(Vec::new())
                     };
+                    if RmrkCore::ensure_collection_type(collection_id, CollectionType::Base).is_err() {
+                        return Ok(Vec::new());
+                    }
 
 
                     let theme_names = collection.collection_tokens()
@@ -444,11 +475,13 @@
                     };
                     use pallet_common::CommonCollectionOperations;
 
-                    let collection_id = RmrkCore::unique_collection_id(base_id)?;
-                    let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Base) {
-                        Ok(c) => c,
-                        Err(_) => return Ok(None),
+                    let collection_id = match RmrkCore::unique_collection_id(base_id) {
+                        Ok(id) => id,
+                        Err(_) => return Ok(None)
                     };
+                    if RmrkCore::ensure_collection_type(collection_id, CollectionType::Base).is_err() {
+                        return Ok(None);
+                    }
 
                     let theme_info = collection.collection_tokens()
                         .into_iter()