git.delta.rocks / unique-network / refs/commits / 9d51561e5bd0

difftreelog

Revert "feat: burn children when destroying a collection"

Daniel Shiposha2022-05-27parent: #2c83d97.patch.diff
in: master
This reverts commit 4b7f4d90a16f3a5ab26bed0511dabae078429724.

12 files changed

modifiedpallets/common/src/dispatch.rsdiffbeforeafterboth
--- a/pallets/common/src/dispatch.rs
+++ b/pallets/common/src/dispatch.rs
@@ -6,7 +6,7 @@
 	weights::Pays,
 	traits::Get,
 };
-use up_data_structs::{CollectionId, CreateCollectionData, budget::Budget};
+use up_data_structs::{CollectionId, CreateCollectionData};
 
 use crate::{pallet::Config, CommonCollectionOperations, CollectionHandle};
 
@@ -57,11 +57,7 @@
 
 pub trait CollectionDispatch<T: Config> {
 	fn create(sender: T::AccountId, data: CreateCollectionData<T::AccountId>) -> DispatchResult;
-	fn destroy(
-		sender: T::CrossAccountId,
-		handle: CollectionHandle<T>,
-		nesting_budget: &dyn Budget,
-	) -> DispatchResult;
+	fn destroy(sender: T::CrossAccountId, handle: CollectionHandle<T>) -> DispatchResult;
 
 	fn dispatch(handle: CollectionHandle<T>) -> Self;
 	fn into_inner(self) -> CollectionHandle<T>;
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
before · pallets/common/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)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::account::CrossAccountId;25use frame_support::{26	dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},27	ensure,28	traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},29	BoundedVec,30	weights::Pays,31	transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35	COLLECTION_NUMBER_LIMIT,36	Collection,37	RpcCollection,38	CollectionId,39	CreateItemData,40	MAX_TOKEN_PREFIX_LENGTH,41	COLLECTION_ADMINS_LIMIT,42	TokenId,43	CollectionStats,44	MAX_TOKEN_OWNERSHIP,45	CollectionMode,46	NFT_SPONSOR_TRANSFER_TIMEOUT,47	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,48	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49	MAX_SPONSOR_TIMEOUT,50	CUSTOM_DATA_LIMIT,51	CollectionLimits,52	CreateCollectionData,53	SponsorshipState,54	CreateItemExData,55	SponsoringRateLimit,56	budget::Budget,57	COLLECTION_FIELD_LIMIT,58	PhantomType,59	Property,60	Properties,61	PropertiesPermissionMap,62	PropertyKey,63	PropertyValue,64	PropertyPermission,65	PropertiesError,66	PropertyKeyPermission,67	TokenData,68	TrySetProperty,69	PropertyScope,70	// RMRK71	RmrkCollectionInfo,72	RmrkInstanceInfo,73	RmrkResourceInfo,74	RmrkPropertyInfo,75	RmrkBaseInfo,76	RmrkPartType,77	RmrkTheme,78	RmrkNftChild,79	CollectionPermissions,80	SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97	pub id: CollectionId,98	collection: Collection<T::AccountId>,99	pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102	fn recorder(&self) -> &SubstrateRecorder<T> {103		&self.recorder104	}105	fn into_recorder(self) -> SubstrateRecorder<T> {106		self.recorder107	}108}109impl<T: Config> CollectionHandle<T> {110	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111		<CollectionById<T>>::get(id).map(|collection| Self {112			id,113			collection,114			recorder: SubstrateRecorder::new(gas_limit),115		})116	}117	pub fn new(id: CollectionId) -> Option<Self> {118		Self::new_with_gas_limit(id, u64::MAX)119	}120	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {121		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)122	}123	pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {124		self.recorder125			.consume_gas(T::GasWeightMapping::weight_to_gas(126				<T as frame_system::Config>::DbWeight::get()127					.read128					.saturating_mul(reads),129			))130	}131	pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {132		self.recorder133			.consume_gas(T::GasWeightMapping::weight_to_gas(134				<T as frame_system::Config>::DbWeight::get()135					.write136					.saturating_mul(writes),137			))138	}139	pub fn save(self) -> DispatchResult {140		<CollectionById<T>>::insert(self.id, self.collection);141		Ok(())142	}143}144impl<T: Config> Deref for CollectionHandle<T> {145	type Target = Collection<T::AccountId>;146147	fn deref(&self) -> &Self::Target {148		&self.collection149	}150}151152impl<T: Config> DerefMut for CollectionHandle<T> {153	fn deref_mut(&mut self) -> &mut Self::Target {154		&mut self.collection155	}156}157158impl<T: Config> CollectionHandle<T> {159	pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {160		ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);161		Ok(())162	}163	pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {164		*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))165	}166	pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {167		ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);168		Ok(())169	}170	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {171		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)172	}173	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {174		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)175	}176	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {177		ensure!(178			<Allowlist<T>>::get((self.id, user)),179			<Error<T>>::AddressNotInAllowlist180		);181		Ok(())182	}183}184185#[frame_support::pallet]186pub mod pallet {187	use super::*;188	use pallet_evm::account;189	use dispatch::CollectionDispatch;190	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};191	use frame_system::pallet_prelude::*;192	use frame_support::traits::Currency;193	use up_data_structs::{TokenId, mapping::TokenAddressMapping};194	use scale_info::TypeInfo;195	use weights::WeightInfo;196197	#[pallet::config]198	pub trait Config:199		frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config200	{201		type WeightInfo: WeightInfo;202		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;203204		type Currency: Currency<Self::AccountId>;205206		#[pallet::constant]207		type CollectionCreationPrice: Get<208			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,209		>;210		type CollectionDispatch: CollectionDispatch<Self>;211212		type TreasuryAccountId: Get<Self::AccountId>;213214		type EvmTokenAddressMapping: TokenAddressMapping<H160>;215		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;216	}217218	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);219220	#[pallet::pallet]221	#[pallet::storage_version(STORAGE_VERSION)]222	#[pallet::generate_store(pub(super) trait Store)]223	pub struct Pallet<T>(_);224225	#[pallet::extra_constants]226	impl<T: Config> Pallet<T> {227		pub fn collection_admins_limit() -> u32 {228			COLLECTION_ADMINS_LIMIT229		}230	}231232	#[pallet::event]233	#[pallet::generate_deposit(pub fn deposit_event)]234	pub enum Event<T: Config> {235		/// New collection was created236		///237		/// # Arguments238		///239		/// * collection_id: Globally unique identifier of newly created collection.240		///241		/// * mode: [CollectionMode] converted into u8.242		///243		/// * account_id: Collection owner.244		CollectionCreated(CollectionId, u8, T::AccountId),245246		/// New collection was destroyed247		///248		/// # Arguments249		///250		/// * collection_id: Globally unique identifier of collection.251		CollectionDestroyed(CollectionId),252253		/// New item was created.254		///255		/// # Arguments256		///257		/// * collection_id: Id of the collection where item was created.258		///259		/// * item_id: Id of an item. Unique within the collection.260		///261		/// * recipient: Owner of newly created item262		///263		/// * amount: Always 1 for NFT264		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),265266		/// Collection item was burned.267		///268		/// # Arguments269		///270		/// * collection_id.271		///272		/// * item_id: Identifier of burned NFT.273		///274		/// * owner: which user has destroyed its tokens275		///276		/// * amount: Always 1 for NFT277		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),278279		/// Item was transferred280		///281		/// * collection_id: Id of collection to which item is belong282		///283		/// * item_id: Id of an item284		///285		/// * sender: Original owner of item286		///287		/// * recipient: New owner of item288		///289		/// * amount: Always 1 for NFT290		Transfer(291			CollectionId,292			TokenId,293			T::CrossAccountId,294			T::CrossAccountId,295			u128,296		),297298		/// * collection_id299		///300		/// * item_id301		///302		/// * sender303		///304		/// * spender305		///306		/// * amount307		Approved(308			CollectionId,309			TokenId,310			T::CrossAccountId,311			T::CrossAccountId,312			u128,313		),314315		CollectionPropertySet(CollectionId, PropertyKey),316317		CollectionPropertyDeleted(CollectionId, PropertyKey),318319		TokenPropertySet(CollectionId, TokenId, PropertyKey),320321		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),322323		PropertyPermissionSet(CollectionId, PropertyKey),324	}325326	#[pallet::error]327	pub enum Error<T> {328		/// This collection does not exist.329		CollectionNotFound,330		/// Sender parameter and item owner must be equal.331		MustBeTokenOwner,332		/// No permission to perform action333		NoPermission,334		/// Collection is not in mint mode.335		PublicMintingNotAllowed,336		/// Address is not in allow list.337		AddressNotInAllowlist,338339		/// Collection name can not be longer than 63 char.340		CollectionNameLimitExceeded,341		/// Collection description can not be longer than 255 char.342		CollectionDescriptionLimitExceeded,343		/// Token prefix can not be longer than 15 char.344		CollectionTokenPrefixLimitExceeded,345		/// Total collections bound exceeded.346		TotalCollectionsLimitExceeded,347		/// Exceeded max admin count348		CollectionAdminCountExceeded,349		/// Collection limit bounds per collection exceeded350		CollectionLimitBoundsExceeded,351		/// Tried to enable permissions which are only permitted to be disabled352		OwnerPermissionsCantBeReverted,353		/// Collection settings not allowing items transferring354		TransferNotAllowed,355		/// Account token limit exceeded per collection356		AccountTokenLimitExceeded,357		/// Collection token limit exceeded358		CollectionTokenLimitExceeded,359		/// Metadata flag frozen360		MetadataFlagFrozen,361362		/// Item not exists.363		TokenNotFound,364		/// Item balance not enough.365		TokenValueTooLow,366		/// Requested value more than approved.367		ApprovedValueTooLow,368		/// Tried to approve more than owned369		CantApproveMoreThanOwned,370371		/// Can't transfer tokens to ethereum zero address372		AddressIsZero,373		/// Target collection doesn't supports this operation374		UnsupportedOperation,375376		/// Not sufficient founds to perform action377		NotSufficientFounds,378379		/// Collection has nesting disabled380		NestingIsDisabled,381		/// Only owner may nest tokens under this collection382		OnlyOwnerAllowedToNest,383		/// Only tokens from specific collections may nest tokens under this384		SourceCollectionIsNotAllowedToNest,385386		/// Tried to store more data than allowed in collection field387		CollectionFieldSizeExceeded,388389		/// Tried to store more property data than allowed390		NoSpaceForProperty,391392		/// Tried to store more property keys than allowed393		PropertyLimitReached,394395		/// Property key is too long396		PropertyKeyIsTooLong,397398		/// Only ASCII letters, digits, and '_', '-' are allowed399		InvalidCharacterInPropertyKey,400401		/// Empty property keys are forbidden402		EmptyPropertyKey,403	}404405	#[pallet::storage]406	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;407	#[pallet::storage]408	pub type DestroyedCollectionCount<T> =409		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;410411	/// Collection info412	#[pallet::storage]413	pub type CollectionById<T> = StorageMap<414		Hasher = Blake2_128Concat,415		Key = CollectionId,416		Value = Collection<<T as frame_system::Config>::AccountId>,417		QueryKind = OptionQuery,418	>;419420	/// Collection properties421	#[pallet::storage]422	#[pallet::getter(fn collection_properties)]423	pub type CollectionProperties<T> = StorageMap<424		Hasher = Blake2_128Concat,425		Key = CollectionId,426		Value = Properties,427		QueryKind = ValueQuery,428		OnEmpty = up_data_structs::CollectionProperties,429	>;430431	#[pallet::storage]432	#[pallet::getter(fn property_permissions)]433	pub type CollectionPropertyPermissions<T> = StorageMap<434		Hasher = Blake2_128Concat,435		Key = CollectionId,436		Value = PropertiesPermissionMap,437		QueryKind = ValueQuery,438	>;439440	#[pallet::storage]441	pub type AdminAmount<T> = StorageMap<442		Hasher = Blake2_128Concat,443		Key = CollectionId,444		Value = u32,445		QueryKind = ValueQuery,446	>;447448	/// List of collection admins449	#[pallet::storage]450	pub type IsAdmin<T: Config> = StorageNMap<451		Key = (452			Key<Blake2_128Concat, CollectionId>,453			Key<Blake2_128Concat, T::CrossAccountId>,454		),455		Value = bool,456		QueryKind = ValueQuery,457	>;458459	/// Allowlisted collection users460	#[pallet::storage]461	pub type Allowlist<T: Config> = StorageNMap<462		Key = (463			Key<Blake2_128Concat, CollectionId>,464			Key<Blake2_128Concat, T::CrossAccountId>,465		),466		Value = bool,467		QueryKind = ValueQuery,468	>;469470	/// Not used by code, exists only to provide some types to metadata471	#[pallet::storage]472	pub type DummyStorageValue<T: Config> = StorageValue<473		Value = (474			CollectionStats,475			CollectionId,476			TokenId,477			PhantomType<TokenData<T::CrossAccountId>>,478			PhantomType<RpcCollection<T::AccountId>>,479			// RMRK480			PhantomType<RmrkCollectionInfo<T::AccountId>>,481			PhantomType<RmrkInstanceInfo<T::AccountId>>,482			PhantomType<RmrkResourceInfo>,483			PhantomType<RmrkPropertyInfo>,484			PhantomType<RmrkBaseInfo<T::AccountId>>,485			PhantomType<RmrkPartType>,486			PhantomType<RmrkTheme>,487			PhantomType<RmrkNftChild>,488		),489		QueryKind = OptionQuery,490	>;491492	#[pallet::hooks]493	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {494		fn on_runtime_upgrade() -> Weight {495			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {496				use up_data_structs::{CollectionVersion1, CollectionVersion2};497				<CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {498					let mut props = Vec::new();499					if !v.offchain_schema.is_empty() {500						props.push(Property {501							key: b"_old_offchainSchema".to_vec().try_into().unwrap(),502							value: v.offchain_schema.clone().into_inner().try_into().expect("offchain schema too big"),503						});504					}505					if !v.variable_on_chain_schema.is_empty() {506						props.push(Property {507							key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),508							value: v.variable_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),509						});510					}511					if !v.const_on_chain_schema.is_empty() {512						props.push(Property {513							key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),514							value: v.const_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),515						});516					}517					props.push(Property {518						key: b"_old_schemaVersion".to_vec().try_into().unwrap(),519						value: match v.schema_version {520							SchemaVersion::ImageURL => b"ImageUrl".as_slice(),521							SchemaVersion::Unique => b"Unique".as_slice(),522						}.to_vec().try_into().unwrap(),523					});524					Self::set_scoped_collection_properties(525						id,526						PropertyScope::None,527						props.into_iter(),528					).expect("existing data larger than properties");529					let mut new = CollectionVersion2::from(v.clone());530					new.permissions.access = Some(v.access);531					new.permissions.mint_mode = Some(v.mint_mode);532					Some(new)533				});534			}535536			0537		}538	}539}540541impl<T: Config> Pallet<T> {542	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens543	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {544		ensure!(545			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,546			<Error<T>>::AddressIsZero547		);548		Ok(())549	}550	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {551		<IsAdmin<T>>::iter_prefix((collection,))552			.map(|(a, _)| a)553			.collect()554	}555	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {556		<Allowlist<T>>::iter_prefix((collection,))557			.map(|(a, _)| a)558			.collect()559	}560	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {561		<Allowlist<T>>::get((collection, user))562	}563	pub fn collection_stats() -> CollectionStats {564		let created = <CreatedCollectionCount<T>>::get();565		let destroyed = <DestroyedCollectionCount<T>>::get();566		CollectionStats {567			created: created.0,568			destroyed: destroyed.0,569			alive: created.0 - destroyed.0,570		}571	}572573	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {574		let collection = <CollectionById<T>>::get(collection);575		if collection.is_none() {576			return None;577		}578579		let collection = collection.unwrap();580		let limits = collection.limits;581		let effective_limits = CollectionLimits {582			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),583			sponsored_data_size: Some(limits.sponsored_data_size()),584			sponsored_data_rate_limit: Some(585				limits586					.sponsored_data_rate_limit587					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),588			),589			token_limit: Some(limits.token_limit()),590			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(591				match collection.mode {592					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,593					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,594					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,595				},596			)),597			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),598			owner_can_transfer: Some(limits.owner_can_transfer()),599			owner_can_destroy: Some(limits.owner_can_destroy()),600			transfers_enabled: Some(limits.transfers_enabled()),601		};602603		Some(effective_limits)604	}605606	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {607		let Collection {608			name,609			description,610			owner,611			mode,612			token_prefix,613			sponsorship,614			limits,615			permissions,616		} = <CollectionById<T>>::get(collection)?;617618		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)619			.into_iter()620			.map(|(key, permission)| PropertyKeyPermission {621				key,622				permission,623			})624			.collect();625626		let properties = <CollectionProperties<T>>::get(collection)627			.into_iter()628			.map(|(key, value)| Property {629				key,630				value,631			})632			.collect();633634		Some(RpcCollection {635			name: name.into_inner(),636			description: description.into_inner(),637			owner,638			mode,639			token_prefix: token_prefix.into_inner(),640			sponsorship,641			limits,642			permissions,643			token_property_permissions,644			properties,645		})646	}647}648649macro_rules! limit_default {650	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{651		$(652			if let Some($new) = $new.$field {653				let $old = $old.$field($($arg)?);654				let _ = $new;655				let _ = $old;656				$check657			} else {658				$new.$field = $old.$field659			}660		)*661	}};662}663macro_rules! limit_default_clone {664	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{665		$(666			if let Some($new) = $new.$field.clone() {667				let $old = $old.$field($($arg)?);668				let _ = $new;669				let _ = $old;670				$check671			} else {672				$new.$field = $old.$field.clone()673			}674		)*675	}};676}677678impl<T: Config> Pallet<T> {679	pub fn init_collection(680		owner: T::AccountId,681		data: CreateCollectionData<T::AccountId>,682	) -> Result<CollectionId, DispatchError> {683		{684			ensure!(685				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,686				Error::<T>::CollectionTokenPrefixLimitExceeded687			);688		}689690		let created_count = <CreatedCollectionCount<T>>::get()691			.0692			.checked_add(1)693			.ok_or(ArithmeticError::Overflow)?;694		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;695		let id = CollectionId(created_count);696697		// bound Total number of collections698		ensure!(699			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,700			<Error<T>>::TotalCollectionsLimitExceeded701		);702703		// =========704705		let collection = Collection {706			owner: owner.clone(),707			name: data.name,708			mode: data.mode.clone(),709			description: data.description,710			token_prefix: data.token_prefix,711			sponsorship: data712				.pending_sponsor713				.map(SponsorshipState::Unconfirmed)714				.unwrap_or_default(),715			limits: data716				.limits717				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))718				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,719			permissions: data720				.permissions721				.map(|permissions| Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions))722				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,723		};724725		let mut collection_properties = up_data_structs::CollectionProperties::get();726		collection_properties727			.try_set_from_iter(data.properties.into_iter())728			.map_err(<Error<T>>::from)?;729730		CollectionProperties::<T>::insert(id, collection_properties);731732		let mut token_props_permissions = PropertiesPermissionMap::new();733		token_props_permissions734			.try_set_from_iter(data.token_property_permissions.into_iter())735			.map_err(<Error<T>>::from)?;736737		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);738739		// Take a (non-refundable) deposit of collection creation740		{741			let mut imbalance =742				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();743			imbalance.subsume(744				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(745					&T::TreasuryAccountId::get(),746					T::CollectionCreationPrice::get(),747				),748			);749			<T as Config>::Currency::settle(750				&owner,751				imbalance,752				WithdrawReasons::TRANSFER,753				ExistenceRequirement::KeepAlive,754			)755			.map_err(|_| Error::<T>::NotSufficientFounds)?;756		}757758		<CreatedCollectionCount<T>>::put(created_count);759		<Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));760		<CollectionById<T>>::insert(id, collection);761		Ok(id)762	}763764	pub fn destroy_collection(765		collection: CollectionHandle<T>,766		sender: &T::CrossAccountId,767	) -> DispatchResult {768		ensure!(769			collection.limits.owner_can_destroy(),770			<Error<T>>::NoPermission,771		);772		collection.check_is_owner(sender)?;773774		let destroyed_collections = <DestroyedCollectionCount<T>>::get()775			.0776			.checked_add(1)777			.ok_or(ArithmeticError::Overflow)?;778779		// =========780781		<DestroyedCollectionCount<T>>::put(destroyed_collections);782		<CollectionById<T>>::remove(collection.id);783		<AdminAmount<T>>::remove(collection.id);784		<IsAdmin<T>>::remove_prefix((collection.id,), None);785		<Allowlist<T>>::remove_prefix((collection.id,), None);786		<CollectionProperties<T>>::remove(collection.id);787788		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));789		Ok(())790	}791792	pub fn set_collection_property(793		collection: &CollectionHandle<T>,794		sender: &T::CrossAccountId,795		property: Property,796	) -> DispatchResult {797		collection.check_is_owner_or_admin(sender)?;798799		CollectionProperties::<T>::try_mutate(collection.id, |properties| {800			let property = property.clone();801			properties.try_set(property.key, property.value)802		})803		.map_err(<Error<T>>::from)?;804805		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));806807		Ok(())808	}809810	pub fn set_scoped_collection_property(811		collection_id: CollectionId,812		scope: PropertyScope,813		property: Property,814	) -> DispatchResult {815		CollectionProperties::<T>::try_mutate(collection_id, |properties| {816			properties.try_scoped_set(scope, property.key, property.value)817		})818		.map_err(<Error<T>>::from)?;819820		Ok(())821	}822823	pub fn set_scoped_collection_properties(824		collection_id: CollectionId,825		scope: PropertyScope,826		properties: impl Iterator<Item = Property>,827	) -> DispatchResult {828		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {829			stored_properties.try_scoped_set_from_iter(scope, properties)830		})831		.map_err(<Error<T>>::from)?;832833		Ok(())834	}835836	#[transactional]837	pub fn set_collection_properties(838		collection: &CollectionHandle<T>,839		sender: &T::CrossAccountId,840		properties: Vec<Property>,841	) -> DispatchResult {842		for property in properties {843			Self::set_collection_property(collection, sender, property)?;844		}845846		Ok(())847	}848849	pub fn delete_collection_property(850		collection: &CollectionHandle<T>,851		sender: &T::CrossAccountId,852		property_key: PropertyKey,853	) -> DispatchResult {854		collection.check_is_owner_or_admin(sender)?;855856		CollectionProperties::<T>::try_mutate(collection.id, |properties| {857			properties.remove(&property_key)858		})859		.map_err(<Error<T>>::from)?;860861		Self::deposit_event(Event::CollectionPropertyDeleted(862			collection.id,863			property_key,864		));865866		Ok(())867	}868869	#[transactional]870	pub fn delete_collection_properties(871		collection: &CollectionHandle<T>,872		sender: &T::CrossAccountId,873		property_keys: Vec<PropertyKey>,874	) -> DispatchResult {875		for key in property_keys {876			Self::delete_collection_property(collection, sender, key)?;877		}878879		Ok(())880	}881882	// For migrations883	pub fn set_property_permission_unchecked(884		collection: CollectionId,885		property_permission: PropertyKeyPermission,886	) -> DispatchResult {887		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {888			permissions.try_set(property_permission.key, property_permission.permission)889		})890		.map_err(<Error<T>>::from)?;891		Ok(())892	}893894	pub fn set_property_permission(895		collection: &CollectionHandle<T>,896		sender: &T::CrossAccountId,897		property_permission: PropertyKeyPermission,898	) -> DispatchResult {899		collection.check_is_owner_or_admin(sender)?;900901		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);902		let current_permission = all_permissions.get(&property_permission.key);903		if matches![904			current_permission,905			Some(PropertyPermission { mutable: false, .. })906		] {907			return Err(<Error<T>>::NoPermission.into());908		}909910		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {911			let property_permission = property_permission.clone();912			permissions.try_set(property_permission.key, property_permission.permission)913		})914		.map_err(<Error<T>>::from)?;915916		Self::deposit_event(Event::PropertyPermissionSet(917			collection.id,918			property_permission.key,919		));920921		Ok(())922	}923924	#[transactional]925	pub fn set_property_permissions(926		collection: &CollectionHandle<T>,927		sender: &T::CrossAccountId,928		property_permissions: Vec<PropertyKeyPermission>,929	) -> DispatchResult {930		for prop_pemission in property_permissions {931			Self::set_property_permission(collection, sender, prop_pemission)?;932		}933934		Ok(())935	}936937	pub fn get_collection_property(938		collection_id: CollectionId,939		key: &PropertyKey,940	) -> Option<PropertyValue> {941		Self::collection_properties(collection_id).get(key).cloned()942	}943944	pub fn bytes_keys_to_property_keys(945		keys: Vec<Vec<u8>>,946	) -> Result<Vec<PropertyKey>, DispatchError> {947		keys.into_iter()948			.map(|key| -> Result<PropertyKey, DispatchError> {949				key.try_into()950					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())951			})952			.collect::<Result<Vec<PropertyKey>, DispatchError>>()953	}954955	pub fn filter_collection_properties(956		collection_id: CollectionId,957		keys: Option<Vec<PropertyKey>>,958	) -> Result<Vec<Property>, DispatchError> {959		let properties = Self::collection_properties(collection_id);960961		let properties = keys962			.map(|keys| {963				keys.into_iter()964					.filter_map(|key| {965						properties.get(&key).map(|value| Property {966							key,967							value: value.clone(),968						})969					})970					.collect()971			})972			.unwrap_or_else(|| {973				properties974					.into_iter()975					.map(|(key, value)| Property {976						key,977						value,978					})979					.collect()980			});981982		Ok(properties)983	}984985	pub fn filter_property_permissions(986		collection_id: CollectionId,987		keys: Option<Vec<PropertyKey>>,988	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {989		let permissions = Self::property_permissions(collection_id);990991		let key_permissions = keys992			.map(|keys| {993				keys.into_iter()994					.filter_map(|key| {995						permissions996							.get(&key)997							.map(|permission| PropertyKeyPermission {998								key,999								permission: permission.clone(),1000							})1001					})1002					.collect()1003			})1004			.unwrap_or_else(|| {1005				permissions1006					.into_iter()1007					.map(|(key, permission)| PropertyKeyPermission {1008						key,1009						permission,1010					})1011					.collect()1012			});10131014		Ok(key_permissions)1015	}10161017	pub fn toggle_allowlist(1018		collection: &CollectionHandle<T>,1019		sender: &T::CrossAccountId,1020		user: &T::CrossAccountId,1021		allowed: bool,1022	) -> DispatchResult {1023		collection.check_is_owner_or_admin(sender)?;10241025		// =========10261027		if allowed {1028			<Allowlist<T>>::insert((collection.id, user), true);1029		} else {1030			<Allowlist<T>>::remove((collection.id, user));1031		}10321033		Ok(())1034	}10351036	pub fn toggle_admin(1037		collection: &CollectionHandle<T>,1038		sender: &T::CrossAccountId,1039		user: &T::CrossAccountId,1040		admin: bool,1041	) -> DispatchResult {1042		collection.check_is_owner_or_admin(sender)?;10431044		let was_admin = <IsAdmin<T>>::get((collection.id, user));1045		if was_admin == admin {1046			return Ok(());1047		}1048		let amount = <AdminAmount<T>>::get(collection.id);10491050		if admin {1051			let amount = amount1052				.checked_add(1)1053				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1054			ensure!(1055				amount <= Self::collection_admins_limit(),1056				<Error<T>>::CollectionAdminCountExceeded,1057			);10581059			// =========10601061			<AdminAmount<T>>::insert(collection.id, amount);1062			<IsAdmin<T>>::insert((collection.id, user), true);1063		} else {1064			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1065			<IsAdmin<T>>::remove((collection.id, user));1066		}10671068		Ok(())1069	}10701071	pub fn clamp_limits(1072		mode: CollectionMode,1073		old_limit: &CollectionLimits,1074		mut new_limit: CollectionLimits,1075	) -> Result<CollectionLimits, DispatchError> {1076		limit_default!(old_limit, new_limit,1077			account_token_ownership_limit => ensure!(1078				new_limit <= MAX_TOKEN_OWNERSHIP,1079				<Error<T>>::CollectionLimitBoundsExceeded,1080			),1081			sponsor_transfer_timeout(match mode {1082				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1083				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1084				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1085			}) => ensure!(1086				new_limit <= MAX_SPONSOR_TIMEOUT,1087				<Error<T>>::CollectionLimitBoundsExceeded,1088			),1089			sponsored_data_size => ensure!(1090				new_limit <= CUSTOM_DATA_LIMIT,1091				<Error<T>>::CollectionLimitBoundsExceeded,1092			),1093			token_limit => ensure!(1094				old_limit >= new_limit && new_limit > 0,1095				<Error<T>>::CollectionTokenLimitExceeded1096			),1097			owner_can_transfer => ensure!(1098				old_limit || !new_limit,1099				<Error<T>>::OwnerPermissionsCantBeReverted,1100			),1101			owner_can_destroy => ensure!(1102				old_limit || !new_limit,1103				<Error<T>>::OwnerPermissionsCantBeReverted,1104			),1105			sponsored_data_rate_limit => {},1106			transfers_enabled => {},1107		);1108		Ok(new_limit)1109	}1110	pub fn clamp_permissions(1111		mode: CollectionMode,1112		old_limit: &CollectionPermissions,1113		mut new_limit: CollectionPermissions,1114	) -> Result<CollectionPermissions, DispatchError> {1115		limit_default_clone!(old_limit, new_limit,1116		);1117		Ok(new_limit)1118	}1119}11201121#[macro_export]1122macro_rules! unsupported {1123	() => {1124		Err(<Error<T>>::UnsupportedOperation.into())1125	};1126}11271128/// Worst cases1129pub trait CommonWeightInfo<CrossAccountId> {1130	fn create_item() -> Weight;1131	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1132	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1133	fn burn_item() -> Weight;1134	fn set_collection_properties(amount: u32) -> Weight;1135	fn delete_collection_properties(amount: u32) -> Weight;1136	fn set_token_properties(amount: u32) -> Weight;1137	fn delete_token_properties(amount: u32) -> Weight;1138	fn set_property_permissions(amount: u32) -> Weight;1139	fn transfer() -> Weight;1140	fn approve() -> Weight;1141	fn transfer_from() -> Weight;1142	fn burn_from() -> Weight;1143}11441145pub trait CommonCollectionOperations<T: Config> {1146	fn create_item(1147		&self,1148		sender: T::CrossAccountId,1149		to: T::CrossAccountId,1150		data: CreateItemData,1151		nesting_budget: &dyn Budget,1152	) -> DispatchResultWithPostInfo;1153	fn create_multiple_items(1154		&self,1155		sender: T::CrossAccountId,1156		to: T::CrossAccountId,1157		data: Vec<CreateItemData>,1158		nesting_budget: &dyn Budget,1159	) -> DispatchResultWithPostInfo;1160	fn create_multiple_items_ex(1161		&self,1162		sender: T::CrossAccountId,1163		data: CreateItemExData<T::CrossAccountId>,1164		nesting_budget: &dyn Budget,1165	) -> DispatchResultWithPostInfo;1166	fn burn_item(1167		&self,1168		sender: T::CrossAccountId,1169		token: TokenId,1170		amount: u128,1171	) -> DispatchResultWithPostInfo;1172	fn burn_item_unchecked(1173		&self,1174		owner: &T::CrossAccountId,1175		token: TokenId,1176		amount: u128,1177	) -> DispatchResult;1178	fn set_collection_properties(1179		&self,1180		sender: T::CrossAccountId,1181		properties: Vec<Property>,1182	) -> DispatchResultWithPostInfo;1183	fn delete_collection_properties(1184		&self,1185		sender: &T::CrossAccountId,1186		property_keys: Vec<PropertyKey>,1187	) -> DispatchResultWithPostInfo;1188	fn set_token_properties(1189		&self,1190		sender: T::CrossAccountId,1191		token_id: TokenId,1192		property: Vec<Property>,1193	) -> DispatchResultWithPostInfo;1194	fn delete_token_properties(1195		&self,1196		sender: T::CrossAccountId,1197		token_id: TokenId,1198		property_keys: Vec<PropertyKey>,1199	) -> DispatchResultWithPostInfo;1200	fn set_property_permissions(1201		&self,1202		sender: &T::CrossAccountId,1203		property_permissions: Vec<PropertyKeyPermission>,1204	) -> DispatchResultWithPostInfo;1205	fn transfer(1206		&self,1207		sender: T::CrossAccountId,1208		to: T::CrossAccountId,1209		token: TokenId,1210		amount: u128,1211		nesting_budget: &dyn Budget,1212	) -> DispatchResultWithPostInfo;1213	fn approve(1214		&self,1215		sender: T::CrossAccountId,1216		spender: T::CrossAccountId,1217		token: TokenId,1218		amount: u128,1219	) -> DispatchResultWithPostInfo;1220	fn transfer_from(1221		&self,1222		sender: T::CrossAccountId,1223		from: T::CrossAccountId,1224		to: T::CrossAccountId,1225		token: TokenId,1226		amount: u128,1227		nesting_budget: &dyn Budget,1228	) -> DispatchResultWithPostInfo;1229	fn burn_from(1230		&self,1231		sender: T::CrossAccountId,1232		from: T::CrossAccountId,1233		token: TokenId,1234		amount: u128,1235		nesting_budget: &dyn Budget,1236	) -> DispatchResultWithPostInfo;12371238	fn check_nesting(1239		&self,1240		sender: T::CrossAccountId,1241		from: (CollectionId, TokenId),1242		under: TokenId,1243		budget: &dyn Budget,1244	) -> DispatchResult;12451246	fn nest(1247		&self,1248		_under: TokenId,1249		_to_nest: (CollectionId, TokenId)1250	) {}12511252	fn unnest(1253		&self,1254		_under: TokenId,1255		_to_nest: (CollectionId, TokenId)1256	) {}12571258	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1259	fn collection_tokens(&self) -> Vec<TokenId>;1260	fn token_exists(&self, token: TokenId) -> bool;1261	fn last_token_id(&self) -> TokenId;12621263	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1264	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1265	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1266	/// Amount of unique collection tokens1267	fn total_supply(&self) -> u32;1268	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1269	fn account_balance(&self, account: T::CrossAccountId) -> u32;1270	/// Amount of specific token account have (Applicable to fungible/refungible)1271	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1272	fn allowance(1273		&self,1274		sender: T::CrossAccountId,1275		spender: T::CrossAccountId,1276		token: TokenId,1277	) -> u128;1278}12791280// Flexible enough for implementing CommonCollectionOperations1281pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1282	let post_info = PostDispatchInfo {1283		actual_weight: Some(weight),1284		pays_fee: Pays::Yes,1285	};1286	match res {1287		Ok(()) => Ok(post_info),1288		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1289	}1290}12911292impl<T: Config> From<PropertiesError> for Error<T> {1293	fn from(error: PropertiesError) -> Self {1294		match error {1295			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1296			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1297			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1298			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1299			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1300		}1301	}1302}
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -170,17 +170,6 @@
 		)
 	}
 
-	fn burn_item_unchecked(
-		&self,
-		owner: &T::CrossAccountId,
-		_token: TokenId,
-		amount: u128,
-	) -> sp_runtime::DispatchResult {
-		<Pallet<T>>::burn_item_unchecked(self, owner, amount)?;
-
-		Ok(())
-	}
-
 	fn transfer(
 		&self,
 		from: T::CrossAccountId,
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -160,36 +160,6 @@
 		owner: &T::CrossAccountId,
 		amount: u128,
 	) -> DispatchResult {
-		if collection.access == AccessMode::AllowList {
-			collection.check_allowlist(owner)?;
-		}
-
-		// =========
-
-		Self::burn_item_unchecked(collection, owner, amount)?;
-
-		<PalletEvm<T>>::deposit_log(
-			ERC20Events::Transfer {
-				from: *owner.as_eth(),
-				to: H160::default(),
-				value: amount.into(),
-			}
-			.to_log(collection_id_to_address(collection.id)),
-		);
-		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(
-			collection.id,
-			TokenId::default(),
-			owner.clone(),
-			amount,
-		));
-		Ok(())
-	}
-
-	pub fn burn_item_unchecked(
-		collection: &FungibleHandle<T>,
-		owner: &T::CrossAccountId,
-		amount: u128,
-	) -> DispatchResult {
 		let total_supply = <TotalSupply<T>>::get(collection.id)
 			.checked_sub(amount)
 			.ok_or(<CommonError<T>>::TokenValueTooLow)?;
@@ -216,6 +186,20 @@
 		}
 		<TotalSupply<T>>::insert(collection.id, total_supply);
 
+		<PalletEvm<T>>::deposit_log(
+			ERC20Events::Transfer {
+				from: *owner.as_eth(),
+				to: H160::default(),
+				value: amount.into(),
+			}
+			.to_log(collection_id_to_address(collection.id)),
+		);
+		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(
+			collection.id,
+			TokenId::default(),
+			owner.clone(),
+			amount,
+		));
 		Ok(())
 	}
 
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -264,19 +264,6 @@
 		}
 	}
 
-	fn burn_item_unchecked(
-		&self,
-		owner:& T::CrossAccountId,
-		token: TokenId,
-		amount: u128,
-	) -> sp_runtime::DispatchResult {
-		if amount == 1 {
-			<Pallet<T>>::burn_item_unchecked(self, owner, token)
-		} else {
-			Ok(())
-		}
-	}
-
 	fn transfer(
 		&self,
 		from: T::CrossAccountId,
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -27,7 +27,6 @@
 use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
 use pallet_common::{
 	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,
-	dispatch::CollectionDispatch,
 	eth::collection_id_to_address,
 };
 use pallet_structure::Pallet as PalletStructure;
@@ -80,8 +79,6 @@
 		NonfungibleItemsHaveNoAmount,
 		/// Unable to burn NFT with children
 		CantBurnNftWithChildren,
-		/// Too many children to burn when destroying a collection
-		TooManyChildrenToBurn,
 	}
 
 	#[pallet::config]
@@ -293,14 +290,13 @@
 	pub fn destroy_collection(
 		collection: NonfungibleHandle<T>,
 		sender: &T::CrossAccountId,
-		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
 		let id = collection.id;
 
 		// =========
 
-		Self::burn_children_in_collection(id, nesting_budget)?;
 		PalletCommon::destroy_collection(collection.0, sender)?;
+
 		<TokenData<T>>::remove_prefix((id,), None);
 		<TokenChildren<T>>::remove_prefix((id,), None);
 		<Owned<T>>::remove_prefix((id,), None);
@@ -308,47 +304,9 @@
 		<TokensBurnt<T>>::remove(id);
 		<Allowance<T>>::remove_prefix((id,), None);
 		<AccountBalance<T>>::remove_prefix((id,), None);
-		Ok(())
-	}
-
-	#[transactional]
-	fn burn_children_in_collection(collection_id: CollectionId, nesting_budget: &dyn Budget) -> DispatchResult {
-		for (parent_id, child) in <TokenChildren<T>>::drain_prefix((collection_id,))
-			.map(|((parent_id, child), _)| (parent_id, child)) {
-
-			let parent_address = T::CrossTokenAddressMapping::token_to_address(collection_id, parent_id);
-			Self::burn_tree(parent_address, child.0, child.1, nesting_budget)?;
-		}
-
 		Ok(())
 	}
 
-	fn burn_tree(
-		parent: T::CrossAccountId,
-		collection_id: CollectionId,
-		token_id: TokenId,
-		nesting_budget: &dyn Budget
-	) -> DispatchResult {
-		if !nesting_budget.consume() {
-			return Err(<Error<T>>::TooManyChildrenToBurn.into());
-		}
-
-		let handle = <CollectionHandle<T>>::try_get(collection_id)?;
-		let handle = T::CollectionDispatch::dispatch(handle);
-		let handle = handle.as_dyn();
-
-		let amount = handle.balance(parent.clone(), token_id);
-
-		handle.burn_item_unchecked(&parent, token_id, amount)?;
-
-		for child in <TokenChildren<T>>::drain_prefix((collection_id, token_id)).map(|(child, _)| child) {
-			let parent = T::CrossTokenAddressMapping::token_to_address(collection_id, token_id);
-			Self::burn_tree(parent, child.0, child.1, nesting_budget)?;
-		}
-
-		Ok(())
-	}
-
 	pub fn burn(
 		collection: &NonfungibleHandle<T>,
 		sender: &T::CrossAccountId,
@@ -370,11 +328,31 @@
 			return Err(<Error<T>>::CantBurnNftWithChildren.into());
 		}
 
-		let old_spender = <Allowance<T>>::get((collection.id, token));
+		let burnt = <TokensBurnt<T>>::get(collection.id)
+			.checked_add(1)
+			.ok_or(ArithmeticError::Overflow)?;
+
+		let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))
+			.checked_sub(1)
+			.ok_or(ArithmeticError::Overflow)?;
+
+		if balance == 0 {
+			<AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));
+		} else {
+			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);
+		}
+
+		if let Some(owner) = T::CrossTokenAddressMapping::address_to_token(&token_data.owner) {
+			Self::unnest(owner, (collection.id, token));
+		}
 
 		// =========
 
-		Self::burn_item_unchecked(collection, &token_data.owner, token)?;
+		<Owned<T>>::remove((collection.id, &token_data.owner, token));
+		<TokensBurnt<T>>::insert(collection.id, burnt);
+		<TokenData<T>>::remove((collection.id, token));
+		<TokenProperties<T>>::remove((collection.id, token));
+		let old_spender = <Allowance<T>>::take((collection.id, token));
 
 		if let Some(old_spender) = old_spender {
 			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(
@@ -400,40 +378,6 @@
 			token_data.owner,
 			1,
 		));
-		Ok(())
-	}
-
-	pub fn burn_item_unchecked(
-		collection: &NonfungibleHandle<T>,
-		owner: &T::CrossAccountId,
-		token: TokenId,
-	) -> DispatchResult {
-		let burnt = <TokensBurnt<T>>::get(collection.id)
-			.checked_add(1)
-			.ok_or(ArithmeticError::Overflow)?;
-
-		let balance = <AccountBalance<T>>::get((collection.id, owner.clone()))
-			.checked_sub(1)
-			.ok_or(ArithmeticError::Overflow)?;
-
-		// =========
-
-		if let Some(owner) = T::CrossTokenAddressMapping::address_to_token(owner) {
-			Self::unnest(owner, (collection.id, token));
-		}
-
-		if balance == 0 {
-			<AccountBalance<T>>::remove((collection.id, owner.clone()));
-		} else {
-			<AccountBalance<T>>::insert((collection.id, owner.clone()), balance);
-		}
-
-		<Owned<T>>::remove((collection.id, owner, token));
-		<TokensBurnt<T>>::insert(collection.id, burnt);
-		<TokenData<T>>::remove((collection.id, token));
-		<TokenProperties<T>>::remove((collection.id, token));
-		<Allowance<T>>::remove((collection.id, token));
-
 		Ok(())
 	}
 
modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/lib.rs
+++ b/pallets/proxy-rmrk-core/src/lib.rs
@@ -179,8 +179,7 @@
 
             ensure!(collection.total_supply() == 0, <Error<T>>::CollectionNotEmpty);
 
-            let empty_budget = budget::Value::new(0);
-            <PalletNft<T>>::destroy_collection(collection, &cross_sender, &empty_budget)
+            <PalletNft<T>>::destroy_collection(collection, &cross_sender)
                 .map_err(Self::map_common_err_to_proxy)?;
 
             Self::deposit_event(Event::CollectionDestroyed { issuer: sender, collection_id });
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -205,15 +205,6 @@
 		)
 	}
 
-	fn burn_item_unchecked(
-		&self,
-		owner: &T::CrossAccountId,
-		token: TokenId,
-		amount: u128,
-	) -> sp_runtime::DispatchResult {
-		<Pallet<T>>::burn_item_unchecked(self, owner, token, amount)
-	}
-
 	fn transfer(
 		&self,
 		from: T::CrossAccountId,
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -245,25 +245,6 @@
 		token: TokenId,
 		amount: u128,
 	) -> DispatchResult {
-		Self::burn_item_unchecked(collection, owner, token, amount)?;
-
-		// TODO: ERC20 transfer event
-		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(
-			collection.id,
-			token,
-			owner.clone(),
-			amount,
-		));
-
-		Ok(())
-	}
-
-	pub fn burn_item_unchecked(
-		collection: &RefungibleHandle<T>,
-		owner: &T::CrossAccountId,
-		token: TokenId,
-		amount: u128,
-	) -> DispatchResult {
 		let total_supply = <TotalSupply<T>>::get((collection.id, token))
 			.checked_sub(amount)
 			.ok_or(<CommonError<T>>::TokenValueTooLow)?;
@@ -318,6 +299,13 @@
 			<Balance<T>>::insert((collection.id, token, owner), balance);
 		}
 		<TotalSupply<T>>::insert((collection.id, token), total_supply);
+		// TODO: ERC20 transfer event
+		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(
+			collection.id,
+			token,
+			owner.clone(),
+			amount,
+		));
 		Ok(())
 	}
 
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -332,24 +332,15 @@
 		/// # Arguments
 		///
 		/// * collection_id: collection to destroy.
-		#[weight =
-			<SelfWeightOf<T>>::destroy_collection()
-			+ <SelfWeightOf<T>>::burn_children_in_collection(*max_children_to_burn)
-		]
+		#[weight = <SelfWeightOf<T>>::destroy_collection()]
 		#[transactional]
-		pub fn destroy_collection(
-			origin,
-			collection_id: CollectionId,
-			max_children_to_burn: u32,
-		) -> DispatchResult {
+		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
 
-			let budget = budget::Value::new(max_children_to_burn);
-
 			// =========
 
-			T::CollectionDispatch::destroy(sender, collection, &budget)?;
+			T::CollectionDispatch::destroy(sender, collection)?;
 
 			<NftTransferBasket<T>>::remove_prefix(collection_id, None);
 			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);
modifiedpallets/unique/src/weights.rsdiffbeforeafterboth
--- a/pallets/unique/src/weights.rs
+++ b/pallets/unique/src/weights.rs
@@ -34,7 +34,6 @@
 pub trait WeightInfo {
 	fn create_collection() -> Weight;
 	fn destroy_collection() -> Weight;
-	fn burn_children_in_collection(max: u32) -> Weight;
 	fn add_to_allow_list() -> Weight;
 	fn remove_from_allow_list() -> Weight;
 	fn set_public_access_mode() -> Weight;
@@ -74,12 +73,6 @@
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(5 as Weight))
 	}
-
-	fn burn_children_in_collection(max: u32) -> Weight {
-		// TODO
-		(50_000_000 as Weight).saturating_mul(max as Weight)
-	}
-
 	// Storage: Common CollectionById (r:1 w:0)
 	// Storage: Common Allowlist (r:0 w:1)
 	fn add_to_allow_list() -> Weight {
@@ -199,12 +192,6 @@
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(5 as Weight))
 	}
-
-	fn burn_children_in_collection(max: u32) -> Weight {
-		// TODO
-		(50_000_000 as Weight).saturating_mul(max as Weight)
-	}
-
 	// Storage: Common CollectionById (r:1 w:0)
 	// Storage: Common Allowlist (r:0 w:1)
 	fn add_to_allow_list() -> Weight {
modifiedruntime/common/src/dispatch.rsdiffbeforeafterboth
--- a/runtime/common/src/dispatch.rs
+++ b/runtime/common/src/dispatch.rs
@@ -1,4 +1,4 @@
-use frame_support::{dispatch::{DispatchResult}, ensure};
+use frame_support::{dispatch::DispatchResult, ensure};
 use pallet_evm::PrecompileResult;
 use sp_core::{H160, U256};
 use sp_std::{borrow::ToOwned, vec::Vec};
@@ -12,7 +12,6 @@
 use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle, erc::RefungibleTokenHandle};
 use up_data_structs::{
 	CollectionMode, CreateCollectionData, MAX_DECIMAL_POINTS, mapping::TokenAddressMapping,
-	budget::Budget,
 };
 
 pub enum CollectionDispatchT<T>
@@ -47,11 +46,7 @@
 		Ok(())
 	}
 
-	fn destroy(
-		sender: T::CrossAccountId,
-		collection: CollectionHandle<T>,
-		nesting_budget: &dyn Budget,
-	) -> DispatchResult {
+	fn destroy(sender: T::CrossAccountId, collection: CollectionHandle<T>) -> DispatchResult {
 		match collection.mode {
 			CollectionMode::ReFungible => {
 				PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?
@@ -60,11 +55,7 @@
 				PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?
 			}
 			CollectionMode::NFT => {
-				PalletNonfungible::destroy_collection(
-					NonfungibleHandle::cast(collection),
-					&sender,
-					nesting_budget,
-				)?
+				PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?
 			}
 		}
 		Ok(())