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
before · pallets/proxy-rmrk-core/src/lib.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/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::*;24use pallet_common::{25	Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::Pallet as PalletStructure;29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142#[frame_support::pallet]43pub mod pallet {44	use super::*;45	use pallet_evm::account;4647	#[pallet::config]48	pub trait Config:49		frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config50	{51		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;52	}5354	#[pallet::storage]55	#[pallet::getter(fn collection_index)]56	pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5758	#[pallet::storage]59	#[pallet::getter(fn collection_index_map)]60	pub type CollectionIndexMap<T: Config> =61		StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6263	#[pallet::pallet]64	#[pallet::generate_store(pub(super) trait Store)]65	pub struct Pallet<T>(_);6667	#[pallet::event]68	#[pallet::generate_deposit(pub(super) fn deposit_event)]69	pub enum Event<T: Config> {70		CollectionCreated {71			issuer: T::AccountId,72			collection_id: RmrkCollectionId,73		},74		CollectionDestroyed {75			issuer: T::AccountId,76			collection_id: RmrkCollectionId,77		},78		IssuerChanged {79			old_issuer: T::AccountId,80			new_issuer: T::AccountId,81			collection_id: RmrkCollectionId,82		},83		CollectionLocked {84			issuer: T::AccountId,85			collection_id: RmrkCollectionId,86		},87		NftMinted {88			owner: T::AccountId,89			collection_id: RmrkCollectionId,90			nft_id: RmrkNftId,91		},92		NFTBurned {93			owner: T::AccountId,94			nft_id: RmrkNftId,95		},96		PropertySet {97			collection_id: RmrkCollectionId,98			maybe_nft_id: Option<RmrkNftId>,99			key: RmrkKeyString,100			value: RmrkValueString,101		},102		ResourceAdded {103			nft_id: RmrkNftId,104			resource_id: RmrkResourceId,105		},106		ResourceRemoval {107			nft_id: RmrkNftId,108			resource_id: RmrkResourceId,109		},110	}111112	#[pallet::error]113	pub enum Error<T> {114		/* Unique-specific events */115		CorruptedCollectionType,116		NftTypeEncodeError,117		RmrkPropertyKeyIsTooLong,118		RmrkPropertyValueIsTooLong,119120		/* RMRK compatible events */121		CollectionNotEmpty,122		NoAvailableCollectionId,123		NoAvailableNftId,124		CollectionUnknown,125		NoPermission,126		CollectionFullOrLocked,127		ResourceDoesntExist,128	}129130	#[pallet::call]131	impl<T: Config> Pallet<T> {132		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]133		#[transactional]134		pub fn create_collection(135			origin: OriginFor<T>,136			metadata: RmrkString,137			max: Option<u32>,138			symbol: RmrkCollectionSymbol,139		) -> DispatchResult {140			let sender = ensure_signed(origin)?;141142			let limits = CollectionLimits {143				owner_can_transfer: Some(false),144				token_limit: max,145				..Default::default()146			};147148			let data = CreateCollectionData {149				limits: Some(limits),150				token_prefix: symbol151					.into_inner()152					.try_into()153					.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,154				..Default::default()155			};156157			<CollectionIndex<T>>::mutate(|n| *n += 1);158159			let unique_collection_id = Self::init_collection(160				T::CrossAccountId::from_sub(sender.clone()),161				data,162				[163					Self::rmrk_property(Metadata, &metadata)?,164					Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,165				]166				.into_iter(),167			)?;168			let rmrk_collection_id = <CollectionIndex<T>>::get();169170			<CollectionIndexMap<T>>::insert(rmrk_collection_id, unique_collection_id);171172			Self::deposit_event(Event::CollectionCreated {173				issuer: sender,174				collection_id: rmrk_collection_id,175			});176177			Ok(())178		}179180		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]181		#[transactional]182		pub fn destroy_collection(183			origin: OriginFor<T>,184			collection_id: RmrkCollectionId,185		) -> DispatchResult {186			let sender = ensure_signed(origin)?;187			let cross_sender = T::CrossAccountId::from_sub(sender.clone());188189			let collection = Self::get_typed_nft_collection(190				Self::unique_collection_id(collection_id)?,191				misc::CollectionType::Regular,192			)?;193194			ensure!(195				collection.total_supply() == 0,196				<Error<T>>::CollectionNotEmpty197			);198199			<PalletNft<T>>::destroy_collection(collection, &cross_sender)200				.map_err(Self::map_common_err_to_proxy)?;201202			Self::deposit_event(Event::CollectionDestroyed {203				issuer: sender,204				collection_id,205			});206207			Ok(())208		}209210		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]211		#[transactional]212		pub fn change_collection_issuer(213			origin: OriginFor<T>,214			collection_id: RmrkCollectionId,215			new_issuer: <T::Lookup as StaticLookup>::Source,216		) -> DispatchResult {217			let sender = ensure_signed(origin)?;218219			let new_issuer = T::Lookup::lookup(new_issuer)?;220221			Self::change_collection_owner(222				Self::unique_collection_id(collection_id)?,223				misc::CollectionType::Regular,224				sender.clone(),225				new_issuer.clone(),226			)?;227228			Self::deposit_event(Event::IssuerChanged {229				old_issuer: sender,230				new_issuer,231				collection_id,232			});233234			Ok(())235		}236237		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]238		#[transactional]239		pub fn lock_collection(240			origin: OriginFor<T>,241			collection_id: RmrkCollectionId,242		) -> DispatchResult {243			let sender = ensure_signed(origin)?;244			let cross_sender = T::CrossAccountId::from_sub(sender.clone());245246			let collection = Self::get_typed_nft_collection(247				Self::unique_collection_id(collection_id)?,248				misc::CollectionType::Regular,249			)?;250251			Self::check_collection_owner(&collection, &cross_sender)?;252253			let token_count = collection.total_supply();254255			let mut collection = collection.into_inner();256			collection.limits.token_limit = Some(token_count);257			collection.save()?;258259			Self::deposit_event(Event::CollectionLocked {260				issuer: sender,261				collection_id,262			});263264			Ok(())265		}266267		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]268		#[transactional]269		pub fn mint_nft(270			origin: OriginFor<T>,271			owner: T::AccountId,272			collection_id: RmrkCollectionId,273			recipient: Option<T::AccountId>,274			royalty_amount: Option<Permill>,275			metadata: RmrkString,276		) -> DispatchResult {277			let sender = ensure_signed(origin)?;278			let sender = T::CrossAccountId::from_sub(sender);279			let cross_owner = T::CrossAccountId::from_sub(owner.clone());280281			let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {282				recipient: recipient.unwrap_or_else(|| owner.clone()),283				amount,284			});285286			let collection = Self::get_typed_nft_collection(287				Self::unique_collection_id(collection_id)?,288				misc::CollectionType::Regular,289			)?;290291			let nft_id = Self::create_nft(292				&sender,293				&cross_owner,294				&collection,295				[296					Self::rmrk_property(TokenType, &NftType::Regular)?,297					Self::rmrk_property(RoyaltyInfo, &royalty_info)?,298					Self::rmrk_property(Metadata, &metadata)?,299					Self::rmrk_property(Equipped, &false)?,300					Self::rmrk_property(301						ResourceCollection,302						&Self::init_collection(303							sender.clone(),304							CreateCollectionData {305								..Default::default()306							},307							[Self::rmrk_property(308								CollectionType,309								&misc::CollectionType::Resource,310							)?]311							.into_iter(),312						)?,313					)?, // todo possibly add limits to the collection if rmrk warrants them314					Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,315				]316				.into_iter(),317			)318			.map_err(|err| match err {319				DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),320				err => Self::map_common_err_to_proxy(err),321			})?;322323			Self::deposit_event(Event::NftMinted {324				owner,325				collection_id,326				nft_id: nft_id.0,327			});328329			Ok(())330		}331332		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]333		#[transactional]334		pub fn burn_nft(335			origin: OriginFor<T>,336			collection_id: RmrkCollectionId,337			nft_id: RmrkNftId,338		) -> DispatchResult {339			let sender = ensure_signed(origin)?;340			let cross_sender = T::CrossAccountId::from_sub(sender.clone());341342			Self::destroy_nft(343				cross_sender,344				Self::unique_collection_id(collection_id)?,345				misc::CollectionType::Regular,346				nft_id.into(),347			)?;348349			Self::deposit_event(Event::NFTBurned {350				owner: sender,351				nft_id,352			});353354			Ok(())355		}356357		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]358		#[transactional]359		pub fn set_property(360			origin: OriginFor<T>,361			#[pallet::compact] rmrk_collection_id: RmrkCollectionId,362			maybe_nft_id: Option<RmrkNftId>,363			key: RmrkKeyString,364			value: RmrkValueString,365		) -> DispatchResult {366			let sender = ensure_signed(origin)?;367			let sender = T::CrossAccountId::from_sub(sender);368369			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;370371			match maybe_nft_id {372				Some(nft_id) => {373					let token_id: TokenId = nft_id.into();374375					Self::ensure_nft_owner(collection_id, token_id, &sender)?;376					Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;377378					<PalletNft<T>>::set_scoped_token_property(379						collection_id,380						token_id,381						PropertyScope::Rmrk,382						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,383					)?;384				}385				None => {386					let collection = Self::get_typed_nft_collection(387						collection_id,388						misc::CollectionType::Regular,389					)?;390391					Self::check_collection_owner(&collection, &sender)?;392393					<PalletCommon<T>>::set_scoped_collection_property(394						collection_id,395						PropertyScope::Rmrk,396						Self::rmrk_property(UserProperty(key.as_slice()), &value)?,397					)?;398				}399			}400401			Self::deposit_event(Event::PropertySet {402				collection_id: rmrk_collection_id,403				maybe_nft_id,404				key,405				value,406			});407408			Ok(())409		}410411		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]412		#[transactional]413		pub fn add_basic_resource(414			origin: OriginFor<T>,415			collection_id: RmrkCollectionId,416			nft_id: RmrkNftId,417			resource: RmrkBasicResource,418		) -> DispatchResult {419			let sender = ensure_signed(origin.clone())?;420421			let resource_id = Self::resource_add(422				sender,423				Self::unique_collection_id(collection_id)?,424				nft_id.into(),425				[426					Self::rmrk_property(TokenType, &NftType::Resource)?,427					Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,428					Self::rmrk_property(Src, &resource.src)?,429					Self::rmrk_property(Metadata, &resource.metadata)?,430					Self::rmrk_property(License, &resource.license)?,431					Self::rmrk_property(Thumb, &resource.thumb)?,432				]433				.into_iter(),434			)?;435436			Self::deposit_event(Event::ResourceAdded {437				nft_id,438				resource_id,439			});440			Ok(())441		}442443		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]444		#[transactional]445		pub fn add_composable_resource(446			origin: OriginFor<T>,447			collection_id: RmrkCollectionId,448			nft_id: RmrkNftId,449			_resource_id: RmrkBoundedResource,450			resource: RmrkComposableResource,451		) -> DispatchResult {452			let sender = ensure_signed(origin.clone())?;453454			let resource_id = Self::resource_add(455				sender,456				Self::unique_collection_id(collection_id)?,457				nft_id.into(),458				[459					Self::rmrk_property(TokenType, &NftType::Resource)?,460					Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,461					Self::rmrk_property(Parts, &resource.parts)?,462					Self::rmrk_property(Base, &resource.base)?,463					Self::rmrk_property(Src, &resource.src)?,464					Self::rmrk_property(Metadata, &resource.metadata)?,465					Self::rmrk_property(License, &resource.license)?,466					Self::rmrk_property(Thumb, &resource.thumb)?,467				]468				.into_iter(),469			)?;470471			Self::deposit_event(Event::ResourceAdded {472				nft_id,473				resource_id,474			});475			Ok(())476		}477478		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]479		#[transactional]480		pub fn add_slot_resource(481			origin: OriginFor<T>,482			collection_id: RmrkCollectionId,483			nft_id: RmrkNftId,484			resource: RmrkSlotResource,485		) -> DispatchResult {486			let sender = ensure_signed(origin.clone())?;487488			let resource_id = Self::resource_add(489				sender,490				Self::unique_collection_id(collection_id)?,491				nft_id.into(),492				[493					Self::rmrk_property(TokenType, &NftType::Resource)?,494					Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,495					Self::rmrk_property(Base, &resource.base)?,496					Self::rmrk_property(Src, &resource.src)?,497					Self::rmrk_property(Metadata, &resource.metadata)?,498					Self::rmrk_property(Slot, &resource.slot)?,499					Self::rmrk_property(License, &resource.license)?,500					Self::rmrk_property(Thumb, &resource.thumb)?,501				]502				.into_iter(),503			)?;504505			Self::deposit_event(Event::ResourceAdded {506				nft_id,507				resource_id,508			});509			Ok(())510		}511512		#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]513		#[transactional]514		pub fn remove_resource(515			origin: OriginFor<T>,516			collection_id: RmrkCollectionId,517			nft_id: RmrkNftId,518			resource_id: RmrkResourceId,519		) -> DispatchResult {520			let sender = ensure_signed(origin.clone())?;521522			Self::resource_remove(523				sender,524				Self::unique_collection_id(collection_id)?,525				nft_id.into(),526				resource_id.into(),527			)?;528529			Self::deposit_event(Event::ResourceRemoval {530				nft_id,531				resource_id,532			});533			Ok(())534		}535	}536}537538impl<T: Config> Pallet<T> {539	pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {540		let key = rmrk_key.to_key::<T>()?;541542		let scoped_key = PropertyScope::Rmrk543			.apply(key)544			.map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;545546		Ok(scoped_key)547	}548549	// todo think about renaming these550	pub fn rmrk_property<E: Encode>(551		rmrk_key: RmrkProperty,552		value: &E,553	) -> Result<Property, DispatchError> {554		let key = rmrk_key.to_key::<T>()?;555556		let value = value557			.encode()558			.try_into()559			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;560561		let property = Property { key, value };562563		Ok(property)564	}565566	pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {567		vec.decode()568			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())569	}570571	pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>572	where573		BoundedVec<u8, S>: TryFrom<Vec<u8>>,574	{575		vec.rebind()576			.map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())577	}578579	fn init_collection(580		sender: T::CrossAccountId,581		data: CreateCollectionData<T::AccountId>,582		properties: impl Iterator<Item = Property>,583	) -> Result<CollectionId, DispatchError> {584		let collection_id = <PalletNft<T>>::init_collection(sender, data);585586		if let Err(DispatchError::Arithmetic(_)) = &collection_id {587			return Err(<Error<T>>::NoAvailableCollectionId.into());588		}589590		<PalletCommon<T>>::set_scoped_collection_properties(591			collection_id?,592			PropertyScope::Rmrk,593			properties,594		)?;595596		collection_id597	}598599	pub fn create_nft(600		sender: &T::CrossAccountId,601		owner: &T::CrossAccountId,602		collection: &NonfungibleHandle<T>,603		properties: impl Iterator<Item = Property>,604	) -> Result<TokenId, DispatchError> {605		let data = CreateNftExData {606			properties: BoundedVec::default(),607			owner: owner.clone(),608		};609610		let budget = budget::Value::new(2);611612		<PalletNft<T>>::create_item(collection, sender, data, &budget)?;613614		let nft_id = <PalletNft<T>>::current_token_id(collection.id);615616		<PalletNft<T>>::set_scoped_token_properties(617			collection.id,618			nft_id,619			PropertyScope::Rmrk,620			properties,621		)?;622623		Ok(nft_id)624	}625626	fn destroy_nft(627		sender: T::CrossAccountId,628		collection_id: CollectionId,629		collection_type: misc::CollectionType,630		token_id: TokenId,631	) -> DispatchResult {632		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;633634		<PalletNft<T>>::burn(&collection, &sender, token_id)635			.map_err(Self::map_common_err_to_proxy)?;636637		Ok(())638	}639640	fn resource_add(641		sender: T::AccountId,642		collection_id: CollectionId,643		token_id: TokenId,644		resource_properties: impl Iterator<Item = Property>,645	) -> Result<RmrkResourceId, DispatchError> {646		let collection =647			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;648		ensure!(collection.owner == sender, Error::<T>::NoPermission);649650		// Check NFT lock status // todo depends on market, maybe later651		//ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);652653		let sender = T::CrossAccountId::from_sub(sender);654		let budget = budget::Value::new(10);655		let pending = !<PalletStructure<T>>::check_indirectly_owned(656			sender.clone(),657			collection_id,658			token_id,659			None,660			&budget,661		)?;662663		let resource_collection_id: CollectionId =664			Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;665		let resource_collection =666			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;667668		// todo probably add extra connections to bases, slots, etc., when RMRK starts to use them669670		let resource_id = Self::create_nft(671			&sender, // todo owner of the nft?672			&sender,673			&resource_collection,674			resource_properties.chain(675				[676					Self::rmrk_property(PendingResourceAccept, &pending)?,677					Self::rmrk_property(PendingResourceRemoval, &false)?,678				]679				.into_iter(),680			),681		)682		.map_err(|err| match err {683			DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),684			err => Self::map_common_err_to_proxy(err),685		})?;686687		Ok(resource_id.0)688	}689690	fn resource_remove(691		sender: T::AccountId,692		collection_id: CollectionId,693		nft_id: TokenId,694		resource_id: TokenId,695	) -> DispatchResult {696		let collection =697			Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;698		ensure!(collection.owner == sender, Error::<T>::NoPermission);699700		let resource_collection_id: CollectionId =701			Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;702		let resource_collection =703			Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;704		ensure!(705			<PalletNft<T>>::token_exists(&resource_collection, resource_id),706			Error::<T>::ResourceDoesntExist707		);708709		let budget = up_data_structs::budget::Value::new(10);710		let topmost_owner =711			<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;712713		let sender = T::CrossAccountId::from_sub(sender);714		if topmost_owner == sender {715			<PalletNft<T>>::burn(&collection, &sender, nft_id)716				.map_err(Self::map_common_err_to_proxy)?;717		} else {718			<PalletNft<T>>::set_scoped_token_property(719				collection_id,720				nft_id,721				PropertyScope::Rmrk,722				Self::rmrk_property(PendingResourceRemoval, &true)?,723			)?;724		}725726		Ok(())727	}728729	fn change_collection_owner(730		collection_id: CollectionId,731		collection_type: misc::CollectionType,732		sender: T::AccountId,733		new_owner: T::AccountId,734	) -> DispatchResult {735		let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;736		Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;737738		let mut collection = collection.into_inner();739740		collection.owner = new_owner;741		collection.save()742	}743744	fn check_collection_owner(745		collection: &NonfungibleHandle<T>,746		account: &T::CrossAccountId,747	) -> DispatchResult {748		collection749			.check_is_owner(account)750			.map_err(Self::map_common_err_to_proxy)751	}752753	pub fn last_collection_idx() -> RmrkCollectionId {754		<CollectionIndex<T>>::get()755	}756757	pub fn unique_collection_id(758		rmrk_collection_id: RmrkCollectionId,759	) -> Result<CollectionId, DispatchError> {760		<CollectionIndexMap<T>>::try_get(rmrk_collection_id)761			.map_err(|_| <Error<T>>::CollectionUnknown.into())762	}763764	pub fn get_nft_collection(765		collection_id: CollectionId,766	) -> Result<NonfungibleHandle<T>, DispatchError> {767		let collection = <CollectionHandle<T>>::try_get(collection_id)768			.map_err(|_| <Error<T>>::CollectionUnknown)?;769770		match collection.mode {771			CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),772			_ => Err(<Error<T>>::CollectionUnknown.into()),773		}774	}775776	pub fn collection_exists(collection_id: CollectionId) -> bool {777		<CollectionHandle<T>>::try_get(collection_id).is_ok()778	}779780	pub fn get_collection_property(781		collection_id: CollectionId,782		key: RmrkProperty,783	) -> Result<PropertyValue, DispatchError> {784		let collection_property = <PalletCommon<T>>::collection_properties(collection_id)785			.get(&Self::rmrk_property_key(key)?)786			.ok_or(<Error<T>>::CollectionUnknown)?787			.clone();788789		Ok(collection_property)790	}791792	pub fn get_collection_property_decoded<V: Decode>(793		collection_id: CollectionId,794		key: RmrkProperty,795	) -> Result<V, DispatchError> {796		Self::decode_property(Self::get_collection_property(collection_id, key)?)797	}798799	pub fn get_collection_type(800		collection_id: CollectionId,801	) -> Result<misc::CollectionType, DispatchError> {802		Self::get_collection_property_decoded(collection_id, CollectionType)803			.map_err(|_| <Error<T>>::CorruptedCollectionType.into())804	}805806	pub fn ensure_collection_type(807		collection_id: CollectionId,808		collection_type: misc::CollectionType,809	) -> DispatchResult {810		let actual_type = Self::get_collection_type(collection_id)?;811		ensure!(812			actual_type == collection_type,813			<CommonError<T>>::NoPermission814		);815816		Ok(())817	}818819	pub fn get_typed_nft_collection(820		collection_id: CollectionId,821		collection_type: misc::CollectionType,822	) -> Result<NonfungibleHandle<T>, DispatchError> {823		Self::ensure_collection_type(collection_id, collection_type)?;824825		Self::get_nft_collection(collection_id)826	}827828	pub fn get_nft_property(829		collection_id: CollectionId,830		nft_id: TokenId,831		key: RmrkProperty,832	) -> Result<PropertyValue, DispatchError> {833		let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))834			.get(&Self::rmrk_property_key(key)?)835			.ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?836			.clone();837838		Ok(nft_property)839	}840841	pub fn get_nft_property_decoded<V: Decode>(842		collection_id: CollectionId,843		nft_id: TokenId,844		key: RmrkProperty,845	) -> Result<V, DispatchError> {846		Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)847	}848849	pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {850		<TokenData<T>>::contains_key((collection_id, nft_id))851	}852853	pub fn get_nft_type(854		collection_id: CollectionId,855		token_id: TokenId,856	) -> Result<NftType, DispatchError> {857		Self::get_nft_property_decoded(collection_id, token_id, TokenType)858			.map_err(|_| <Error<T>>::NftTypeEncodeError.into())859	}860861	pub fn ensure_nft_type(862		collection_id: CollectionId,863		token_id: TokenId,864		nft_type: NftType,865	) -> DispatchResult {866		let actual_type = Self::get_nft_type(collection_id, token_id)?;867		ensure!(actual_type == nft_type, <Error<T>>::NoPermission);868869		Ok(())870	}871872	pub fn ensure_nft_owner(873		collection_id: CollectionId,874		token_id: TokenId,875		possible_owner: &T::CrossAccountId,876	) -> DispatchResult {877		let token_data =878			<TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;879880		ensure!(881			token_data.owner == *possible_owner,882			<Error<T>>::NoPermission883		);884885		Ok(())886	}887888	pub fn filter_user_properties<Key, Value, R, Mapper>(889		collection_id: CollectionId,890		token_id: Option<TokenId>,891		filter_keys: Option<Vec<RmrkPropertyKey>>,892		mapper: Mapper,893	) -> Result<Vec<R>, DispatchError>894	where895		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,896		Value: Decode + Default,897		Mapper: Fn(Key, Value) -> R,898	{899		filter_keys900			.map(|keys| {901				let properties = keys902					.into_iter()903					.filter_map(|key| {904						let key: Key = key.try_into().ok()?;905906						let value = match token_id {907							Some(token_id) => Self::get_nft_property_decoded(908								collection_id,909								token_id,910								UserProperty(key.as_ref()),911							),912							None => Self::get_collection_property_decoded(913								collection_id,914								UserProperty(key.as_ref()),915							),916						}917						.ok()?;918919						Some(mapper(key, value))920					})921					.collect();922923				Ok(properties)924			})925			.unwrap_or_else(|| {926				let properties =927					Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();928929				Ok(properties)930			})931	}932933	pub fn iterate_user_properties<Key, Value, R, Mapper>(934		collection_id: CollectionId,935		token_id: Option<TokenId>,936		mapper: Mapper,937	) -> Result<impl Iterator<Item = R>, DispatchError>938	where939		Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,940		Value: Decode + Default,941		Mapper: Fn(Key, Value) -> R,942	{943		let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;944945		let properties = match token_id {946			Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),947			None => <PalletCommon<T>>::collection_properties(collection_id),948		};949950		let properties = properties.into_iter().filter_map(move |(key, value)| {951			let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;952953			let key: Key = key.to_vec().try_into().ok()?;954			let value: Value = value.decode().ok()?;955956			Some(mapper(key, value))957		});958959		Ok(properties)960	}961962	fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {963		map_common_err_to_proxy! {964			match err {965				NoPermission => NoPermission,966				CollectionTokenLimitExceeded => CollectionFullOrLocked,967				PublicMintingNotAllowed => NoPermission,968				TokenNotFound => NoAvailableNftId969			}970		}971	}972}
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -254,6 +254,7 @@
 		spender: T::CrossAccountId,
 		token: TokenId,
 		amount: u128,
+		_nesting_budget: &dyn Budget,
 	) -> DispatchResultWithPostInfo {
 		with_weight(
 			<Pallet<T>>::set_allowance(self, &sender, &spender, token, amount),
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()