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

difftreelog

fix revert set_allowance

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

8 files changed

modifiedpallets/common/src/lib.rsdiffbeforeafterboth
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	}11711172	pub fn clamp_permissions(1173		_mode: CollectionMode,1174		old_limit: &CollectionPermissions,1175		mut new_limit: CollectionPermissions,1176	) -> Result<CollectionPermissions, DispatchError> {1177		limit_default_clone!(old_limit, new_limit,1178			access => {},1179			mint_mode => {},1180			nesting => {},1181		);1182		Ok(new_limit)1183	}1184}11851186#[macro_export]1187macro_rules! unsupported {1188	() => {1189		Err(<Error<T>>::UnsupportedOperation.into())1190	};1191}11921193/// Worst cases1194pub trait CommonWeightInfo<CrossAccountId> {1195	fn create_item() -> Weight;1196	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1197	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1198	fn burn_item() -> Weight;1199	fn set_collection_properties(amount: u32) -> Weight;1200	fn delete_collection_properties(amount: u32) -> Weight;1201	fn set_token_properties(amount: u32) -> Weight;1202	fn delete_token_properties(amount: u32) -> Weight;1203	fn set_property_permissions(amount: u32) -> Weight;1204	fn transfer() -> Weight;1205	fn approve() -> Weight;1206	fn transfer_from() -> Weight;1207	fn burn_from() -> Weight;12081209	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1210	/// whole users's balance1211	///1212	/// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead1213	fn burn_recursively_self_raw() -> Weight;1214	/// Cost of iterating over `amount` children while burning, without counting child burning itself1215	///1216	/// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead1217	fn burn_recursively_breadth_raw(amount: u32) -> Weight;12181219	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1220		Self::burn_recursively_self_raw()1221			.saturating_mul(max_selfs.max(1) as u64)1222			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1223	}1224}12251226pub trait CommonCollectionOperations<T: Config> {1227	fn create_item(1228		&self,1229		sender: T::CrossAccountId,1230		to: T::CrossAccountId,1231		data: CreateItemData,1232		nesting_budget: &dyn Budget,1233	) -> DispatchResultWithPostInfo;1234	fn create_multiple_items(1235		&self,1236		sender: T::CrossAccountId,1237		to: T::CrossAccountId,1238		data: Vec<CreateItemData>,1239		nesting_budget: &dyn Budget,1240	) -> DispatchResultWithPostInfo;1241	fn create_multiple_items_ex(1242		&self,1243		sender: T::CrossAccountId,1244		data: CreateItemExData<T::CrossAccountId>,1245		nesting_budget: &dyn Budget,1246	) -> DispatchResultWithPostInfo;1247	fn burn_item(1248		&self,1249		sender: T::CrossAccountId,1250		token: TokenId,1251		amount: u128,1252	) -> DispatchResultWithPostInfo;1253	fn burn_item_recursively(1254		&self,1255		sender: T::CrossAccountId,1256		token: TokenId,1257		self_budget: &dyn Budget,1258		breadth_budget: &dyn Budget,1259	) -> DispatchResultWithPostInfo;1260	fn set_collection_properties(1261		&self,1262		sender: T::CrossAccountId,1263		properties: Vec<Property>,1264	) -> DispatchResultWithPostInfo;1265	fn delete_collection_properties(1266		&self,1267		sender: &T::CrossAccountId,1268		property_keys: Vec<PropertyKey>,1269	) -> DispatchResultWithPostInfo;1270	fn set_token_properties(1271		&self,1272		sender: T::CrossAccountId,1273		token_id: TokenId,1274		property: Vec<Property>,1275	) -> DispatchResultWithPostInfo;1276	fn delete_token_properties(1277		&self,1278		sender: T::CrossAccountId,1279		token_id: TokenId,1280		property_keys: Vec<PropertyKey>,1281	) -> DispatchResultWithPostInfo;1282	fn set_property_permissions(1283		&self,1284		sender: &T::CrossAccountId,1285		property_permissions: Vec<PropertyKeyPermission>,1286	) -> DispatchResultWithPostInfo;1287	fn transfer(1288		&self,1289		sender: T::CrossAccountId,1290		to: T::CrossAccountId,1291		token: TokenId,1292		amount: u128,1293		nesting_budget: &dyn Budget,1294	) -> DispatchResultWithPostInfo;1295	fn approve(1296		&self,1297		sender: T::CrossAccountId,1298		spender: T::CrossAccountId,1299		token: TokenId,1300		amount: u128,1301		nesting_budget: &dyn Budget,1302	) -> DispatchResultWithPostInfo;1303	fn transfer_from(1304		&self,1305		sender: T::CrossAccountId,1306		from: T::CrossAccountId,1307		to: T::CrossAccountId,1308		token: TokenId,1309		amount: u128,1310		nesting_budget: &dyn Budget,1311	) -> DispatchResultWithPostInfo;1312	fn burn_from(1313		&self,1314		sender: T::CrossAccountId,1315		from: T::CrossAccountId,1316		token: TokenId,1317		amount: u128,1318		nesting_budget: &dyn Budget,1319	) -> DispatchResultWithPostInfo;13201321	fn check_nesting(1322		&self,1323		sender: T::CrossAccountId,1324		from: (CollectionId, TokenId),1325		under: TokenId,1326		budget: &dyn Budget,1327	) -> DispatchResult;13281329	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13301331	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13321333	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1334	fn collection_tokens(&self) -> Vec<TokenId>;1335	fn token_exists(&self, token: TokenId) -> bool;1336	fn last_token_id(&self) -> TokenId;13371338	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1339	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1340	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1341	/// Amount of unique collection tokens1342	fn total_supply(&self) -> u32;1343	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1344	fn account_balance(&self, account: T::CrossAccountId) -> u32;1345	/// Amount of specific token account have (Applicable to fungible/refungible)1346	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1347	fn allowance(1348		&self,1349		sender: T::CrossAccountId,1350		spender: T::CrossAccountId,1351		token: TokenId,1352	) -> u128;1353}13541355// Flexible enough for implementing CommonCollectionOperations1356pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1357	let post_info = PostDispatchInfo {1358		actual_weight: Some(weight),1359		pays_fee: Pays::Yes,1360	};1361	match res {1362		Ok(()) => Ok(post_info),1363		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1364	}1365}13661367impl<T: Config> From<PropertiesError> for Error<T> {1368	fn from(error: PropertiesError) -> Self {1369		match error {1370			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1371			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1372			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1373			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1374			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1375		}1376	}1377}
after · 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	}11711172	pub fn clamp_permissions(1173		_mode: CollectionMode,1174		old_limit: &CollectionPermissions,1175		mut new_limit: CollectionPermissions,1176	) -> Result<CollectionPermissions, DispatchError> {1177		limit_default_clone!(old_limit, new_limit,1178			access => {},1179			mint_mode => {},1180			nesting => {},1181		);1182		Ok(new_limit)1183	}1184}11851186#[macro_export]1187macro_rules! unsupported {1188	() => {1189		Err(<Error<T>>::UnsupportedOperation.into())1190	};1191}11921193/// Worst cases1194pub trait CommonWeightInfo<CrossAccountId> {1195	fn create_item() -> Weight;1196	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1197	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1198	fn burn_item() -> Weight;1199	fn set_collection_properties(amount: u32) -> Weight;1200	fn delete_collection_properties(amount: u32) -> Weight;1201	fn set_token_properties(amount: u32) -> Weight;1202	fn delete_token_properties(amount: u32) -> Weight;1203	fn set_property_permissions(amount: u32) -> Weight;1204	fn transfer() -> Weight;1205	fn approve() -> Weight;1206	fn transfer_from() -> Weight;1207	fn burn_from() -> Weight;12081209	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1210	/// whole users's balance1211	///1212	/// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead1213	fn burn_recursively_self_raw() -> Weight;1214	/// Cost of iterating over `amount` children while burning, without counting child burning itself1215	///1216	/// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead1217	fn burn_recursively_breadth_raw(amount: u32) -> Weight;12181219	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1220		Self::burn_recursively_self_raw()1221			.saturating_mul(max_selfs.max(1) as u64)1222			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1223	}1224}12251226pub trait CommonCollectionOperations<T: Config> {1227	fn create_item(1228		&self,1229		sender: T::CrossAccountId,1230		to: T::CrossAccountId,1231		data: CreateItemData,1232		nesting_budget: &dyn Budget,1233	) -> DispatchResultWithPostInfo;1234	fn create_multiple_items(1235		&self,1236		sender: T::CrossAccountId,1237		to: T::CrossAccountId,1238		data: Vec<CreateItemData>,1239		nesting_budget: &dyn Budget,1240	) -> DispatchResultWithPostInfo;1241	fn create_multiple_items_ex(1242		&self,1243		sender: T::CrossAccountId,1244		data: CreateItemExData<T::CrossAccountId>,1245		nesting_budget: &dyn Budget,1246	) -> DispatchResultWithPostInfo;1247	fn burn_item(1248		&self,1249		sender: T::CrossAccountId,1250		token: TokenId,1251		amount: u128,1252	) -> DispatchResultWithPostInfo;1253	fn burn_item_recursively(1254		&self,1255		sender: T::CrossAccountId,1256		token: TokenId,1257		self_budget: &dyn Budget,1258		breadth_budget: &dyn Budget,1259	) -> DispatchResultWithPostInfo;1260	fn set_collection_properties(1261		&self,1262		sender: T::CrossAccountId,1263		properties: Vec<Property>,1264	) -> DispatchResultWithPostInfo;1265	fn delete_collection_properties(1266		&self,1267		sender: &T::CrossAccountId,1268		property_keys: Vec<PropertyKey>,1269	) -> DispatchResultWithPostInfo;1270	fn set_token_properties(1271		&self,1272		sender: T::CrossAccountId,1273		token_id: TokenId,1274		property: Vec<Property>,1275	) -> DispatchResultWithPostInfo;1276	fn delete_token_properties(1277		&self,1278		sender: T::CrossAccountId,1279		token_id: TokenId,1280		property_keys: Vec<PropertyKey>,1281	) -> DispatchResultWithPostInfo;1282	fn set_property_permissions(1283		&self,1284		sender: &T::CrossAccountId,1285		property_permissions: Vec<PropertyKeyPermission>,1286	) -> DispatchResultWithPostInfo;1287	fn transfer(1288		&self,1289		sender: T::CrossAccountId,1290		to: T::CrossAccountId,1291		token: TokenId,1292		amount: u128,1293		nesting_budget: &dyn Budget,1294	) -> DispatchResultWithPostInfo;1295	fn approve(1296		&self,1297		sender: T::CrossAccountId,1298		spender: T::CrossAccountId,1299		token: TokenId,1300		amount: u128,1301	) -> DispatchResultWithPostInfo;1302	fn transfer_from(1303		&self,1304		sender: T::CrossAccountId,1305		from: T::CrossAccountId,1306		to: T::CrossAccountId,1307		token: TokenId,1308		amount: u128,1309		nesting_budget: &dyn Budget,1310	) -> DispatchResultWithPostInfo;1311	fn burn_from(1312		&self,1313		sender: T::CrossAccountId,1314		from: T::CrossAccountId,1315		token: TokenId,1316		amount: u128,1317		nesting_budget: &dyn Budget,1318	) -> DispatchResultWithPostInfo;13191320	fn check_nesting(1321		&self,1322		sender: T::CrossAccountId,1323		from: (CollectionId, TokenId),1324		under: TokenId,1325		budget: &dyn Budget,1326	) -> DispatchResult;13271328	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13291330	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13311332	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1333	fn collection_tokens(&self) -> Vec<TokenId>;1334	fn token_exists(&self, token: TokenId) -> bool;1335	fn last_token_id(&self) -> TokenId;13361337	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1338	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1339	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1340	/// Amount of unique collection tokens1341	fn total_supply(&self) -> u32;1342	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1343	fn account_balance(&self, account: T::CrossAccountId) -> u32;1344	/// Amount of specific token account have (Applicable to fungible/refungible)1345	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1346	fn allowance(1347		&self,1348		sender: T::CrossAccountId,1349		spender: T::CrossAccountId,1350		token: TokenId,1351	) -> u128;1352}13531354// Flexible enough for implementing CommonCollectionOperations1355pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1356	let post_info = PostDispatchInfo {1357		actual_weight: Some(weight),1358		pays_fee: Pays::Yes,1359	};1360	match res {1361		Ok(()) => Ok(post_info),1362		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1363	}1364}13651366impl<T: Config> From<PropertiesError> for Error<T> {1367	fn from(error: PropertiesError) -> Self {1368		match error {1369			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1370			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1371			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1372			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1373			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1374		}1375	}1376}
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -226,7 +226,6 @@
 		spender: T::CrossAccountId,
 		token: TokenId,
 		amount: u128,
-		_nesting_budget: &dyn Budget,
 	) -> DispatchResultWithPostInfo {
 		ensure!(
 			token == TokenId::default(),
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -308,15 +308,14 @@
 		spender: T::CrossAccountId,
 		token: TokenId,
 		amount: u128,
-		nesting_budget: &dyn Budget,
 	) -> DispatchResultWithPostInfo {
 		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
 
 		with_weight(
 			if amount == 1 {
-				<Pallet<T>>::set_allowance(self, &sender, token, Some(&spender), nesting_budget)
+				<Pallet<T>>::set_allowance(self, &sender, token, Some(&spender))
 			} else {
-				<Pallet<T>>::set_allowance(self, &sender, token, None, nesting_budget)
+				<Pallet<T>>::set_allowance(self, &sender, token, None)
 			},
 			<CommonWeights<T>>::approve(),
 		)
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -269,11 +269,8 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let approved = T::CrossAccountId::from_eth(approved);
 		let token = token_id.try_into()?;
-		let budget = self
-			.recorder
-			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved), &budget)
+		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))
 			.map_err(dispatch_to_evm::<T>)?;
 		Ok(())
 	}
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -878,7 +878,6 @@
 		sender: &T::CrossAccountId,
 		token: TokenId,
 		spender: Option<&T::CrossAccountId>,
-		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
 		if collection.permissions.access() == AccessMode::AllowList {
 			collection.check_allowlist(sender)?;
@@ -890,16 +889,10 @@
 		if let Some(spender) = spender {
 			<PalletCommon<T>>::ensure_correct_receiver(spender)?;
 		}
-
-		let is_owned = <PalletStructure<T>>::check_indirectly_owned(
-			sender.clone(),
-			collection.id,
-			token,
-			None,
-			nesting_budget
-		)?;
 
-		if !is_owned {
+		let token_data =
+			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;
+		if &token_data.owner != sender {
 			ensure!(
 				collection.ignores_owned_amount(sender),
 				<CommonError<T>>::CantApproveMoreThanOwned
@@ -926,9 +919,6 @@
 			// `from`, `to` checked in [`transfer`]
 			collection.check_allowlist(spender)?;
 		}
-		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {
-			return Ok(());
-		}
 		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {
 			// TODO: should collection owner be allowed to perform this transfer?
 			ensure!(
@@ -943,6 +933,9 @@
 			);
 			return Ok(());
 		}
+		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {
+			return Ok(());
+		}
 		ensure!(
 			collection.ignores_allowance(spender),
 			<CommonError<T>>::ApprovedValueTooLow
modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/lib.rs
+++ b/pallets/proxy-rmrk-core/src/lib.rs
@@ -414,13 +414,14 @@
 					let is_approval_required = cross_sender != spender;
 
 					if is_approval_required {
-						<PalletNft<T>>::set_allowance(
-							&collection,
-							&cross_sender,
-							nft_id,
-							Some(&spender),
-							&budget
-						).map_err(Self::map_common_err_to_proxy)?;
+						// FIXME
+						// <PalletNft<T>>::set_allowance(
+						// 	&collection,
+						// 	&cross_sender,
+						// 	nft_id,
+						// 	Some(&spender),
+						// 	&budget
+						// ).map_err(Self::map_common_err_to_proxy)?;
 
 						return Ok(());
 					}
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -254,7 +254,6 @@
 		spender: T::CrossAccountId,
 		token: TokenId,
 		amount: u128,
-		_nesting_budget: &dyn Budget,
 	) -> DispatchResultWithPostInfo {
 		with_weight(
 			<Pallet<T>>::set_allowance(self, &sender, &spender, token, amount),
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -815,9 +815,8 @@
 		#[transactional]
 		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
-			let budget = budget::Value::new(NESTING_BUDGET);
 
-			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount, &budget))
+			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))
 		}
 
 		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.