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

difftreelog

Merge branch 'develop' into feature/CORE-302-ss58Format

Igor Kozyrev2022-06-08parents: #0362d33 #c557492.patch.diff
in: master

17 files changed

modifiedpallets/common/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/common/src/benchmarking.rs
+++ b/pallets/common/src/benchmarking.rs
@@ -20,8 +20,8 @@
 use frame_benchmarking::{benchmarks, account};
 use up_data_structs::{
 	CollectionMode, CreateCollectionData, CollectionId, Property, PropertyKey, PropertyValue,
-	MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,
-	OFFCHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, MAX_PROPERTIES_PER_ITEM,
+	CollectionPermissions, NestingRule, MAX_COLLECTION_NAME_LENGTH,
+	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, MAX_PROPERTIES_PER_ITEM,
 };
 use frame_support::{
 	traits::{Currency, Get},
@@ -74,15 +74,15 @@
 }
 
 pub fn create_collection_raw<T: Config, R>(
-	owner: T::AccountId,
+	owner: T::CrossAccountId,
 	mode: CollectionMode,
 	handler: impl FnOnce(
-		T::AccountId,
+		T::CrossAccountId,
 		CreateCollectionData<T::AccountId>,
 	) -> Result<CollectionId, DispatchError>,
 	cast: impl FnOnce(CollectionHandle<T>) -> R,
 ) -> Result<R, DispatchError> {
-	T::Currency::deposit_creating(&owner, T::CollectionCreationPrice::get());
+	<T as Config>::Currency::deposit_creating(&owner.as_sub(), T::CollectionCreationPrice::get());
 	let name = create_u16_data::<MAX_COLLECTION_NAME_LENGTH>();
 	let description = create_u16_data::<MAX_COLLECTION_DESCRIPTION_LENGTH>();
 	let token_prefix = create_data::<MAX_TOKEN_PREFIX_LENGTH>();
@@ -93,13 +93,19 @@
 			name,
 			description,
 			token_prefix,
+			permissions: Some(CollectionPermissions {
+				nesting: Some(NestingRule::Permissive),
+				..Default::default()
+			}),
 			..Default::default()
 		},
 	)
 	.and_then(CollectionHandle::try_get)
 	.map(cast)
 }
-fn create_collection<T: Config>(owner: T::AccountId) -> Result<CollectionHandle<T>, DispatchError> {
+fn create_collection<T: Config>(
+	owner: T::CrossAccountId,
+) -> Result<CollectionHandle<T>, DispatchError> {
 	create_collection_raw(
 		owner,
 		CollectionMode::NFT,
@@ -127,7 +133,7 @@
 		bench_init!($($rest)*);
 	};
 	($name:ident: collection($owner:ident); $($rest:tt)*) => {
-		let $name = create_collection::<T>($owner.clone())?;
+		let $name = create_collection::<T>(T::CrossAccountId::from_sub($owner.clone()))?;
 		bench_init!($($rest)*);
 	};
 	($name:ident: cross; $($rest:tt)*) => {
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, Pallet as PalletEvm};25use evm_coder::ToLog;26use frame_support::{27	dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},28	ensure,29	traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},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	TokenChild,44	CollectionStats,45	MAX_TOKEN_OWNERSHIP,46	CollectionMode,47	NFT_SPONSOR_TRANSFER_TIMEOUT,48	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,50	MAX_SPONSOR_TIMEOUT,51	CUSTOM_DATA_LIMIT,52	CollectionLimits,53	CreateCollectionData,54	SponsorshipState,55	CreateItemExData,56	SponsoringRateLimit,57	budget::Budget,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	}117118	pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {119		<CollectionById<T>>::get(id).map(|collection| Self {120			id,121			collection,122			recorder,123		})124	}125126	pub fn new(id: CollectionId) -> Option<Self> {127		Self::new_with_gas_limit(id, u64::MAX)128	}129	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {130		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)131	}132	pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {133		self.recorder134			.consume_gas(T::GasWeightMapping::weight_to_gas(135				<T as frame_system::Config>::DbWeight::get()136					.read137					.saturating_mul(reads),138			))139	}140	pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {141		self.recorder142			.consume_gas(T::GasWeightMapping::weight_to_gas(143				<T as frame_system::Config>::DbWeight::get()144					.write145					.saturating_mul(writes),146			))147	}148	pub fn save(self) -> DispatchResult {149		<CollectionById<T>>::insert(self.id, self.collection);150		Ok(())151	}152153	pub fn set_sponsor(&mut self, sponsor: T::AccountId) {154		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);155	}156157	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> bool {158		if self.collection.sponsorship.pending_sponsor() != Some(sender) {159			return false;160		};161162		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());163		true164	}165}166impl<T: Config> Deref for CollectionHandle<T> {167	type Target = Collection<T::AccountId>;168169	fn deref(&self) -> &Self::Target {170		&self.collection171	}172}173174impl<T: Config> DerefMut for CollectionHandle<T> {175	fn deref_mut(&mut self) -> &mut Self::Target {176		&mut self.collection177	}178}179180impl<T: Config> CollectionHandle<T> {181	pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {182		ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);183		Ok(())184	}185	pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {186		*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))187	}188	pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {189		ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);190		Ok(())191	}192	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {193		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)194	}195	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {196		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)197	}198	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {199		ensure!(200			<Allowlist<T>>::get((self.id, user)),201			<Error<T>>::AddressNotInAllowlist202		);203		Ok(())204	}205}206207#[frame_support::pallet]208pub mod pallet {209	use super::*;210	use pallet_evm::account;211	use dispatch::CollectionDispatch;212	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};213	use frame_system::pallet_prelude::*;214	use frame_support::traits::Currency;215	use up_data_structs::{TokenId, mapping::TokenAddressMapping};216	use scale_info::TypeInfo;217	use weights::WeightInfo;218219	#[pallet::config]220	pub trait Config:221		frame_system::Config222		+ pallet_evm_coder_substrate::Config223		+ pallet_evm::Config224		+ TypeInfo225		+ account::Config226	{227		type WeightInfo: WeightInfo;228		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;229230		type Currency: Currency<Self::AccountId>;231232		#[pallet::constant]233		type CollectionCreationPrice: Get<234			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,235		>;236		type CollectionDispatch: CollectionDispatch<Self>;237238		type TreasuryAccountId: Get<Self::AccountId>;239		type ContractAddress: Get<H160>;240241		type EvmTokenAddressMapping: TokenAddressMapping<H160>;242		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;243	}244245	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);246247	#[pallet::pallet]248	#[pallet::storage_version(STORAGE_VERSION)]249	#[pallet::generate_store(pub(super) trait Store)]250	pub struct Pallet<T>(_);251252	#[pallet::extra_constants]253	impl<T: Config> Pallet<T> {254		pub fn collection_admins_limit() -> u32 {255			COLLECTION_ADMINS_LIMIT256		}257	}258259	#[pallet::event]260	#[pallet::generate_deposit(pub fn deposit_event)]261	pub enum Event<T: Config> {262		/// New collection was created263		///264		/// # Arguments265		///266		/// * collection_id: Globally unique identifier of newly created collection.267		///268		/// * mode: [CollectionMode] converted into u8.269		///270		/// * account_id: Collection owner.271		CollectionCreated(CollectionId, u8, T::AccountId),272273		/// New collection was destroyed274		///275		/// # Arguments276		///277		/// * collection_id: Globally unique identifier of collection.278		CollectionDestroyed(CollectionId),279280		/// New item was created.281		///282		/// # Arguments283		///284		/// * collection_id: Id of the collection where item was created.285		///286		/// * item_id: Id of an item. Unique within the collection.287		///288		/// * recipient: Owner of newly created item289		///290		/// * amount: Always 1 for NFT291		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),292293		/// Collection item was burned.294		///295		/// # Arguments296		///297		/// * collection_id.298		///299		/// * item_id: Identifier of burned NFT.300		///301		/// * owner: which user has destroyed its tokens302		///303		/// * amount: Always 1 for NFT304		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),305306		/// Item was transferred307		///308		/// * collection_id: Id of collection to which item is belong309		///310		/// * item_id: Id of an item311		///312		/// * sender: Original owner of item313		///314		/// * recipient: New owner of item315		///316		/// * amount: Always 1 for NFT317		Transfer(318			CollectionId,319			TokenId,320			T::CrossAccountId,321			T::CrossAccountId,322			u128,323		),324325		/// * collection_id326		///327		/// * item_id328		///329		/// * sender330		///331		/// * spender332		///333		/// * amount334		Approved(335			CollectionId,336			TokenId,337			T::CrossAccountId,338			T::CrossAccountId,339			u128,340		),341342		CollectionPropertySet(CollectionId, PropertyKey),343344		CollectionPropertyDeleted(CollectionId, PropertyKey),345346		TokenPropertySet(CollectionId, TokenId, PropertyKey),347348		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),349350		PropertyPermissionSet(CollectionId, PropertyKey),351	}352353	#[pallet::error]354	pub enum Error<T> {355		/// This collection does not exist.356		CollectionNotFound,357		/// Sender parameter and item owner must be equal.358		MustBeTokenOwner,359		/// No permission to perform action360		NoPermission,361		/// Destroying only empty collections is allowed362		CantDestroyNotEmptyCollection,363		/// Collection is not in mint mode.364		PublicMintingNotAllowed,365		/// Address is not in allow list.366		AddressNotInAllowlist,367368		/// Collection name can not be longer than 63 char.369		CollectionNameLimitExceeded,370		/// Collection description can not be longer than 255 char.371		CollectionDescriptionLimitExceeded,372		/// Token prefix can not be longer than 15 char.373		CollectionTokenPrefixLimitExceeded,374		/// Total collections bound exceeded.375		TotalCollectionsLimitExceeded,376		/// Exceeded max admin count377		CollectionAdminCountExceeded,378		/// Collection limit bounds per collection exceeded379		CollectionLimitBoundsExceeded,380		/// Tried to enable permissions which are only permitted to be disabled381		OwnerPermissionsCantBeReverted,382		/// Collection settings not allowing items transferring383		TransferNotAllowed,384		/// Account token limit exceeded per collection385		AccountTokenLimitExceeded,386		/// Collection token limit exceeded387		CollectionTokenLimitExceeded,388		/// Metadata flag frozen389		MetadataFlagFrozen,390391		/// Item not exists.392		TokenNotFound,393		/// Item balance not enough.394		TokenValueTooLow,395		/// Requested value more than approved.396		ApprovedValueTooLow,397		/// Tried to approve more than owned398		CantApproveMoreThanOwned,399400		/// Can't transfer tokens to ethereum zero address401		AddressIsZero,402		/// Target collection doesn't supports this operation403		UnsupportedOperation,404405		/// Not sufficient founds to perform action406		NotSufficientFounds,407408		/// Collection has nesting disabled409		NestingIsDisabled,410		/// Only owner may nest tokens under this collection411		OnlyOwnerAllowedToNest,412		/// Only tokens from specific collections may nest tokens under this413		SourceCollectionIsNotAllowedToNest,414415		/// Tried to store more data than allowed in collection field416		CollectionFieldSizeExceeded,417418		/// Tried to store more property data than allowed419		NoSpaceForProperty,420421		/// Tried to store more property keys than allowed422		PropertyLimitReached,423424		/// Property key is too long425		PropertyKeyIsTooLong,426427		/// Only ASCII letters, digits, and '_', '-' are allowed428		InvalidCharacterInPropertyKey,429430		/// Empty property keys are forbidden431		EmptyPropertyKey,432	}433434	#[pallet::storage]435	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;436	#[pallet::storage]437	pub type DestroyedCollectionCount<T> =438		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;439440	/// Collection info441	#[pallet::storage]442	pub type CollectionById<T> = StorageMap<443		Hasher = Blake2_128Concat,444		Key = CollectionId,445		Value = Collection<<T as frame_system::Config>::AccountId>,446		QueryKind = OptionQuery,447	>;448449	/// Collection properties450	#[pallet::storage]451	#[pallet::getter(fn collection_properties)]452	pub type CollectionProperties<T> = StorageMap<453		Hasher = Blake2_128Concat,454		Key = CollectionId,455		Value = Properties,456		QueryKind = ValueQuery,457		OnEmpty = up_data_structs::CollectionProperties,458	>;459460	#[pallet::storage]461	#[pallet::getter(fn property_permissions)]462	pub type CollectionPropertyPermissions<T> = StorageMap<463		Hasher = Blake2_128Concat,464		Key = CollectionId,465		Value = PropertiesPermissionMap,466		QueryKind = ValueQuery,467	>;468469	#[pallet::storage]470	pub type AdminAmount<T> = StorageMap<471		Hasher = Blake2_128Concat,472		Key = CollectionId,473		Value = u32,474		QueryKind = ValueQuery,475	>;476477	/// List of collection admins478	#[pallet::storage]479	pub type IsAdmin<T: Config> = StorageNMap<480		Key = (481			Key<Blake2_128Concat, CollectionId>,482			Key<Blake2_128Concat, T::CrossAccountId>,483		),484		Value = bool,485		QueryKind = ValueQuery,486	>;487488	/// Allowlisted collection users489	#[pallet::storage]490	pub type Allowlist<T: Config> = StorageNMap<491		Key = (492			Key<Blake2_128Concat, CollectionId>,493			Key<Blake2_128Concat, T::CrossAccountId>,494		),495		Value = bool,496		QueryKind = ValueQuery,497	>;498499	/// Not used by code, exists only to provide some types to metadata500	#[pallet::storage]501	pub type DummyStorageValue<T: Config> = StorageValue<502		Value = (503			CollectionStats,504			CollectionId,505			TokenId,506			TokenChild,507			PhantomType<(508				TokenData<T::CrossAccountId>,509				RpcCollection<T::AccountId>,510				// RMRK511				RmrkCollectionInfo<T::AccountId>,512				RmrkInstanceInfo<T::AccountId>,513				RmrkResourceInfo,514				RmrkPropertyInfo,515				RmrkBaseInfo<T::AccountId>,516				RmrkPartType,517				RmrkTheme,518				RmrkNftChild,519			)>,520		),521		QueryKind = OptionQuery,522	>;523524	#[pallet::hooks]525	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {526		fn on_runtime_upgrade() -> Weight {527			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {528				use up_data_structs::{CollectionVersion1, CollectionVersion2};529				<CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {530					let mut props = Vec::new();531					if !v.offchain_schema.is_empty() {532						props.push(Property {533							key: b"_old_offchainSchema".to_vec().try_into().unwrap(),534							value: v535								.offchain_schema536								.clone()537								.into_inner()538								.try_into()539								.expect("offchain schema too big"),540						});541					}542					if !v.variable_on_chain_schema.is_empty() {543						props.push(Property {544							key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),545							value: v546								.variable_on_chain_schema547								.clone()548								.into_inner()549								.try_into()550								.expect("offchain schema too big"),551						});552					}553					if !v.const_on_chain_schema.is_empty() {554						props.push(Property {555							key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),556							value: v557								.const_on_chain_schema558								.clone()559								.into_inner()560								.try_into()561								.expect("offchain schema too big"),562						});563					}564					props.push(Property {565						key: b"_old_schemaVersion".to_vec().try_into().unwrap(),566						value: match v.schema_version {567							SchemaVersion::ImageURL => b"ImageUrl".as_slice(),568							SchemaVersion::Unique => b"Unique".as_slice(),569						}570						.to_vec()571						.try_into()572						.unwrap(),573					});574					Self::set_scoped_collection_properties(575						id,576						PropertyScope::None,577						props.into_iter(),578					)579					.expect("existing data larger than properties");580					let mut new = CollectionVersion2::from(v.clone());581					new.permissions.access = Some(v.access);582					new.permissions.mint_mode = Some(v.mint_mode);583					Some(new)584				});585			}586587			0588		}589	}590}591592impl<T: Config> Pallet<T> {593	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens594	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {595		ensure!(596			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,597			<Error<T>>::AddressIsZero598		);599		Ok(())600	}601	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {602		<IsAdmin<T>>::iter_prefix((collection,))603			.map(|(a, _)| a)604			.collect()605	}606	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {607		<Allowlist<T>>::iter_prefix((collection,))608			.map(|(a, _)| a)609			.collect()610	}611	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {612		<Allowlist<T>>::get((collection, user))613	}614	pub fn collection_stats() -> CollectionStats {615		let created = <CreatedCollectionCount<T>>::get();616		let destroyed = <DestroyedCollectionCount<T>>::get();617		CollectionStats {618			created: created.0,619			destroyed: destroyed.0,620			alive: created.0 - destroyed.0,621		}622	}623624	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {625		let collection = <CollectionById<T>>::get(collection);626		if collection.is_none() {627			return None;628		}629630		let collection = collection.unwrap();631		let limits = collection.limits;632		let effective_limits = CollectionLimits {633			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),634			sponsored_data_size: Some(limits.sponsored_data_size()),635			sponsored_data_rate_limit: Some(636				limits637					.sponsored_data_rate_limit638					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),639			),640			token_limit: Some(limits.token_limit()),641			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(642				match collection.mode {643					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,644					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,645					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,646				},647			)),648			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),649			owner_can_transfer: Some(limits.owner_can_transfer()),650			owner_can_destroy: Some(limits.owner_can_destroy()),651			transfers_enabled: Some(limits.transfers_enabled()),652		};653654		Some(effective_limits)655	}656657	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {658		let Collection {659			name,660			description,661			owner,662			mode,663			token_prefix,664			sponsorship,665			limits,666			permissions,667		} = <CollectionById<T>>::get(collection)?;668669		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)670			.into_iter()671			.map(|(key, permission)| PropertyKeyPermission { key, permission })672			.collect();673674		let properties = <CollectionProperties<T>>::get(collection)675			.into_iter()676			.map(|(key, value)| Property { key, value })677			.collect();678679		let permissions = CollectionPermissions {680			access: Some(permissions.access()),681			mint_mode: Some(permissions.mint_mode()),682			nesting: Some(permissions.nesting().clone()),683		};684685		Some(RpcCollection {686			name: name.into_inner(),687			description: description.into_inner(),688			owner,689			mode,690			token_prefix: token_prefix.into_inner(),691			sponsorship,692			limits,693			permissions,694			token_property_permissions,695			properties,696		})697	}698}699700macro_rules! limit_default {701	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{702		$(703			if let Some($new) = $new.$field {704				let $old = $old.$field($($arg)?);705				let _ = $new;706				let _ = $old;707				$check708			} else {709				$new.$field = $old.$field710			}711		)*712	}};713}714macro_rules! limit_default_clone {715	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{716		$(717			if let Some($new) = $new.$field.clone() {718				let $old = $old.$field($($arg)?);719				let _ = $new;720				let _ = $old;721				$check722			} else {723				$new.$field = $old.$field.clone()724			}725		)*726	}};727}728729impl<T: Config> Pallet<T> {730	pub fn init_collection(731		owner: T::CrossAccountId,732		data: CreateCollectionData<T::AccountId>,733	) -> Result<CollectionId, DispatchError> {734		{735			ensure!(736				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,737				Error::<T>::CollectionTokenPrefixLimitExceeded738			);739		}740741		let created_count = <CreatedCollectionCount<T>>::get()742			.0743			.checked_add(1)744			.ok_or(ArithmeticError::Overflow)?;745		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;746		let id = CollectionId(created_count);747748		// bound Total number of collections749		ensure!(750			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,751			<Error<T>>::TotalCollectionsLimitExceeded752		);753754		// =========755756		let collection = Collection {757			owner: owner.as_sub().clone(),758			name: data.name,759			mode: data.mode.clone(),760			description: data.description,761			token_prefix: data.token_prefix,762			sponsorship: data763				.pending_sponsor764				.map(SponsorshipState::Unconfirmed)765				.unwrap_or_default(),766			limits: data767				.limits768				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))769				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,770			permissions: data771				.permissions772				.map(|permissions| {773					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)774				})775				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,776		};777778		let mut collection_properties = up_data_structs::CollectionProperties::get();779		collection_properties780			.try_set_from_iter(data.properties.into_iter())781			.map_err(<Error<T>>::from)?;782783		CollectionProperties::<T>::insert(id, collection_properties);784785		let mut token_props_permissions = PropertiesPermissionMap::new();786		token_props_permissions787			.try_set_from_iter(data.token_property_permissions.into_iter())788			.map_err(<Error<T>>::from)?;789790		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);791792		// Take a (non-refundable) deposit of collection creation793		{794			let mut imbalance =795				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();796			imbalance.subsume(797				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(798					&T::TreasuryAccountId::get(),799					T::CollectionCreationPrice::get(),800				),801			);802			<T as Config>::Currency::settle(803				&owner.as_sub(),804				imbalance,805				WithdrawReasons::TRANSFER,806				ExistenceRequirement::KeepAlive,807			)808			.map_err(|_| Error::<T>::NotSufficientFounds)?;809		}810811		<CreatedCollectionCount<T>>::put(created_count);812		<Pallet<T>>::deposit_event(Event::CollectionCreated(813			id,814			data.mode.id(),815			owner.as_sub().clone(),816		));817		<PalletEvm<T>>::deposit_log(818			erc::CollectionHelpersEvents::CollectionCreated {819				owner: *owner.as_eth(),820				collection_id: eth::collection_id_to_address(id),821			}822			.to_log(T::ContractAddress::get()),823		);824		<CollectionById<T>>::insert(id, collection);825		Ok(id)826	}827828	pub fn destroy_collection(829		collection: CollectionHandle<T>,830		sender: &T::CrossAccountId,831	) -> DispatchResult {832		ensure!(833			collection.limits.owner_can_destroy(),834			<Error<T>>::NoPermission,835		);836		collection.check_is_owner(sender)?;837838		let destroyed_collections = <DestroyedCollectionCount<T>>::get()839			.0840			.checked_add(1)841			.ok_or(ArithmeticError::Overflow)?;842843		// =========844845		<DestroyedCollectionCount<T>>::put(destroyed_collections);846		<CollectionById<T>>::remove(collection.id);847		<AdminAmount<T>>::remove(collection.id);848		<IsAdmin<T>>::remove_prefix((collection.id,), None);849		<Allowlist<T>>::remove_prefix((collection.id,), None);850		<CollectionProperties<T>>::remove(collection.id);851852		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));853		Ok(())854	}855856	pub fn set_collection_property(857		collection: &CollectionHandle<T>,858		sender: &T::CrossAccountId,859		property: Property,860	) -> DispatchResult {861		collection.check_is_owner_or_admin(sender)?;862863		CollectionProperties::<T>::try_mutate(collection.id, |properties| {864			let property = property.clone();865			properties.try_set(property.key, property.value)866		})867		.map_err(<Error<T>>::from)?;868869		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));870871		Ok(())872	}873874	pub fn set_scoped_collection_property(875		collection_id: CollectionId,876		scope: PropertyScope,877		property: Property,878	) -> DispatchResult {879		CollectionProperties::<T>::try_mutate(collection_id, |properties| {880			properties.try_scoped_set(scope, property.key, property.value)881		})882		.map_err(<Error<T>>::from)?;883884		Ok(())885	}886887	pub fn set_scoped_collection_properties(888		collection_id: CollectionId,889		scope: PropertyScope,890		properties: impl Iterator<Item = Property>,891	) -> DispatchResult {892		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {893			stored_properties.try_scoped_set_from_iter(scope, properties)894		})895		.map_err(<Error<T>>::from)?;896897		Ok(())898	}899900	#[transactional]901	pub fn set_collection_properties(902		collection: &CollectionHandle<T>,903		sender: &T::CrossAccountId,904		properties: Vec<Property>,905	) -> DispatchResult {906		for property in properties {907			Self::set_collection_property(collection, sender, property)?;908		}909910		Ok(())911	}912913	pub fn delete_collection_property(914		collection: &CollectionHandle<T>,915		sender: &T::CrossAccountId,916		property_key: PropertyKey,917	) -> DispatchResult {918		collection.check_is_owner_or_admin(sender)?;919920		CollectionProperties::<T>::try_mutate(collection.id, |properties| {921			properties.remove(&property_key)922		})923		.map_err(<Error<T>>::from)?;924925		Self::deposit_event(Event::CollectionPropertyDeleted(926			collection.id,927			property_key,928		));929930		Ok(())931	}932933	#[transactional]934	pub fn delete_collection_properties(935		collection: &CollectionHandle<T>,936		sender: &T::CrossAccountId,937		property_keys: Vec<PropertyKey>,938	) -> DispatchResult {939		for key in property_keys {940			Self::delete_collection_property(collection, sender, key)?;941		}942943		Ok(())944	}945946	// For migrations947	pub fn set_property_permission_unchecked(948		collection: CollectionId,949		property_permission: PropertyKeyPermission,950	) -> DispatchResult {951		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {952			permissions.try_set(property_permission.key, property_permission.permission)953		})954		.map_err(<Error<T>>::from)?;955		Ok(())956	}957958	pub fn set_property_permission(959		collection: &CollectionHandle<T>,960		sender: &T::CrossAccountId,961		property_permission: PropertyKeyPermission,962	) -> DispatchResult {963		collection.check_is_owner_or_admin(sender)?;964965		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);966		let current_permission = all_permissions.get(&property_permission.key);967		if matches![968			current_permission,969			Some(PropertyPermission { mutable: false, .. })970		] {971			return Err(<Error<T>>::NoPermission.into());972		}973974		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {975			let property_permission = property_permission.clone();976			permissions.try_set(property_permission.key, property_permission.permission)977		})978		.map_err(<Error<T>>::from)?;979980		Self::deposit_event(Event::PropertyPermissionSet(981			collection.id,982			property_permission.key,983		));984985		Ok(())986	}987988	#[transactional]989	pub fn set_property_permissions(990		collection: &CollectionHandle<T>,991		sender: &T::CrossAccountId,992		property_permissions: Vec<PropertyKeyPermission>,993	) -> DispatchResult {994		for prop_pemission in property_permissions {995			Self::set_property_permission(collection, sender, prop_pemission)?;996		}997998		Ok(())999	}10001001	pub fn get_collection_property(1002		collection_id: CollectionId,1003		key: &PropertyKey,1004	) -> Option<PropertyValue> {1005		Self::collection_properties(collection_id).get(key).cloned()1006	}10071008	pub fn bytes_keys_to_property_keys(1009		keys: Vec<Vec<u8>>,1010	) -> Result<Vec<PropertyKey>, DispatchError> {1011		keys.into_iter()1012			.map(|key| -> Result<PropertyKey, DispatchError> {1013				key.try_into()1014					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1015			})1016			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1017	}10181019	pub fn filter_collection_properties(1020		collection_id: CollectionId,1021		keys: Option<Vec<PropertyKey>>,1022	) -> Result<Vec<Property>, DispatchError> {1023		let properties = Self::collection_properties(collection_id);10241025		let properties = keys1026			.map(|keys| {1027				keys.into_iter()1028					.filter_map(|key| {1029						properties.get(&key).map(|value| Property {1030							key,1031							value: value.clone(),1032						})1033					})1034					.collect()1035			})1036			.unwrap_or_else(|| {1037				properties1038					.into_iter()1039					.map(|(key, value)| Property { key, value })1040					.collect()1041			});10421043		Ok(properties)1044	}10451046	pub fn filter_property_permissions(1047		collection_id: CollectionId,1048		keys: Option<Vec<PropertyKey>>,1049	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1050		let permissions = Self::property_permissions(collection_id);10511052		let key_permissions = keys1053			.map(|keys| {1054				keys.into_iter()1055					.filter_map(|key| {1056						permissions1057							.get(&key)1058							.map(|permission| PropertyKeyPermission {1059								key,1060								permission: permission.clone(),1061							})1062					})1063					.collect()1064			})1065			.unwrap_or_else(|| {1066				permissions1067					.into_iter()1068					.map(|(key, permission)| PropertyKeyPermission { key, permission })1069					.collect()1070			});10711072		Ok(key_permissions)1073	}10741075	pub fn toggle_allowlist(1076		collection: &CollectionHandle<T>,1077		sender: &T::CrossAccountId,1078		user: &T::CrossAccountId,1079		allowed: bool,1080	) -> DispatchResult {1081		collection.check_is_owner_or_admin(sender)?;10821083		// =========10841085		if allowed {1086			<Allowlist<T>>::insert((collection.id, user), true);1087		} else {1088			<Allowlist<T>>::remove((collection.id, user));1089		}10901091		Ok(())1092	}10931094	pub fn toggle_admin(1095		collection: &CollectionHandle<T>,1096		sender: &T::CrossAccountId,1097		user: &T::CrossAccountId,1098		admin: bool,1099	) -> DispatchResult {1100		collection.check_is_owner_or_admin(sender)?;11011102		let was_admin = <IsAdmin<T>>::get((collection.id, user));1103		if was_admin == admin {1104			return Ok(());1105		}1106		let amount = <AdminAmount<T>>::get(collection.id);11071108		if admin {1109			let amount = amount1110				.checked_add(1)1111				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1112			ensure!(1113				amount <= Self::collection_admins_limit(),1114				<Error<T>>::CollectionAdminCountExceeded,1115			);11161117			// =========11181119			<AdminAmount<T>>::insert(collection.id, amount);1120			<IsAdmin<T>>::insert((collection.id, user), true);1121		} else {1122			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1123			<IsAdmin<T>>::remove((collection.id, user));1124		}11251126		Ok(())1127	}11281129	pub fn clamp_limits(1130		mode: CollectionMode,1131		old_limit: &CollectionLimits,1132		mut new_limit: CollectionLimits,1133	) -> Result<CollectionLimits, DispatchError> {1134		limit_default!(old_limit, new_limit,1135			account_token_ownership_limit => ensure!(1136				new_limit <= MAX_TOKEN_OWNERSHIP,1137				<Error<T>>::CollectionLimitBoundsExceeded,1138			),1139			sponsored_data_size => ensure!(1140				new_limit <= CUSTOM_DATA_LIMIT,1141				<Error<T>>::CollectionLimitBoundsExceeded,1142			),11431144			sponsored_data_rate_limit => {},1145			token_limit => ensure!(1146				old_limit >= new_limit && new_limit > 0,1147				<Error<T>>::CollectionTokenLimitExceeded1148			),11491150			sponsor_transfer_timeout(match mode {1151				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1152				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1153				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1154			}) => ensure!(1155				new_limit <= MAX_SPONSOR_TIMEOUT,1156				<Error<T>>::CollectionLimitBoundsExceeded,1157			),1158			sponsor_approve_timeout => {},1159			owner_can_transfer => ensure!(1160				old_limit || !new_limit,1161				<Error<T>>::OwnerPermissionsCantBeReverted,1162			),1163			owner_can_destroy => ensure!(1164				old_limit || !new_limit,1165				<Error<T>>::OwnerPermissionsCantBeReverted,1166			),1167			transfers_enabled => {},1168		);1169		Ok(new_limit)1170	}1171	pub fn clamp_permissions(1172		mode: CollectionMode,1173		old_limit: &CollectionPermissions,1174		mut new_limit: CollectionPermissions,1175	) -> Result<CollectionPermissions, DispatchError> {1176		limit_default_clone!(old_limit, new_limit,1177			access => {},1178			mint_mode => {},1179			nesting => {},1180		);1181		Ok(new_limit)1182	}1183}11841185#[macro_export]1186macro_rules! unsupported {1187	() => {1188		Err(<Error<T>>::UnsupportedOperation.into())1189	};1190}11911192/// Worst cases1193pub trait CommonWeightInfo<CrossAccountId> {1194	fn create_item() -> Weight;1195	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1196	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1197	fn burn_item() -> Weight;1198	fn set_collection_properties(amount: u32) -> Weight;1199	fn delete_collection_properties(amount: u32) -> Weight;1200	fn set_token_properties(amount: u32) -> Weight;1201	fn delete_token_properties(amount: u32) -> Weight;1202	fn set_property_permissions(amount: u32) -> Weight;1203	fn transfer() -> Weight;1204	fn approve() -> Weight;1205	fn transfer_from() -> Weight;1206	fn burn_from() -> Weight;1207}12081209pub trait CommonCollectionOperations<T: Config> {1210	fn create_item(1211		&self,1212		sender: T::CrossAccountId,1213		to: T::CrossAccountId,1214		data: CreateItemData,1215		nesting_budget: &dyn Budget,1216	) -> DispatchResultWithPostInfo;1217	fn create_multiple_items(1218		&self,1219		sender: T::CrossAccountId,1220		to: T::CrossAccountId,1221		data: Vec<CreateItemData>,1222		nesting_budget: &dyn Budget,1223	) -> DispatchResultWithPostInfo;1224	fn create_multiple_items_ex(1225		&self,1226		sender: T::CrossAccountId,1227		data: CreateItemExData<T::CrossAccountId>,1228		nesting_budget: &dyn Budget,1229	) -> DispatchResultWithPostInfo;1230	fn burn_item(1231		&self,1232		sender: T::CrossAccountId,1233		token: TokenId,1234		amount: u128,1235	) -> DispatchResultWithPostInfo;1236	fn set_collection_properties(1237		&self,1238		sender: T::CrossAccountId,1239		properties: Vec<Property>,1240	) -> DispatchResultWithPostInfo;1241	fn delete_collection_properties(1242		&self,1243		sender: &T::CrossAccountId,1244		property_keys: Vec<PropertyKey>,1245	) -> DispatchResultWithPostInfo;1246	fn set_token_properties(1247		&self,1248		sender: T::CrossAccountId,1249		token_id: TokenId,1250		property: Vec<Property>,1251	) -> DispatchResultWithPostInfo;1252	fn delete_token_properties(1253		&self,1254		sender: T::CrossAccountId,1255		token_id: TokenId,1256		property_keys: Vec<PropertyKey>,1257	) -> DispatchResultWithPostInfo;1258	fn set_property_permissions(1259		&self,1260		sender: &T::CrossAccountId,1261		property_permissions: Vec<PropertyKeyPermission>,1262	) -> DispatchResultWithPostInfo;1263	fn transfer(1264		&self,1265		sender: T::CrossAccountId,1266		to: T::CrossAccountId,1267		token: TokenId,1268		amount: u128,1269		nesting_budget: &dyn Budget,1270	) -> DispatchResultWithPostInfo;1271	fn approve(1272		&self,1273		sender: T::CrossAccountId,1274		spender: T::CrossAccountId,1275		token: TokenId,1276		amount: u128,1277	) -> DispatchResultWithPostInfo;1278	fn transfer_from(1279		&self,1280		sender: T::CrossAccountId,1281		from: T::CrossAccountId,1282		to: T::CrossAccountId,1283		token: TokenId,1284		amount: u128,1285		nesting_budget: &dyn Budget,1286	) -> DispatchResultWithPostInfo;1287	fn burn_from(1288		&self,1289		sender: T::CrossAccountId,1290		from: T::CrossAccountId,1291		token: TokenId,1292		amount: u128,1293		nesting_budget: &dyn Budget,1294	) -> DispatchResultWithPostInfo;12951296	fn check_nesting(1297		&self,1298		sender: T::CrossAccountId,1299		from: (CollectionId, TokenId),1300		under: TokenId,1301		budget: &dyn Budget,1302	) -> DispatchResult;13031304	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13051306	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13071308	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1309	fn collection_tokens(&self) -> Vec<TokenId>;1310	fn token_exists(&self, token: TokenId) -> bool;1311	fn last_token_id(&self) -> TokenId;13121313	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1314	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1315	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1316	/// Amount of unique collection tokens1317	fn total_supply(&self) -> u32;1318	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1319	fn account_balance(&self, account: T::CrossAccountId) -> u32;1320	/// Amount of specific token account have (Applicable to fungible/refungible)1321	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1322	fn allowance(1323		&self,1324		sender: T::CrossAccountId,1325		spender: T::CrossAccountId,1326		token: TokenId,1327	) -> u128;1328}13291330// Flexible enough for implementing CommonCollectionOperations1331pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1332	let post_info = PostDispatchInfo {1333		actual_weight: Some(weight),1334		pays_fee: Pays::Yes,1335	};1336	match res {1337		Ok(()) => Ok(post_info),1338		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1339	}1340}13411342impl<T: Config> From<PropertiesError> for Error<T> {1343	fn from(error: PropertiesError) -> Self {1344		match error {1345			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1346			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1347			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1348			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1349			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1350		}1351	}1352}
modifiedpallets/fungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/fungible/src/benchmarking.rs
+++ b/pallets/fungible/src/benchmarking.rs
@@ -25,7 +25,9 @@
 
 const SEED: u32 = 1;
 
-fn create_collection<T: Config>(owner: T::AccountId) -> Result<FungibleHandle<T>, DispatchError> {
+fn create_collection<T: Config>(
+	owner: T::CrossAccountId,
+) -> Result<FungibleHandle<T>, DispatchError> {
 	create_collection_raw(
 		owner,
 		CollectionMode::Fungible(0),
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -16,9 +16,10 @@
 
 use core::marker::PhantomData;
 
-use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};
+use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, traits::Get};
 use up_data_structs::{TokenId, CollectionId, CreateItemExData, budget::Budget, CreateItemData};
 use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
+use pallet_structure::Error as StructureError;
 use sp_runtime::ArithmeticError;
 use sp_std::{vec::Vec, vec};
 use up_data_structs::{Property, PropertyKey, PropertyValue, PropertyKeyPermission};
@@ -91,6 +92,16 @@
 	fn burn_from() -> Weight {
 		<SelfWeightOf<T>>::burn_from()
 	}
+
+	fn burn_recursively_self_raw() -> Weight {
+		// Read to get total balance
+		Self::burn_item() + T::DbWeight::get().reads(1)
+	}
+
+	fn burn_recursively_breadth_raw(_amount: u32) -> Weight {
+		// Fungible tokens can't have children
+		0
+	}
 }
 
 impl<T: Config> CommonCollectionOperations<T> for FungibleHandle<T> {
@@ -170,6 +181,26 @@
 		)
 	}
 
+	fn burn_item_recursively(
+		&self,
+		sender: T::CrossAccountId,
+		token: TokenId,
+		self_budget: &dyn Budget,
+		_breadth_budget: &dyn Budget,
+	) -> DispatchResultWithPostInfo {
+		// Should not happen?
+		ensure!(
+			token == TokenId::default(),
+			<Error<T>>::FungibleItemsHaveNoId
+		);
+		ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);
+
+		with_weight(
+			<Pallet<T>>::burn(self, &sender, <Balance<T>>::get((self.id, &sender))),
+			<CommonWeights<T>>::burn_recursively_self_raw(),
+		)
+	}
+
 	fn transfer(
 		&self,
 		from: T::CrossAccountId,
modifiedpallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -18,12 +18,9 @@
 use crate::{Pallet, Config, NonfungibleHandle};
 
 use sp_std::prelude::*;
-use pallet_common::benchmarking::{create_collection_raw, create_data, property_key, property_value};
+use pallet_common::benchmarking::{create_collection_raw, property_key, property_value};
 use frame_benchmarking::{benchmarks, account};
-use up_data_structs::{
-	CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, CUSTOM_DATA_LIMIT,
-	budget::Unlimited,
-};
+use up_data_structs::{CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, budget::Unlimited};
 use pallet_common::bench_init;
 
 const SEED: u32 = 1;
@@ -49,7 +46,7 @@
 }
 
 fn create_collection<T: Config>(
-	owner: T::AccountId,
+	owner: T::CrossAccountId,
 ) -> Result<NonfungibleHandle<T>, DispatchError> {
 	create_collection_raw(
 		owner,
@@ -96,6 +93,26 @@
 		let item = create_max_item(&collection, &sender, burner.clone())?;
 	}: {<Pallet<T>>::burn(&collection, &burner, item)?}
 
+	burn_recursively_self_raw {
+		bench_init!{
+			owner: sub; collection: collection(owner);
+			sender: cross_from_sub(owner); burner: cross_sub;
+		};
+		let item = create_max_item(&collection, &sender, burner.clone())?;
+	}: {<Pallet<T>>::burn_recursively(&collection, &burner, item, &Unlimited, &Unlimited)}
+
+	burn_recursively_breadth_plus_self_plus_self_per_each_raw {
+		let b in 0..200;
+		bench_init!{
+			owner: sub; collection: collection(owner);
+			sender: cross_from_sub(owner); burner: cross_sub;
+		};
+		let item = create_max_item(&collection, &sender, burner.clone())?;
+		for i in 0..b {
+			create_max_item(&collection, &sender, T::CrossTokenAddressMapping::token_to_address(collection.id, item))?;
+		}
+	}: {<Pallet<T>>::burn_recursively(&collection, &burner, item, &Unlimited, &Unlimited)}
+
 	transfer {
 		bench_init!{
 			owner: sub; collection: collection(owner);
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -108,6 +108,15 @@
 	fn burn_from() -> Weight {
 		<SelfWeightOf<T>>::burn_from()
 	}
+
+	fn burn_recursively_self_raw() -> Weight {
+		<SelfWeightOf<T>>::burn_recursively_self_raw()
+	}
+
+	fn burn_recursively_breadth_raw(amount: u32) -> Weight {
+		<SelfWeightOf<T>>::burn_recursively_breadth_plus_self_plus_self_per_each_raw(amount)
+			.saturating_sub(Self::burn_recursively_self_raw().saturating_mul(amount as u64 + 1))
+	}
 }
 
 fn map_create_data<T: Config>(
@@ -264,6 +273,16 @@
 		}
 	}
 
+	fn burn_item_recursively(
+		&self,
+		sender: T::CrossAccountId,
+		token: TokenId,
+		self_budget: &dyn Budget,
+		breadth_budget: &dyn Budget,
+	) -> DispatchResultWithPostInfo {
+		<Pallet<T>>::burn_recursively(self, &sender, token, self_budget, breadth_budget)
+	}
+
 	fn transfer(
 		&self,
 		from: T::CrossAccountId,
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -18,7 +18,13 @@
 
 use erc::ERC721Events;
 use evm_coder::ToLog;
-use frame_support::{BoundedVec, ensure, fail, transactional, storage::with_transaction};
+use frame_support::{
+	BoundedVec, ensure, fail, transactional,
+	storage::with_transaction,
+	pallet_prelude::DispatchResultWithPostInfo,
+	pallet_prelude::Weight,
+	weights::{PostDispatchInfo, Pays},
+};
 use up_data_structs::{
 	AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,
 	mapping::TokenAddressMapping, NestingRule, budget::Budget, Property, PropertyPermission,
@@ -29,7 +35,7 @@
 	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,
 	eth::collection_id_to_address,
 };
-use pallet_structure::Pallet as PalletStructure;
+use pallet_structure::{Pallet as PalletStructure, Error as StructureError};
 use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
 use sp_core::H160;
 use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};
@@ -39,6 +45,7 @@
 use scale_info::TypeInfo;
 
 pub use pallet::*;
+use weights::WeightInfo;
 #[cfg(feature = "runtime-benchmarks")]
 pub mod benchmarking;
 pub mod common;
@@ -373,7 +380,7 @@
 			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(
 				collection.id,
 				token,
-				sender.clone(),
+				token_data.owner.clone(),
 				old_spender,
 				0,
 			));
@@ -396,6 +403,45 @@
 		Ok(())
 	}
 
+	#[transactional]
+	pub fn burn_recursively(
+		collection: &NonfungibleHandle<T>,
+		sender: &T::CrossAccountId,
+		token: TokenId,
+		self_budget: &dyn Budget,
+		breadth_budget: &dyn Budget,
+	) -> DispatchResultWithPostInfo {
+		ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);
+
+		let current_token_account =
+			T::CrossTokenAddressMapping::token_to_address(collection.id, token);
+
+		let mut weight = 0 as Weight;
+
+		// This method is transactional, if user in fact doesn't have permissions to remove token -
+		// tokens removed here will be restored after rejected transaction
+		for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {
+			ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);
+			let PostDispatchInfo { actual_weight, .. } =
+				<PalletStructure<T>>::burn_item_recursively(
+					current_token_account.clone(),
+					collection,
+					token,
+					self_budget,
+					breadth_budget,
+				)?;
+			if let Some(actual_weight) = actual_weight {
+				weight = weight.saturating_add(actual_weight);
+			}
+		}
+
+		Self::burn(collection, sender, token)?;
+		DispatchResultWithPostInfo::Ok(PostDispatchInfo {
+			actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),
+			pays_fee: Pays::Yes,
+		})
+	}
+
 	pub fn set_token_property(
 		collection: &NonfungibleHandle<T>,
 		sender: &T::CrossAccountId,
@@ -964,6 +1010,7 @@
 				);
 				ensure_sender_allowed::<T>(handle.id, under, from, sender, nesting_budget)?
 			}
+			NestingRule::Permissive => {}
 		}
 		Ok(())
 	}
modifiedpallets/nonfungible/src/weights.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/weights.rs
+++ b/pallets/nonfungible/src/weights.rs
@@ -36,6 +36,8 @@
 	fn create_multiple_items(b: u32, ) -> Weight;
 	fn create_multiple_items_ex(b: u32, ) -> Weight;
 	fn burn_item() -> Weight;
+	fn burn_recursively_self_raw() -> Weight;
+	fn burn_recursively_breadth_plus_self_plus_self_per_each_raw(b: u32, ) -> Weight;
 	fn transfer() -> Weight;
 	fn approve() -> Weight;
 	fn transfer_from() -> Weight;
@@ -92,7 +94,35 @@
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
 			.saturating_add(T::DbWeight::get().writes(4 as Weight))
 	}
-
+	// Storage: Nonfungible TokenChildren (r:1 w:0)
+	// Storage: Nonfungible TokenData (r:1 w:1)
+	// Storage: Nonfungible TokensBurnt (r:1 w:1)
+	// Storage: Nonfungible AccountBalance (r:1 w:1)
+	// Storage: Nonfungible Allowance (r:1 w:0)
+	// Storage: Nonfungible Owned (r:0 w:1)
+	// Storage: Nonfungible TokenProperties (r:0 w:1)
+	fn burn_recursively_self_raw() -> Weight {
+		(86_136_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(5 as Weight))
+			.saturating_add(T::DbWeight::get().writes(5 as Weight))
+	}
+	// Storage: Nonfungible TokenChildren (r:1 w:0)
+	// Storage: Nonfungible TokenData (r:1 w:1)
+	// Storage: Nonfungible TokensBurnt (r:1 w:1)
+	// Storage: Nonfungible AccountBalance (r:1 w:1)
+	// Storage: Nonfungible Allowance (r:1 w:0)
+	// Storage: Nonfungible Owned (r:0 w:1)
+	// Storage: Nonfungible TokenProperties (r:0 w:1)
+	// Storage: Common CollectionById (r:1 w:0)
+	fn burn_recursively_breadth_plus_self_plus_self_per_each_raw(b: u32, ) -> Weight {
+		(0 as Weight)
+			// Standard Error: 42_828_000
+			.saturating_add((381_478_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(T::DbWeight::get().reads(6 as Weight))
+			.saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(b as Weight)))
+			.saturating_add(T::DbWeight::get().writes(5 as Weight))
+			.saturating_add(T::DbWeight::get().writes((4 as Weight).saturating_mul(b as Weight)))
+	}
 	// Storage: Nonfungible TokenData (r:1 w:1)
 	// Storage: Nonfungible AccountBalance (r:2 w:2)
 	// Storage: Nonfungible Allowance (r:1 w:0)
@@ -204,7 +234,35 @@
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(4 as Weight))
 	}
-
+	// Storage: Nonfungible TokenChildren (r:1 w:0)
+	// Storage: Nonfungible TokenData (r:1 w:1)
+	// Storage: Nonfungible TokensBurnt (r:1 w:1)
+	// Storage: Nonfungible AccountBalance (r:1 w:1)
+	// Storage: Nonfungible Allowance (r:1 w:0)
+	// Storage: Nonfungible Owned (r:0 w:1)
+	// Storage: Nonfungible TokenProperties (r:0 w:1)
+	fn burn_recursively_self_raw() -> Weight {
+		(86_136_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(5 as Weight))
+	}
+	// Storage: Nonfungible TokenChildren (r:1 w:0)
+	// Storage: Nonfungible TokenData (r:1 w:1)
+	// Storage: Nonfungible TokensBurnt (r:1 w:1)
+	// Storage: Nonfungible AccountBalance (r:1 w:1)
+	// Storage: Nonfungible Allowance (r:1 w:0)
+	// Storage: Nonfungible Owned (r:0 w:1)
+	// Storage: Nonfungible TokenProperties (r:0 w:1)
+	// Storage: Common CollectionById (r:1 w:0)
+	fn burn_recursively_breadth_plus_self_plus_self_per_each_raw(b: u32, ) -> Weight {
+		(0 as Weight)
+			// Standard Error: 42_828_000
+			.saturating_add((381_478_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add(RocksDbWeight::get().reads(6 as Weight))
+			.saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(b as Weight)))
+			.saturating_add(RocksDbWeight::get().writes(5 as Weight))
+			.saturating_add(RocksDbWeight::get().writes((4 as Weight).saturating_mul(b as Weight)))
+	}
 	// Storage: Nonfungible TokenData (r:1 w:1)
 	// Storage: Nonfungible AccountBalance (r:2 w:2)
 	// Storage: Nonfungible Allowance (r:1 w:0)
modifiedpallets/refungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/refungible/src/benchmarking.rs
+++ b/pallets/refungible/src/benchmarking.rs
@@ -50,7 +50,9 @@
 	Ok(TokenId(<TokensMinted<T>>::get(&collection.id)))
 }
 
-fn create_collection<T: Config>(owner: T::AccountId) -> Result<RefungibleHandle<T>, DispatchError> {
+fn create_collection<T: Config>(
+	owner: T::CrossAccountId,
+) -> Result<RefungibleHandle<T>, DispatchError> {
 	create_collection_raw(
 		owner,
 		CollectionMode::NFT,
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -17,12 +17,13 @@
 use core::marker::PhantomData;
 
 use sp_std::collections::btree_map::BTreeMap;
-use frame_support::{dispatch::DispatchResultWithPostInfo, fail, weights::Weight};
+use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, traits::Get};
 use up_data_structs::{
 	CollectionId, TokenId, CreateItemExData, CreateRefungibleExData, budget::Budget, Property,
 	PropertyKey, PropertyValue, PropertyKeyPermission, CreateItemData,
 };
 use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
+use pallet_structure::Error as StructureError;
 use sp_runtime::DispatchError;
 use sp_std::{vec::Vec, vec};
 
@@ -113,6 +114,15 @@
 	fn burn_from() -> Weight {
 		<SelfWeightOf<T>>::burn_from()
 	}
+
+	fn burn_recursively_self_raw() -> Weight {
+		// Read to get total balance
+		Self::burn_item() + T::DbWeight::get().reads(1)
+	}
+	fn burn_recursively_breadth_raw(_amount: u32) -> Weight {
+		// Refungible token can't have children
+		0
+	}
 }
 
 fn map_create_data<T: Config>(
@@ -205,6 +215,25 @@
 		)
 	}
 
+	fn burn_item_recursively(
+		&self,
+		sender: T::CrossAccountId,
+		token: TokenId,
+		self_budget: &dyn Budget,
+		_breadth_budget: &dyn Budget,
+	) -> DispatchResultWithPostInfo {
+		ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);
+		with_weight(
+			<Pallet<T>>::burn(
+				self,
+				&sender,
+				token,
+				<Balance<T>>::get((self.id, token, &sender)),
+			),
+			<CommonWeights<T>>::burn_recursively_self_raw(),
+		)
+	}
+
 	fn transfer(
 		&self,
 		from: T::CrossAccountId,
modifiedpallets/scheduler/src/lib.rsdiffbeforeafterboth
--- a/pallets/scheduler/src/lib.rs
+++ b/pallets/scheduler/src/lib.rs
@@ -60,8 +60,9 @@
 // Ensure we're `no_std` when compiling for Wasm.
 #![cfg_attr(not(feature = "std"), no_std)]
 
-#[cfg(feature = "runtime-benchmarks")]
-mod benchmarking;
+// FIXME
+// #[cfg(feature = "runtime-benchmarks")]
+// mod benchmarking;
 
 pub mod weights;
 
modifiedpallets/structure/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/structure/src/benchmarking.rs
+++ b/pallets/structure/src/benchmarking.rs
@@ -5,6 +5,7 @@
 use up_data_structs::{
 	CreateCollectionData, CollectionMode, CreateItemData, CreateNftData, budget::Unlimited,
 };
+use pallet_common::Config as CommonConfig;
 use pallet_evm::account::CrossAccountId;
 
 const SEED: u32 = 1;
@@ -14,8 +15,8 @@
 		let caller: T::AccountId = account("caller", 0, SEED);
 		let caller_cross = T::CrossAccountId::from_sub(caller.clone());
 
-		T::Currency::deposit_creating(&caller, T::CollectionCreationPrice::get());
-		T::CollectionDispatch::create(caller, CreateCollectionData {
+		<T as CommonConfig>::Currency::deposit_creating(&caller, T::CollectionCreationPrice::get());
+		T::CollectionDispatch::create(caller_cross.clone(), CreateCollectionData {
 			mode: CollectionMode::NFT,
 			..Default::default()
 		})?;
modifiedpallets/structure/src/lib.rsdiffbeforeafterboth
--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -3,7 +3,7 @@
 use pallet_common::CommonCollectionOperations;
 use sp_std::collections::btree_set::BTreeSet;
 
-use frame_support::dispatch::{DispatchError, DispatchResult};
+use frame_support::dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo};
 use frame_support::fail;
 pub use pallet::*;
 use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};
@@ -29,6 +29,8 @@
 		OuroborosDetected,
 		/// While searched for owner, encountered depth limit
 		DepthLimit,
+		/// While iterating over children, encountered breadth limit
+		BreadthLimit,
 		/// While searched for owner, found token owner by not-yet-existing token
 		TokenNotFound,
 	}
@@ -184,6 +186,19 @@
 		Err(<Error<T>>::DepthLimit.into())
 	}
 
+	pub fn burn_item_recursively(
+		from: T::CrossAccountId,
+		collection: CollectionId,
+		token: TokenId,
+		self_budget: &dyn Budget,
+		breadth_budget: &dyn Budget,
+	) -> DispatchResultWithPostInfo {
+		let handle = <CollectionHandle<T>>::try_get(collection)?;
+		let dispatch = T::CollectionDispatch::dispatch(handle);
+		let dispatch = dispatch.as_dyn();
+		dispatch.burn_item_recursively(from.clone(), token, self_budget, breadth_budget)
+	}
+
 	pub fn check_nesting(
 		from: T::CrossAccountId,
 		under: &T::CrossAccountId,
modifiedpallets/unique/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/unique/src/benchmarking.rs
+++ b/pallets/unique/src/benchmarking.rs
@@ -22,7 +22,10 @@
 use frame_support::traits::{tokens::currency::Currency, Get};
 use frame_benchmarking::{benchmarks, account};
 use sp_runtime::DispatchError;
-use pallet_common::benchmarking::{create_data, create_var_data, create_u16_data};
+use pallet_common::{
+	Config as CommonConfig,
+	benchmarking::{create_data, create_u16_data},
+};
 
 const SEED: u32 = 1;
 
@@ -30,7 +33,7 @@
 	owner: T::AccountId,
 	mode: CollectionMode,
 ) -> Result<CollectionId, DispatchError> {
-	T::Currency::deposit_creating(&owner, T::CollectionCreationPrice::get());
+	<T as CommonConfig>::Currency::deposit_creating(&owner, T::CollectionCreationPrice::get());
 	let col_name = create_u16_data::<MAX_COLLECTION_NAME_LENGTH>();
 	let col_desc = create_u16_data::<MAX_COLLECTION_DESCRIPTION_LENGTH>();
 	let token_prefix = create_data::<MAX_TOKEN_PREFIX_LENGTH>();
@@ -54,7 +57,7 @@
 		let token_prefix = create_data::<MAX_TOKEN_PREFIX_LENGTH>();
 		let mode: CollectionMode = CollectionMode::NFT;
 		let caller: T::AccountId = account("caller", 0, SEED);
-		T::Currency::deposit_creating(&caller, T::CollectionCreationPrice::get());
+		<T as CommonConfig>::Currency::deposit_creating(&caller, T::CollectionCreationPrice::get());
 	}: _(RawOrigin::Signed(caller.clone()), col_name.clone(), col_desc.clone(), token_prefix.clone(), mode)
 	verify {
 		assert_eq!(<pallet_common::CollectionById<T>>::get(CollectionId(1)).unwrap().owner, caller);
@@ -77,16 +80,6 @@
 		let collection = create_nft_collection::<T>(caller.clone())?;
 		<Pallet<T>>::add_to_allow_list(RawOrigin::Signed(caller.clone()).into(), collection, T::CrossAccountId::from_sub(allowlist_account.clone()))?;
 	}: _(RawOrigin::Signed(caller.clone()), collection, T::CrossAccountId::from_sub(allowlist_account))
-
-	set_public_access_mode {
-		let caller: T::AccountId = account("caller", 0, SEED);
-		let collection = create_nft_collection::<T>(caller.clone())?;
-	}: _(RawOrigin::Signed(caller.clone()), collection, AccessMode::AllowList)
-
-	set_mint_permission {
-		let caller: T::AccountId = account("caller", 0, SEED);
-		let collection = create_nft_collection::<T>(caller.clone())?;
-	}: _(RawOrigin::Signed(caller.clone()), collection, true)
 
 	change_collection_owner {
 		let caller: T::AccountId = account("caller", 0, SEED);
@@ -145,7 +138,6 @@
 			owner_can_transfer: Some(true),
 			sponsored_data_rate_limit: None,
 			transfers_enabled: Some(true),
-			nesting_rule: None,
 		};
 	}: set_collection_limits(RawOrigin::Signed(caller.clone()), collection, cl)
 }
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -469,6 +469,8 @@
 		#[derivative(Debug(format_with = "bounded::set_debug"))]
 		BoundedBTreeSet<CollectionId, ConstU32<16>>,
 	),
+	/// Used for tests
+	Permissive,
 }
 
 #[derive(Encode, Decode, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]
modifiedruntime/common/src/weights.rsdiffbeforeafterboth
--- a/runtime/common/src/weights.rs
+++ b/runtime/common/src/weights.rs
@@ -89,4 +89,12 @@
 	fn burn_from() -> Weight {
 		dispatch_weight::<T>() + max_weight_of!(burn_from())
 	}
+
+	fn burn_recursively_self_raw() -> Weight {
+		max_weight_of!(burn_recursively_self_raw())
+	}
+
+	fn burn_recursively_breadth_raw(amount: u32) -> Weight {
+		max_weight_of!(burn_recursively_breadth_raw(amount))
+	}
 }
modifiedruntime/unique/src/lib.rsdiffbeforeafterboth
--- a/runtime/unique/src/lib.rs
+++ b/runtime/unique/src/lib.rs
@@ -73,6 +73,7 @@
 	CollectionId, TokenId, TokenData, Property, PropertyKeyPermission, CollectionLimits,
 	CollectionStats, RpcCollection,
 	mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping},
+	TokenChild,
 };
 
 // use pallet_contracts::weights::WeightInfo;