git.delta.rocks / unique-network / refs/commits / 7e808da4a3d9

difftreelog

fix revert set_allowance

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

8 files changed

modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -1298,7 +1298,6 @@
 		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,7 +226,6 @@
 		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,15 +308,14 @@
 		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), nesting_budget)
+				<Pallet<T>>::set_allowance(self, &sender, token, Some(&spender))
 			} else {
-				<Pallet<T>>::set_allowance(self, &sender, token, None, nesting_budget)
+				<Pallet<T>>::set_allowance(self, &sender, token, None)
 			},
 			<CommonWeights<T>>::approve(),
 		)
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -269,11 +269,8 @@
 		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), &budget)
+		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))
 			.map_err(dispatch_to_evm::<T>)?;
 		Ok(())
 	}
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
before · pallets/nonfungible/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 erc::ERC721Events;20use evm_coder::ToLog;21use frame_support::{22	BoundedVec, ensure, fail, transactional,23	storage::with_transaction,24	pallet_prelude::DispatchResultWithPostInfo,25	pallet_prelude::Weight,26	weights::{PostDispatchInfo, Pays},27};28use up_data_structs::{29	AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,30	mapping::TokenAddressMapping, NestingRule, budget::Budget, Property, PropertyPermission,31	PropertyKey, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, TokenChild,32};33use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};34use pallet_common::{35	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,36	eth::collection_id_to_address,37};38use pallet_structure::{Pallet as PalletStructure, Error as StructureError};39use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};40use sp_core::H160;41use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};42use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap, collections::btree_set::BTreeSet};43use core::ops::Deref;44use codec::{Encode, Decode, MaxEncodedLen};45use scale_info::TypeInfo;4647pub use pallet::*;48use weights::WeightInfo;49#[cfg(feature = "runtime-benchmarks")]50pub mod benchmarking;51pub mod common;52pub mod erc;53pub mod weights;5455pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;56pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;5758#[struct_versioning::versioned(version = 2, upper)]59#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]60pub struct ItemData<CrossAccountId> {61	#[version(..2)]62	pub const_data: BoundedVec<u8, CustomDataLimit>,6364	#[version(..2)]65	pub variable_data: BoundedVec<u8, CustomDataLimit>,6667	pub owner: CrossAccountId,68}6970#[frame_support::pallet]71pub mod pallet {72	use super::*;73	use frame_support::{74		Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, traits::StorageVersion,75	};76	use frame_system::pallet_prelude::*;77	use up_data_structs::{CollectionId, TokenId};78	use super::weights::WeightInfo;7980	#[pallet::error]81	pub enum Error<T> {82		/// Not Nonfungible item data used to mint in Nonfungible collection.83		NotNonfungibleDataUsedToMintFungibleCollectionToken,84		/// Used amount > 1 with NFT85		NonfungibleItemsHaveNoAmount,86		/// Unable to burn NFT with children87		CantBurnNftWithChildren,88	}8990	#[pallet::config]91	pub trait Config:92		frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config93	{94		type WeightInfo: WeightInfo;95	}9697	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);9899	#[pallet::pallet]100	#[pallet::storage_version(STORAGE_VERSION)]101	#[pallet::generate_store(pub(super) trait Store)]102	pub struct Pallet<T>(_);103104	#[pallet::storage]105	pub type TokensMinted<T: Config> =106		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;107	#[pallet::storage]108	pub type TokensBurnt<T: Config> =109		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;110111	#[pallet::storage]112	pub type TokenData<T: Config> = StorageNMap<113		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),114		Value = ItemData<T::CrossAccountId>,115		QueryKind = OptionQuery,116	>;117118	#[pallet::storage]119	#[pallet::getter(fn token_properties)]120	pub type TokenProperties<T: Config> = StorageNMap<121		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),122		Value = Properties,123		QueryKind = ValueQuery,124		OnEmpty = up_data_structs::TokenProperties,125	>;126127	/// Used to enumerate tokens owned by account128	#[pallet::storage]129	pub type Owned<T: Config> = StorageNMap<130		Key = (131			Key<Twox64Concat, CollectionId>,132			Key<Blake2_128Concat, T::CrossAccountId>,133			Key<Twox64Concat, TokenId>,134		),135		Value = bool,136		QueryKind = ValueQuery,137	>;138139	/// Used to enumerate token's children140	#[pallet::storage]141	#[pallet::getter(fn token_children)]142	pub type TokenChildren<T: Config> = StorageNMap<143		Key = (144			Key<Twox64Concat, CollectionId>,145			Key<Twox64Concat, TokenId>,146			Key<Twox64Concat, (CollectionId, TokenId)>,147		),148		Value = bool,149		QueryKind = ValueQuery,150	>;151152	#[pallet::storage]153	pub type AccountBalance<T: Config> = StorageNMap<154		Key = (155			Key<Twox64Concat, CollectionId>,156			Key<Blake2_128Concat, T::CrossAccountId>,157		),158		Value = u32,159		QueryKind = ValueQuery,160	>;161162	#[pallet::storage]163	pub type Allowance<T: Config> = StorageNMap<164		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),165		Value = T::CrossAccountId,166		QueryKind = OptionQuery,167	>;168169	#[pallet::hooks]170	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {171		fn on_runtime_upgrade() -> Weight {172			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {173				let mut had_consts = BTreeSet::new();174				<TokenData<T>>::translate::<ItemDataVersion1<T::CrossAccountId>, _>(175					|(collection, token), v| {176						let mut props = vec![];177						if !v.const_data.is_empty() {178							props.push(Property {179								key: b"_old_constData".to_vec().try_into().unwrap(),180								value: v181									.const_data182									.clone()183									.into_inner()184									.try_into()185									.expect("const too long"),186							});187							had_consts.insert(collection);188						}189						if !v.variable_data.is_empty() {190							props.push(Property {191								key: b"_old_variableData".to_vec().try_into().unwrap(),192								value: v193									.variable_data194									.clone()195									.into_inner()196									.try_into()197									.expect("variable too long"),198							})199						}200						if !props.is_empty() {201							Self::set_scoped_token_properties(202								collection,203								token,204								PropertyScope::None,205								props.into_iter(),206							)207							.expect("existing token data exceeds property storage");208						}209						Some(<ItemDataVersion2<T::CrossAccountId>>::from(v))210					},211				);212				for collection in had_consts {213					<PalletCommon<T>>::set_property_permission_unchecked(214						collection,215						PropertyKeyPermission {216							key: b"_old_constData".to_vec().try_into().unwrap(),217							permission: PropertyPermission {218								mutable: false,219								collection_admin: true,220								token_owner: false,221							},222						},223					)224					.expect("failed to configure permission");225				}226			}227228			0229		}230	}231}232233pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);234impl<T: Config> NonfungibleHandle<T> {235	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {236		Self(inner)237	}238	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {239		self.0240	}241	pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {242		&mut self.0243	}244}245impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {246	fn recorder(&self) -> &SubstrateRecorder<T> {247		self.0.recorder()248	}249	fn into_recorder(self) -> SubstrateRecorder<T> {250		self.0.into_recorder()251	}252}253impl<T: Config> Deref for NonfungibleHandle<T> {254	type Target = pallet_common::CollectionHandle<T>;255256	fn deref(&self) -> &Self::Target {257		&self.0258	}259}260261impl<T: Config> Pallet<T> {262	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {263		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)264	}265	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {266		<TokenData<T>>::contains_key((collection.id, token))267	}268269	pub fn set_scoped_token_property(270		collection_id: CollectionId,271		token_id: TokenId,272		scope: PropertyScope,273		property: Property,274	) -> DispatchResult {275		TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {276			properties.try_scoped_set(scope, property.key, property.value)277		})278		.map_err(<CommonError<T>>::from)?;279280		Ok(())281	}282283	pub fn set_scoped_token_properties(284		collection_id: CollectionId,285		token_id: TokenId,286		scope: PropertyScope,287		properties: impl Iterator<Item = Property>,288	) -> DispatchResult {289		TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {290			stored_properties.try_scoped_set_from_iter(scope, properties)291		})292		.map_err(<CommonError<T>>::from)?;293294		Ok(())295	}296297	pub fn current_token_id(collection_id: CollectionId) -> TokenId {298		TokenId(<TokensMinted<T>>::get(collection_id))299	}300}301302// unchecked calls skips any permission checks303impl<T: Config> Pallet<T> {304	pub fn init_collection(305		owner: T::CrossAccountId,306		data: CreateCollectionData<T::AccountId>,307	) -> Result<CollectionId, DispatchError> {308		<PalletCommon<T>>::init_collection(owner, data)309	}310	pub fn destroy_collection(311		collection: NonfungibleHandle<T>,312		sender: &T::CrossAccountId,313	) -> DispatchResult {314		let id = collection.id;315316		if Self::collection_has_tokens(id) {317			return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());318		}319320		// =========321322		PalletCommon::destroy_collection(collection.0, sender)?;323324		<TokenData<T>>::remove_prefix((id,), None);325		<TokenChildren<T>>::remove_prefix((id,), None);326		<Owned<T>>::remove_prefix((id,), None);327		<TokensMinted<T>>::remove(id);328		<TokensBurnt<T>>::remove(id);329		<Allowance<T>>::remove_prefix((id,), None);330		<AccountBalance<T>>::remove_prefix((id,), None);331		Ok(())332	}333334	pub fn burn(335		collection: &NonfungibleHandle<T>,336		sender: &T::CrossAccountId,337		token: TokenId,338	) -> DispatchResult {339		let token_data =340			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;341		ensure!(342			&token_data.owner == sender343				|| (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(sender)),344			<CommonError<T>>::NoPermission345		);346347		if collection.permissions.access() == AccessMode::AllowList {348			collection.check_allowlist(sender)?;349		}350351		if Self::token_has_children(collection.id, token) {352			return Err(<Error<T>>::CantBurnNftWithChildren.into());353		}354355		let burnt = <TokensBurnt<T>>::get(collection.id)356			.checked_add(1)357			.ok_or(ArithmeticError::Overflow)?;358359		let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))360			.checked_sub(1)361			.ok_or(ArithmeticError::Overflow)?;362363		// =========364365		if balance == 0 {366			<AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));367		} else {368			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);369		}370371		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);372373		<Owned<T>>::remove((collection.id, &token_data.owner, token));374		<TokensBurnt<T>>::insert(collection.id, burnt);375		<TokenData<T>>::remove((collection.id, token));376		<TokenProperties<T>>::remove((collection.id, token));377		let old_spender = <Allowance<T>>::take((collection.id, token));378379		if let Some(old_spender) = old_spender {380			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(381				collection.id,382				token,383				token_data.owner.clone(),384				old_spender,385				0,386			));387		}388389		<PalletEvm<T>>::deposit_log(390			ERC721Events::Transfer {391				from: *token_data.owner.as_eth(),392				to: H160::default(),393				token_id: token.into(),394			}395			.to_log(collection_id_to_address(collection.id)),396		);397		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(398			collection.id,399			token,400			token_data.owner,401			1,402		));403		Ok(())404	}405406	#[transactional]407	pub fn burn_recursively(408		collection: &NonfungibleHandle<T>,409		sender: &T::CrossAccountId,410		token: TokenId,411		self_budget: &dyn Budget,412		breadth_budget: &dyn Budget,413	) -> DispatchResultWithPostInfo {414		ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);415416		let current_token_account =417			T::CrossTokenAddressMapping::token_to_address(collection.id, token);418419		let mut weight = 0 as Weight;420421		// This method is transactional, if user in fact doesn't have permissions to remove token -422		// tokens removed here will be restored after rejected transaction423		for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {424			ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);425			let PostDispatchInfo { actual_weight, .. } =426				<PalletStructure<T>>::burn_item_recursively(427					current_token_account.clone(),428					collection,429					token,430					self_budget,431					breadth_budget,432				)?;433			if let Some(actual_weight) = actual_weight {434				weight = weight.saturating_add(actual_weight);435			}436		}437438		Self::burn(collection, sender, token)?;439		DispatchResultWithPostInfo::Ok(PostDispatchInfo {440			actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),441			pays_fee: Pays::Yes,442		})443	}444445	pub fn set_token_property(446		collection: &NonfungibleHandle<T>,447		sender: &T::CrossAccountId,448		token_id: TokenId,449		property: Property,450	) -> DispatchResult {451		Self::check_token_change_permission(collection, sender, token_id, &property.key)?;452453		<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {454			let property = property.clone();455			properties.try_set(property.key, property.value)456		})457		.map_err(<CommonError<T>>::from)?;458459		<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(460			collection.id,461			token_id,462			property.key,463		));464465		Ok(())466	}467468	#[transactional]469	pub fn set_token_properties(470		collection: &NonfungibleHandle<T>,471		sender: &T::CrossAccountId,472		token_id: TokenId,473		properties: Vec<Property>,474	) -> DispatchResult {475		for property in properties {476			Self::set_token_property(collection, sender, token_id, property)?;477		}478479		Ok(())480	}481482	pub fn delete_token_property(483		collection: &NonfungibleHandle<T>,484		sender: &T::CrossAccountId,485		token_id: TokenId,486		property_key: PropertyKey,487	) -> DispatchResult {488		Self::check_token_change_permission(collection, sender, token_id, &property_key)?;489490		<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {491			properties.remove(&property_key)492		})493		.map_err(<CommonError<T>>::from)?;494495		<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(496			collection.id,497			token_id,498			property_key,499		));500501		Ok(())502	}503504	fn check_token_change_permission(505		collection: &NonfungibleHandle<T>,506		sender: &T::CrossAccountId,507		token_id: TokenId,508		property_key: &PropertyKey,509	) -> DispatchResult {510		let permission = <PalletCommon<T>>::property_permissions(collection.id)511			.get(property_key)512			.cloned()513			.unwrap_or_else(PropertyPermission::none);514515		let token_data = <TokenData<T>>::get((collection.id, token_id))516			.ok_or(<CommonError<T>>::TokenNotFound)?;517518		let check_token_owner = || -> DispatchResult {519			ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);520			Ok(())521		};522523		let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))524			.get(property_key)525			.is_some();526527		match permission {528			PropertyPermission { mutable: false, .. } if is_property_exists => {529				Err(<CommonError<T>>::NoPermission.into())530			}531532			PropertyPermission {533				collection_admin,534				token_owner,535				..536			} => {537				let mut check_result = Err(<CommonError<T>>::NoPermission.into());538539				if collection_admin {540					check_result = collection.check_is_owner_or_admin(sender);541				}542543				if token_owner {544					check_result.or_else(|_| check_token_owner())545				} else {546					check_result547				}548			}549		}550	}551552	#[transactional]553	pub fn delete_token_properties(554		collection: &NonfungibleHandle<T>,555		sender: &T::CrossAccountId,556		token_id: TokenId,557		property_keys: Vec<PropertyKey>,558	) -> DispatchResult {559		for key in property_keys {560			Self::delete_token_property(collection, sender, token_id, key)?;561		}562563		Ok(())564	}565566	pub fn set_collection_properties(567		collection: &NonfungibleHandle<T>,568		sender: &T::CrossAccountId,569		properties: Vec<Property>,570	) -> DispatchResult {571		<PalletCommon<T>>::set_collection_properties(collection, sender, properties)572	}573574	pub fn delete_collection_properties(575		collection: &CollectionHandle<T>,576		sender: &T::CrossAccountId,577		property_keys: Vec<PropertyKey>,578	) -> DispatchResult {579		<PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)580	}581582	pub fn set_property_permissions(583		collection: &CollectionHandle<T>,584		sender: &T::CrossAccountId,585		property_permissions: Vec<PropertyKeyPermission>,586	) -> DispatchResult {587		<PalletCommon<T>>::set_property_permissions(collection, sender, property_permissions)588	}589590	pub fn set_property_permission(591		collection: &CollectionHandle<T>,592		sender: &T::CrossAccountId,593		permission: PropertyKeyPermission,594	) -> DispatchResult {595		<PalletCommon<T>>::set_property_permission(collection, sender, permission)596	}597598	pub fn transfer(599		collection: &NonfungibleHandle<T>,600		from: &T::CrossAccountId,601		to: &T::CrossAccountId,602		token: TokenId,603		nesting_budget: &dyn Budget,604	) -> DispatchResult {605		ensure!(606			collection.limits.transfers_enabled(),607			<CommonError<T>>::TransferNotAllowed608		);609610		let token_data =611			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;612		// TODO: require sender to be token, owner, require admins to go through transfer_from613		ensure!(614			&token_data.owner == from615				|| (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(from)),616			<CommonError<T>>::NoPermission617		);618619		if collection.permissions.access() == AccessMode::AllowList {620			collection.check_allowlist(from)?;621			collection.check_allowlist(to)?;622		}623		<PalletCommon<T>>::ensure_correct_receiver(to)?;624625		let balance_from = <AccountBalance<T>>::get((collection.id, from))626			.checked_sub(1)627			.ok_or(<CommonError<T>>::TokenValueTooLow)?;628		let balance_to = if from != to {629			let balance_to = <AccountBalance<T>>::get((collection.id, to))630				.checked_add(1)631				.ok_or(ArithmeticError::Overflow)?;632633			ensure!(634				balance_to < collection.limits.account_token_ownership_limit(),635				<CommonError<T>>::AccountTokenLimitExceeded,636			);637638			Some(balance_to)639		} else {640			None641		};642643		<PalletStructure<T>>::nest_if_sent_to_token(644			from.clone(),645			to,646			collection.id,647			token,648			nesting_budget,649		)?;650651		// =========652653		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);654655		<TokenData<T>>::insert(656			(collection.id, token),657			ItemData {658				owner: to.clone(),659				..token_data660			},661		);662663		if let Some(balance_to) = balance_to {664			// from != to665			if balance_from == 0 {666				<AccountBalance<T>>::remove((collection.id, from));667			} else {668				<AccountBalance<T>>::insert((collection.id, from), balance_from);669			}670			<AccountBalance<T>>::insert((collection.id, to), balance_to);671			<Owned<T>>::remove((collection.id, from, token));672			<Owned<T>>::insert((collection.id, to, token), true);673		}674		Self::set_allowance_unchecked(collection, from, token, None, true);675676		<PalletEvm<T>>::deposit_log(677			ERC721Events::Transfer {678				from: *from.as_eth(),679				to: *to.as_eth(),680				token_id: token.into(),681			}682			.to_log(collection_id_to_address(collection.id)),683		);684		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(685			collection.id,686			token,687			from.clone(),688			to.clone(),689			1,690		));691		Ok(())692	}693694	pub fn create_multiple_items(695		collection: &NonfungibleHandle<T>,696		sender: &T::CrossAccountId,697		data: Vec<CreateItemData<T>>,698		nesting_budget: &dyn Budget,699	) -> DispatchResult {700		if !collection.is_owner_or_admin(sender) {701			ensure!(702				collection.permissions.mint_mode(),703				<CommonError<T>>::PublicMintingNotAllowed704			);705			collection.check_allowlist(sender)?;706707			for item in data.iter() {708				collection.check_allowlist(&item.owner)?;709			}710		}711712		for data in data.iter() {713			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;714		}715716		let first_token = <TokensMinted<T>>::get(collection.id);717		let tokens_minted = first_token718			.checked_add(data.len() as u32)719			.ok_or(ArithmeticError::Overflow)?;720		ensure!(721			tokens_minted <= collection.limits.token_limit(),722			<CommonError<T>>::CollectionTokenLimitExceeded723		);724725		let mut balances = BTreeMap::new();726		for data in &data {727			let balance = balances728				.entry(&data.owner)729				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));730			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;731732			ensure!(733				*balance <= collection.limits.account_token_ownership_limit(),734				<CommonError<T>>::AccountTokenLimitExceeded,735			);736		}737738		for (i, data) in data.iter().enumerate() {739			let token = TokenId(first_token + i as u32 + 1);740741			<PalletStructure<T>>::check_nesting(742				sender.clone(),743				&data.owner,744				collection.id,745				token,746				nesting_budget,747			)?;748		}749750		// =========751752		with_transaction(|| {753			for (i, data) in data.iter().enumerate() {754				let token = first_token + i as u32 + 1;755756				<TokenData<T>>::insert(757					(collection.id, token),758					ItemData {759						// const_data: data.const_data.clone(),760						owner: data.owner.clone(),761					},762				);763764				<PalletStructure<T>>::nest_if_sent_to_token_unchecked(765					&data.owner,766					collection.id,767					TokenId(token),768				);769770				if let Err(e) = Self::set_token_properties(771					collection,772					sender,773					TokenId(token),774					data.properties.clone().into_inner(),775				) {776					return TransactionOutcome::Rollback(Err(e));777				}778			}779			TransactionOutcome::Commit(Ok(()))780		})?;781782		<TokensMinted<T>>::insert(collection.id, tokens_minted);783		for (account, balance) in balances {784			<AccountBalance<T>>::insert((collection.id, account), balance);785		}786		for (i, data) in data.into_iter().enumerate() {787			let token = first_token + i as u32 + 1;788			<Owned<T>>::insert((collection.id, &data.owner, token), true);789790			<PalletEvm<T>>::deposit_log(791				ERC721Events::Transfer {792					from: H160::default(),793					to: *data.owner.as_eth(),794					token_id: token.into(),795				}796				.to_log(collection_id_to_address(collection.id)),797			);798			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(799				collection.id,800				TokenId(token),801				data.owner.clone(),802				1,803			));804		}805		Ok(())806	}807808	pub fn set_allowance_unchecked(809		collection: &NonfungibleHandle<T>,810		sender: &T::CrossAccountId,811		token: TokenId,812		spender: Option<&T::CrossAccountId>,813		assume_implicit_eth: bool,814	) {815		if let Some(spender) = spender {816			let old_spender = <Allowance<T>>::get((collection.id, token));817			<Allowance<T>>::insert((collection.id, token), spender);818			// In ERC721 there is only one possible approved user of token, so we set819			// approved user to spender820			<PalletEvm<T>>::deposit_log(821				ERC721Events::Approval {822					owner: *sender.as_eth(),823					approved: *spender.as_eth(),824					token_id: token.into(),825				}826				.to_log(collection_id_to_address(collection.id)),827			);828			// In Unique chain, any token can have any amount of approved users, so we need to829			// set allowance of old owner to 0, and allowance of new owner to 1830			if old_spender.as_ref() != Some(spender) {831				if let Some(old_owner) = old_spender {832					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(833						collection.id,834						token,835						sender.clone(),836						old_owner,837						0,838					));839				}840				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(841					collection.id,842					token,843					sender.clone(),844					spender.clone(),845					1,846				));847			}848		} else {849			let old_spender = <Allowance<T>>::take((collection.id, token));850			if !assume_implicit_eth {851				// In ERC721 there is only one possible approved user of token, so we set852				// approved user to zero address853				<PalletEvm<T>>::deposit_log(854					ERC721Events::Approval {855						owner: *sender.as_eth(),856						approved: H160::default(),857						token_id: token.into(),858					}859					.to_log(collection_id_to_address(collection.id)),860				);861			}862			// In Unique chain, any token can have any amount of approved users, so we need to863			// set allowance of old owner to 0864			if let Some(old_spender) = old_spender {865				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(866					collection.id,867					token,868					sender.clone(),869					old_spender,870					0,871				));872			}873		}874	}875876	pub fn set_allowance(877		collection: &NonfungibleHandle<T>,878		sender: &T::CrossAccountId,879		token: TokenId,880		spender: Option<&T::CrossAccountId>,881		nesting_budget: &dyn Budget,882	) -> DispatchResult {883		if collection.permissions.access() == AccessMode::AllowList {884			collection.check_allowlist(sender)?;885			if let Some(spender) = spender {886				collection.check_allowlist(spender)?;887			}888		}889890		if let Some(spender) = spender {891			<PalletCommon<T>>::ensure_correct_receiver(spender)?;892		}893894		let is_owned = <PalletStructure<T>>::check_indirectly_owned(895			sender.clone(),896			collection.id,897			token,898			None,899			nesting_budget900		)?;901902		if !is_owned {903			ensure!(904				collection.ignores_owned_amount(sender),905				<CommonError<T>>::CantApproveMoreThanOwned906			);907		}908909		// =========910911		Self::set_allowance_unchecked(collection, sender, token, spender, false);912		Ok(())913	}914915	fn check_allowed(916		collection: &NonfungibleHandle<T>,917		spender: &T::CrossAccountId,918		from: &T::CrossAccountId,919		token: TokenId,920		nesting_budget: &dyn Budget,921	) -> DispatchResult {922		if spender.conv_eq(from) {923			return Ok(());924		}925		if collection.permissions.access() == AccessMode::AllowList {926			// `from`, `to` checked in [`transfer`]927			collection.check_allowlist(spender)?;928		}929		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {930			return Ok(());931		}932		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {933			// TODO: should collection owner be allowed to perform this transfer?934			ensure!(935				<PalletStructure<T>>::check_indirectly_owned(936					spender.clone(),937					source.0,938					source.1,939					None,940					nesting_budget941				)?,942				<CommonError<T>>::ApprovedValueTooLow,943			);944			return Ok(());945		}946		ensure!(947			collection.ignores_allowance(spender),948			<CommonError<T>>::ApprovedValueTooLow949		);950		Ok(())951	}952953	pub fn transfer_from(954		collection: &NonfungibleHandle<T>,955		spender: &T::CrossAccountId,956		from: &T::CrossAccountId,957		to: &T::CrossAccountId,958		token: TokenId,959		nesting_budget: &dyn Budget,960	) -> DispatchResult {961		Self::check_allowed(collection, spender, from, token, nesting_budget)?;962963		// =========964965		// Allowance is reset in [`transfer`]966		Self::transfer(collection, from, to, token, nesting_budget)967	}968969	pub fn burn_from(970		collection: &NonfungibleHandle<T>,971		spender: &T::CrossAccountId,972		from: &T::CrossAccountId,973		token: TokenId,974		nesting_budget: &dyn Budget,975	) -> DispatchResult {976		Self::check_allowed(collection, spender, from, token, nesting_budget)?;977978		// =========979980		Self::burn(collection, from, token)981	}982983	pub fn check_nesting(984		handle: &NonfungibleHandle<T>,985		sender: T::CrossAccountId,986		from: (CollectionId, TokenId),987		under: TokenId,988		nesting_budget: &dyn Budget,989	) -> DispatchResult {990		fn ensure_sender_allowed<T: Config>(991			collection: CollectionId,992			token: TokenId,993			for_nest: (CollectionId, TokenId),994			sender: T::CrossAccountId,995			budget: &dyn Budget,996		) -> DispatchResult {997			ensure!(998				<PalletStructure<T>>::check_indirectly_owned(999					sender,1000					collection,1001					token,1002					Some(for_nest),1003					budget1004				)?,1005				<CommonError<T>>::OnlyOwnerAllowedToNest,1006			);1007			Ok(())1008		}1009		match handle.permissions.nesting() {1010			NestingRule::Disabled => fail!(<CommonError<T>>::NestingIsDisabled),1011			NestingRule::Owner => {1012				ensure_sender_allowed::<T>(handle.id, under, from, sender, nesting_budget)?1013			}1014			NestingRule::OwnerRestricted(whitelist) => {1015				ensure!(1016					whitelist.contains(&from.0),1017					<CommonError<T>>::SourceCollectionIsNotAllowedToNest1018				);1019				ensure_sender_allowed::<T>(handle.id, under, from, sender, nesting_budget)?1020			}1021			NestingRule::Permissive => {}1022		}1023		Ok(())1024	}10251026	fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {1027		<TokenChildren<T>>::insert((under.0, under.1, (to_nest.0, to_nest.1)), true);1028	}10291030	fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {1031		<TokenChildren<T>>::remove((under.0, under.1, to_unnest));1032	}10331034	fn collection_has_tokens(collection_id: CollectionId) -> bool {1035		<TokenData<T>>::iter_prefix((collection_id,))1036			.next()1037			.is_some()1038	}10391040	fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {1041		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1042			.next()1043			.is_some()1044	}10451046	pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {1047		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1048			.map(|((child_collection_id, child_id), _)| TokenChild {1049				collection: child_collection_id,1050				token: child_id,1051			})1052			.collect()1053	}10541055	/// Delegated to `create_multiple_items`1056	pub fn create_item(1057		collection: &NonfungibleHandle<T>,1058		sender: &T::CrossAccountId,1059		data: CreateItemData<T>,1060		nesting_budget: &dyn Budget,1061	) -> DispatchResult {1062		Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1063	}1064}
modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/lib.rs
+++ b/pallets/proxy-rmrk-core/src/lib.rs
@@ -414,13 +414,14 @@
 					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)?;
+						// FIXME
+						// <PalletNft<T>>::set_allowance(
+						// 	&collection,
+						// 	&cross_sender,
+						// 	nft_id,
+						// 	Some(&spender),
+						// 	&budget
+						// ).map_err(Self::map_common_err_to_proxy)?;
 
 						return Ok(());
 					}
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -254,7 +254,6 @@
 		spender: T::CrossAccountId,
 		token: TokenId,
 		amount: u128,
-		_nesting_budget: &dyn Budget,
 	) -> DispatchResultWithPostInfo {
 		with_weight(
 			<Pallet<T>>::set_allowance(self, &sender, &spender, token, amount),
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -815,9 +815,8 @@
 		#[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, &budget))
+			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))
 		}
 
 		/// 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.