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

difftreelog

source

pallets/common/src/lib.rs34.9 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::account::CrossAccountId;25use frame_support::{26	dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},27	ensure,28	traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},29	BoundedVec,30	weights::Pays,31	transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35	COLLECTION_NUMBER_LIMIT,36	Collection,37	RpcCollection,38	CollectionId,39	CreateItemData,40	MAX_TOKEN_PREFIX_LENGTH,41	COLLECTION_ADMINS_LIMIT,42	TokenId,43	CollectionStats,44	MAX_TOKEN_OWNERSHIP,45	CollectionMode,46	NFT_SPONSOR_TRANSFER_TIMEOUT,47	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,48	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49	MAX_SPONSOR_TIMEOUT,50	CUSTOM_DATA_LIMIT,51	CollectionLimits,52	CreateCollectionData,53	SponsorshipState,54	CreateItemExData,55	SponsoringRateLimit,56	budget::Budget,57	COLLECTION_FIELD_LIMIT,58	PhantomType,59	Property,60	Properties,61	PropertiesPermissionMap,62	PropertyKey,63	PropertyValue,64	PropertyPermission,65	PropertiesError,66	PropertyKeyPermission,67	TokenData,68	TrySetProperty,69	PropertyScope,70	// RMRK71	RmrkCollectionInfo,72	RmrkInstanceInfo,73	RmrkResourceInfo,74	RmrkPropertyInfo,75	RmrkBaseInfo,76	RmrkPartType,77	RmrkTheme,78	RmrkNftChild,79	CollectionPermissions,80	SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97	pub id: CollectionId,98	collection: Collection<T::AccountId>,99	pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102	fn recorder(&self) -> &SubstrateRecorder<T> {103		&self.recorder104	}105	fn into_recorder(self) -> SubstrateRecorder<T> {106		self.recorder107	}108}109impl<T: Config> CollectionHandle<T> {110	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111		<CollectionById<T>>::get(id).map(|collection| Self {112			id,113			collection,114			recorder: SubstrateRecorder::new(gas_limit),115		})116	}117	pub fn new(id: CollectionId) -> Option<Self> {118		Self::new_with_gas_limit(id, u64::MAX)119	}120	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {121		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)122	}123	pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {124		self.recorder125			.consume_gas(T::GasWeightMapping::weight_to_gas(126				<T as frame_system::Config>::DbWeight::get()127					.read128					.saturating_mul(reads),129			))130	}131	pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {132		self.recorder133			.consume_gas(T::GasWeightMapping::weight_to_gas(134				<T as frame_system::Config>::DbWeight::get()135					.write136					.saturating_mul(writes),137			))138	}139	pub fn save(self) -> DispatchResult {140		<CollectionById<T>>::insert(self.id, self.collection);141		Ok(())142	}143}144impl<T: Config> Deref for CollectionHandle<T> {145	type Target = Collection<T::AccountId>;146147	fn deref(&self) -> &Self::Target {148		&self.collection149	}150}151152impl<T: Config> DerefMut for CollectionHandle<T> {153	fn deref_mut(&mut self) -> &mut Self::Target {154		&mut self.collection155	}156}157158impl<T: Config> CollectionHandle<T> {159	pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {160		ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);161		Ok(())162	}163	pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {164		*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))165	}166	pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {167		ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);168		Ok(())169	}170	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {171		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)172	}173	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {174		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)175	}176	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {177		ensure!(178			<Allowlist<T>>::get((self.id, user)),179			<Error<T>>::AddressNotInAllowlist180		);181		Ok(())182	}183}184185#[frame_support::pallet]186pub mod pallet {187	use super::*;188	use pallet_evm::account;189	use dispatch::CollectionDispatch;190	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};191	use frame_system::pallet_prelude::*;192	use frame_support::traits::Currency;193	use up_data_structs::{TokenId, mapping::TokenAddressMapping};194	use scale_info::TypeInfo;195	use weights::WeightInfo;196197	#[pallet::config]198	pub trait Config:199		frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config200	{201		type WeightInfo: WeightInfo;202		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;203204		type Currency: Currency<Self::AccountId>;205206		#[pallet::constant]207		type CollectionCreationPrice: Get<208			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,209		>;210		type CollectionDispatch: CollectionDispatch<Self>;211212		type TreasuryAccountId: Get<Self::AccountId>;213214		type EvmTokenAddressMapping: TokenAddressMapping<H160>;215		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;216	}217218	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);219220	#[pallet::pallet]221	#[pallet::storage_version(STORAGE_VERSION)]222	#[pallet::generate_store(pub(super) trait Store)]223	pub struct Pallet<T>(_);224225	#[pallet::extra_constants]226	impl<T: Config> Pallet<T> {227		pub fn collection_admins_limit() -> u32 {228			COLLECTION_ADMINS_LIMIT229		}230	}231232	#[pallet::event]233	#[pallet::generate_deposit(pub fn deposit_event)]234	pub enum Event<T: Config> {235		/// New collection was created236		///237		/// # Arguments238		///239		/// * collection_id: Globally unique identifier of newly created collection.240		///241		/// * mode: [CollectionMode] converted into u8.242		///243		/// * account_id: Collection owner.244		CollectionCreated(CollectionId, u8, T::AccountId),245246		/// New collection was destroyed247		///248		/// # Arguments249		///250		/// * collection_id: Globally unique identifier of collection.251		CollectionDestroyed(CollectionId),252253		/// New item was created.254		///255		/// # Arguments256		///257		/// * collection_id: Id of the collection where item was created.258		///259		/// * item_id: Id of an item. Unique within the collection.260		///261		/// * recipient: Owner of newly created item262		///263		/// * amount: Always 1 for NFT264		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),265266		/// Collection item was burned.267		///268		/// # Arguments269		///270		/// * collection_id.271		///272		/// * item_id: Identifier of burned NFT.273		///274		/// * owner: which user has destroyed its tokens275		///276		/// * amount: Always 1 for NFT277		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),278279		/// Item was transferred280		///281		/// * collection_id: Id of collection to which item is belong282		///283		/// * item_id: Id of an item284		///285		/// * sender: Original owner of item286		///287		/// * recipient: New owner of item288		///289		/// * amount: Always 1 for NFT290		Transfer(291			CollectionId,292			TokenId,293			T::CrossAccountId,294			T::CrossAccountId,295			u128,296		),297298		/// * collection_id299		///300		/// * item_id301		///302		/// * sender303		///304		/// * spender305		///306		/// * amount307		Approved(308			CollectionId,309			TokenId,310			T::CrossAccountId,311			T::CrossAccountId,312			u128,313		),314315		CollectionPropertySet(CollectionId, PropertyKey),316317		CollectionPropertyDeleted(CollectionId, PropertyKey),318319		TokenPropertySet(CollectionId, TokenId, PropertyKey),320321		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),322323		PropertyPermissionSet(CollectionId, PropertyKey),324	}325326	#[pallet::error]327	pub enum Error<T> {328		/// This collection does not exist.329		CollectionNotFound,330		/// Sender parameter and item owner must be equal.331		MustBeTokenOwner,332		/// No permission to perform action333		NoPermission,334		/// Destroying only empty collections is allowed335		CantDestroyNotEmptyCollection,336		/// Collection is not in mint mode.337		PublicMintingNotAllowed,338		/// Address is not in allow list.339		AddressNotInAllowlist,340341		/// Collection name can not be longer than 63 char.342		CollectionNameLimitExceeded,343		/// Collection description can not be longer than 255 char.344		CollectionDescriptionLimitExceeded,345		/// Token prefix can not be longer than 15 char.346		CollectionTokenPrefixLimitExceeded,347		/// Total collections bound exceeded.348		TotalCollectionsLimitExceeded,349		/// Exceeded max admin count350		CollectionAdminCountExceeded,351		/// Collection limit bounds per collection exceeded352		CollectionLimitBoundsExceeded,353		/// Tried to enable permissions which are only permitted to be disabled354		OwnerPermissionsCantBeReverted,355		/// Collection settings not allowing items transferring356		TransferNotAllowed,357		/// Account token limit exceeded per collection358		AccountTokenLimitExceeded,359		/// Collection token limit exceeded360		CollectionTokenLimitExceeded,361		/// Metadata flag frozen362		MetadataFlagFrozen,363364		/// Item not exists.365		TokenNotFound,366		/// Item balance not enough.367		TokenValueTooLow,368		/// Requested value more than approved.369		ApprovedValueTooLow,370		/// Tried to approve more than owned371		CantApproveMoreThanOwned,372373		/// Can't transfer tokens to ethereum zero address374		AddressIsZero,375		/// Target collection doesn't supports this operation376		UnsupportedOperation,377378		/// Not sufficient founds to perform action379		NotSufficientFounds,380381		/// Collection has nesting disabled382		NestingIsDisabled,383		/// Only owner may nest tokens under this collection384		OnlyOwnerAllowedToNest,385		/// Only tokens from specific collections may nest tokens under this386		SourceCollectionIsNotAllowedToNest,387388		/// Tried to store more data than allowed in collection field389		CollectionFieldSizeExceeded,390391		/// Tried to store more property data than allowed392		NoSpaceForProperty,393394		/// Tried to store more property keys than allowed395		PropertyLimitReached,396397		/// Property key is too long398		PropertyKeyIsTooLong,399400		/// Only ASCII letters, digits, and '_', '-' are allowed401		InvalidCharacterInPropertyKey,402403		/// Empty property keys are forbidden404		EmptyPropertyKey,405	}406407	#[pallet::storage]408	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;409	#[pallet::storage]410	pub type DestroyedCollectionCount<T> =411		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;412413	/// Collection info414	#[pallet::storage]415	pub type CollectionById<T> = StorageMap<416		Hasher = Blake2_128Concat,417		Key = CollectionId,418		Value = Collection<<T as frame_system::Config>::AccountId>,419		QueryKind = OptionQuery,420	>;421422	/// Collection properties423	#[pallet::storage]424	#[pallet::getter(fn collection_properties)]425	pub type CollectionProperties<T> = StorageMap<426		Hasher = Blake2_128Concat,427		Key = CollectionId,428		Value = Properties,429		QueryKind = ValueQuery,430		OnEmpty = up_data_structs::CollectionProperties,431	>;432433	#[pallet::storage]434	#[pallet::getter(fn property_permissions)]435	pub type CollectionPropertyPermissions<T> = StorageMap<436		Hasher = Blake2_128Concat,437		Key = CollectionId,438		Value = PropertiesPermissionMap,439		QueryKind = ValueQuery,440	>;441442	#[pallet::storage]443	pub type AdminAmount<T> = StorageMap<444		Hasher = Blake2_128Concat,445		Key = CollectionId,446		Value = u32,447		QueryKind = ValueQuery,448	>;449450	/// List of collection admins451	#[pallet::storage]452	pub type IsAdmin<T: Config> = StorageNMap<453		Key = (454			Key<Blake2_128Concat, CollectionId>,455			Key<Blake2_128Concat, T::CrossAccountId>,456		),457		Value = bool,458		QueryKind = ValueQuery,459	>;460461	/// Allowlisted collection users462	#[pallet::storage]463	pub type Allowlist<T: Config> = StorageNMap<464		Key = (465			Key<Blake2_128Concat, CollectionId>,466			Key<Blake2_128Concat, T::CrossAccountId>,467		),468		Value = bool,469		QueryKind = ValueQuery,470	>;471472	/// Not used by code, exists only to provide some types to metadata473	#[pallet::storage]474	pub type DummyStorageValue<T: Config> = StorageValue<475		Value = (476			CollectionStats,477			CollectionId,478			TokenId,479			PhantomType<TokenData<T::CrossAccountId>>,480			PhantomType<RpcCollection<T::AccountId>>,481			// RMRK482			PhantomType<RmrkCollectionInfo<T::AccountId>>,483			PhantomType<RmrkInstanceInfo<T::AccountId>>,484			PhantomType<RmrkResourceInfo>,485			PhantomType<RmrkPropertyInfo>,486			PhantomType<RmrkBaseInfo<T::AccountId>>,487			PhantomType<RmrkPartType>,488			PhantomType<RmrkTheme>,489			PhantomType<RmrkNftChild>,490		),491		QueryKind = OptionQuery,492	>;493494	#[pallet::hooks]495	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {496		fn on_runtime_upgrade() -> Weight {497			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {498				use up_data_structs::{CollectionVersion1, CollectionVersion2};499				<CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {500					let mut props = Vec::new();501					if !v.offchain_schema.is_empty() {502						props.push(Property {503							key: b"_old_offchainSchema".to_vec().try_into().unwrap(),504							value: v.offchain_schema.clone().into_inner().try_into().expect("offchain schema too big"),505						});506					}507					if !v.variable_on_chain_schema.is_empty() {508						props.push(Property {509							key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),510							value: v.variable_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),511						});512					}513					if !v.const_on_chain_schema.is_empty() {514						props.push(Property {515							key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),516							value: v.const_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),517						});518					}519					props.push(Property {520						key: b"_old_schemaVersion".to_vec().try_into().unwrap(),521						value: match v.schema_version {522							SchemaVersion::ImageURL => b"ImageUrl".as_slice(),523							SchemaVersion::Unique => b"Unique".as_slice(),524						}.to_vec().try_into().unwrap(),525					});526					Self::set_scoped_collection_properties(527						id,528						PropertyScope::None,529						props.into_iter(),530					).expect("existing data larger than properties");531					let mut new = CollectionVersion2::from(v.clone());532					new.permissions.access = Some(v.access);533					new.permissions.mint_mode = Some(v.mint_mode);534					Some(new)535				});536			}537538			0539		}540	}541}542543impl<T: Config> Pallet<T> {544	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens545	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {546		ensure!(547			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,548			<Error<T>>::AddressIsZero549		);550		Ok(())551	}552	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {553		<IsAdmin<T>>::iter_prefix((collection,))554			.map(|(a, _)| a)555			.collect()556	}557	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {558		<Allowlist<T>>::iter_prefix((collection,))559			.map(|(a, _)| a)560			.collect()561	}562	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {563		<Allowlist<T>>::get((collection, user))564	}565	pub fn collection_stats() -> CollectionStats {566		let created = <CreatedCollectionCount<T>>::get();567		let destroyed = <DestroyedCollectionCount<T>>::get();568		CollectionStats {569			created: created.0,570			destroyed: destroyed.0,571			alive: created.0 - destroyed.0,572		}573	}574575	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {576		let collection = <CollectionById<T>>::get(collection);577		if collection.is_none() {578			return None;579		}580581		let collection = collection.unwrap();582		let limits = collection.limits;583		let effective_limits = CollectionLimits {584			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),585			sponsored_data_size: Some(limits.sponsored_data_size()),586			sponsored_data_rate_limit: Some(587				limits588					.sponsored_data_rate_limit589					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),590			),591			token_limit: Some(limits.token_limit()),592			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(593				match collection.mode {594					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,595					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,596					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,597				},598			)),599			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),600			owner_can_transfer: Some(limits.owner_can_transfer()),601			owner_can_destroy: Some(limits.owner_can_destroy()),602			transfers_enabled: Some(limits.transfers_enabled()),603		};604605		Some(effective_limits)606	}607608	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {609		let Collection {610			name,611			description,612			owner,613			mode,614			token_prefix,615			sponsorship,616			limits,617			permissions,618		} = <CollectionById<T>>::get(collection)?;619620		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)621			.into_iter()622			.map(|(key, permission)| PropertyKeyPermission {623				key,624				permission,625			})626			.collect();627628		let properties = <CollectionProperties<T>>::get(collection)629			.into_iter()630			.map(|(key, value)| Property {631				key,632				value,633			})634			.collect();635636		Some(RpcCollection {637			name: name.into_inner(),638			description: description.into_inner(),639			owner,640			mode,641			token_prefix: token_prefix.into_inner(),642			sponsorship,643			limits,644			permissions,645			token_property_permissions,646			properties,647		})648	}649}650651macro_rules! limit_default {652	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{653		$(654			if let Some($new) = $new.$field {655				let $old = $old.$field($($arg)?);656				let _ = $new;657				let _ = $old;658				$check659			} else {660				$new.$field = $old.$field661			}662		)*663	}};664}665macro_rules! limit_default_clone {666	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{667		$(668			if let Some($new) = $new.$field.clone() {669				let $old = $old.$field($($arg)?);670				let _ = $new;671				let _ = $old;672				$check673			} else {674				$new.$field = $old.$field.clone()675			}676		)*677	}};678}679680impl<T: Config> Pallet<T> {681	pub fn init_collection(682		owner: T::AccountId,683		data: CreateCollectionData<T::AccountId>,684	) -> Result<CollectionId, DispatchError> {685		{686			ensure!(687				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,688				Error::<T>::CollectionTokenPrefixLimitExceeded689			);690		}691692		let created_count = <CreatedCollectionCount<T>>::get()693			.0694			.checked_add(1)695			.ok_or(ArithmeticError::Overflow)?;696		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;697		let id = CollectionId(created_count);698699		// bound Total number of collections700		ensure!(701			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,702			<Error<T>>::TotalCollectionsLimitExceeded703		);704705		// =========706707		let collection = Collection {708			owner: owner.clone(),709			name: data.name,710			mode: data.mode.clone(),711			description: data.description,712			token_prefix: data.token_prefix,713			sponsorship: data714				.pending_sponsor715				.map(SponsorshipState::Unconfirmed)716				.unwrap_or_default(),717			limits: data718				.limits719				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))720				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,721			permissions: data722				.permissions723				.map(|permissions| Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions))724				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,725		};726727		let mut collection_properties = up_data_structs::CollectionProperties::get();728		collection_properties729			.try_set_from_iter(data.properties.into_iter())730			.map_err(<Error<T>>::from)?;731732		CollectionProperties::<T>::insert(id, collection_properties);733734		let mut token_props_permissions = PropertiesPermissionMap::new();735		token_props_permissions736			.try_set_from_iter(data.token_property_permissions.into_iter())737			.map_err(<Error<T>>::from)?;738739		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);740741		// Take a (non-refundable) deposit of collection creation742		{743			let mut imbalance =744				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();745			imbalance.subsume(746				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(747					&T::TreasuryAccountId::get(),748					T::CollectionCreationPrice::get(),749				),750			);751			<T as Config>::Currency::settle(752				&owner,753				imbalance,754				WithdrawReasons::TRANSFER,755				ExistenceRequirement::KeepAlive,756			)757			.map_err(|_| Error::<T>::NotSufficientFounds)?;758		}759760		<CreatedCollectionCount<T>>::put(created_count);761		<Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));762		<CollectionById<T>>::insert(id, collection);763		Ok(id)764	}765766	pub fn destroy_collection(767		collection: CollectionHandle<T>,768		sender: &T::CrossAccountId,769	) -> DispatchResult {770		ensure!(771			collection.limits.owner_can_destroy(),772			<Error<T>>::NoPermission,773		);774		collection.check_is_owner(sender)?;775776		let destroyed_collections = <DestroyedCollectionCount<T>>::get()777			.0778			.checked_add(1)779			.ok_or(ArithmeticError::Overflow)?;780781		// =========782783		<DestroyedCollectionCount<T>>::put(destroyed_collections);784		<CollectionById<T>>::remove(collection.id);785		<AdminAmount<T>>::remove(collection.id);786		<IsAdmin<T>>::remove_prefix((collection.id,), None);787		<Allowlist<T>>::remove_prefix((collection.id,), None);788		<CollectionProperties<T>>::remove(collection.id);789790		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));791		Ok(())792	}793794	pub fn set_collection_property(795		collection: &CollectionHandle<T>,796		sender: &T::CrossAccountId,797		property: Property,798	) -> DispatchResult {799		collection.check_is_owner_or_admin(sender)?;800801		CollectionProperties::<T>::try_mutate(collection.id, |properties| {802			let property = property.clone();803			properties.try_set(property.key, property.value)804		})805		.map_err(<Error<T>>::from)?;806807		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));808809		Ok(())810	}811812	pub fn set_scoped_collection_property(813		collection_id: CollectionId,814		scope: PropertyScope,815		property: Property,816	) -> DispatchResult {817		CollectionProperties::<T>::try_mutate(collection_id, |properties| {818			properties.try_scoped_set(scope, property.key, property.value)819		})820		.map_err(<Error<T>>::from)?;821822		Ok(())823	}824825	pub fn set_scoped_collection_properties(826		collection_id: CollectionId,827		scope: PropertyScope,828		properties: impl Iterator<Item = Property>,829	) -> DispatchResult {830		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {831			stored_properties.try_scoped_set_from_iter(scope, properties)832		})833		.map_err(<Error<T>>::from)?;834835		Ok(())836	}837838	#[transactional]839	pub fn set_collection_properties(840		collection: &CollectionHandle<T>,841		sender: &T::CrossAccountId,842		properties: Vec<Property>,843	) -> DispatchResult {844		for property in properties {845			Self::set_collection_property(collection, sender, property)?;846		}847848		Ok(())849	}850851	pub fn delete_collection_property(852		collection: &CollectionHandle<T>,853		sender: &T::CrossAccountId,854		property_key: PropertyKey,855	) -> DispatchResult {856		collection.check_is_owner_or_admin(sender)?;857858		CollectionProperties::<T>::try_mutate(collection.id, |properties| {859			properties.remove(&property_key)860		})861		.map_err(<Error<T>>::from)?;862863		Self::deposit_event(Event::CollectionPropertyDeleted(864			collection.id,865			property_key,866		));867868		Ok(())869	}870871	#[transactional]872	pub fn delete_collection_properties(873		collection: &CollectionHandle<T>,874		sender: &T::CrossAccountId,875		property_keys: Vec<PropertyKey>,876	) -> DispatchResult {877		for key in property_keys {878			Self::delete_collection_property(collection, sender, key)?;879		}880881		Ok(())882	}883884	// For migrations885	pub fn set_property_permission_unchecked(886		collection: CollectionId,887		property_permission: PropertyKeyPermission,888	) -> DispatchResult {889		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {890			permissions.try_set(property_permission.key, property_permission.permission)891		})892		.map_err(<Error<T>>::from)?;893		Ok(())894	}895896	pub fn set_property_permission(897		collection: &CollectionHandle<T>,898		sender: &T::CrossAccountId,899		property_permission: PropertyKeyPermission,900	) -> DispatchResult {901		collection.check_is_owner_or_admin(sender)?;902903		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);904		let current_permission = all_permissions.get(&property_permission.key);905		if matches![906			current_permission,907			Some(PropertyPermission { mutable: false, .. })908		] {909			return Err(<Error<T>>::NoPermission.into());910		}911912		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {913			let property_permission = property_permission.clone();914			permissions.try_set(property_permission.key, property_permission.permission)915		})916		.map_err(<Error<T>>::from)?;917918		Self::deposit_event(Event::PropertyPermissionSet(919			collection.id,920			property_permission.key,921		));922923		Ok(())924	}925926	#[transactional]927	pub fn set_property_permissions(928		collection: &CollectionHandle<T>,929		sender: &T::CrossAccountId,930		property_permissions: Vec<PropertyKeyPermission>,931	) -> DispatchResult {932		for prop_pemission in property_permissions {933			Self::set_property_permission(collection, sender, prop_pemission)?;934		}935936		Ok(())937	}938939	pub fn get_collection_property(940		collection_id: CollectionId,941		key: &PropertyKey,942	) -> Option<PropertyValue> {943		Self::collection_properties(collection_id).get(key).cloned()944	}945946	pub fn bytes_keys_to_property_keys(947		keys: Vec<Vec<u8>>,948	) -> Result<Vec<PropertyKey>, DispatchError> {949		keys.into_iter()950			.map(|key| -> Result<PropertyKey, DispatchError> {951				key.try_into()952					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())953			})954			.collect::<Result<Vec<PropertyKey>, DispatchError>>()955	}956957	pub fn filter_collection_properties(958		collection_id: CollectionId,959		keys: Option<Vec<PropertyKey>>,960	) -> Result<Vec<Property>, DispatchError> {961		let properties = Self::collection_properties(collection_id);962963		let properties = keys964			.map(|keys| {965				keys.into_iter()966					.filter_map(|key| {967						properties.get(&key).map(|value| Property {968							key,969							value: value.clone(),970						})971					})972					.collect()973			})974			.unwrap_or_else(|| {975				properties976					.into_iter()977					.map(|(key, value)| Property {978						key,979						value,980					})981					.collect()982			});983984		Ok(properties)985	}986987	pub fn filter_property_permissions(988		collection_id: CollectionId,989		keys: Option<Vec<PropertyKey>>,990	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {991		let permissions = Self::property_permissions(collection_id);992993		let key_permissions = keys994			.map(|keys| {995				keys.into_iter()996					.filter_map(|key| {997						permissions998							.get(&key)999							.map(|permission| PropertyKeyPermission {1000								key,1001								permission: permission.clone(),1002							})1003					})1004					.collect()1005			})1006			.unwrap_or_else(|| {1007				permissions1008					.into_iter()1009					.map(|(key, permission)| PropertyKeyPermission {1010						key,1011						permission,1012					})1013					.collect()1014			});10151016		Ok(key_permissions)1017	}10181019	pub fn toggle_allowlist(1020		collection: &CollectionHandle<T>,1021		sender: &T::CrossAccountId,1022		user: &T::CrossAccountId,1023		allowed: bool,1024	) -> DispatchResult {1025		collection.check_is_owner_or_admin(sender)?;10261027		// =========10281029		if allowed {1030			<Allowlist<T>>::insert((collection.id, user), true);1031		} else {1032			<Allowlist<T>>::remove((collection.id, user));1033		}10341035		Ok(())1036	}10371038	pub fn toggle_admin(1039		collection: &CollectionHandle<T>,1040		sender: &T::CrossAccountId,1041		user: &T::CrossAccountId,1042		admin: bool,1043	) -> DispatchResult {1044		collection.check_is_owner_or_admin(sender)?;10451046		let was_admin = <IsAdmin<T>>::get((collection.id, user));1047		if was_admin == admin {1048			return Ok(());1049		}1050		let amount = <AdminAmount<T>>::get(collection.id);10511052		if admin {1053			let amount = amount1054				.checked_add(1)1055				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1056			ensure!(1057				amount <= Self::collection_admins_limit(),1058				<Error<T>>::CollectionAdminCountExceeded,1059			);10601061			// =========10621063			<AdminAmount<T>>::insert(collection.id, amount);1064			<IsAdmin<T>>::insert((collection.id, user), true);1065		} else {1066			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1067			<IsAdmin<T>>::remove((collection.id, user));1068		}10691070		Ok(())1071	}10721073	pub fn clamp_limits(1074		mode: CollectionMode,1075		old_limit: &CollectionLimits,1076		mut new_limit: CollectionLimits,1077	) -> Result<CollectionLimits, DispatchError> {1078		limit_default!(old_limit, new_limit,1079			account_token_ownership_limit => ensure!(1080				new_limit <= MAX_TOKEN_OWNERSHIP,1081				<Error<T>>::CollectionLimitBoundsExceeded,1082			),1083			sponsor_transfer_timeout(match mode {1084				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1085				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1086				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1087			}) => ensure!(1088				new_limit <= MAX_SPONSOR_TIMEOUT,1089				<Error<T>>::CollectionLimitBoundsExceeded,1090			),1091			sponsored_data_size => ensure!(1092				new_limit <= CUSTOM_DATA_LIMIT,1093				<Error<T>>::CollectionLimitBoundsExceeded,1094			),1095			token_limit => ensure!(1096				old_limit >= new_limit && new_limit > 0,1097				<Error<T>>::CollectionTokenLimitExceeded1098			),1099			owner_can_transfer => ensure!(1100				old_limit || !new_limit,1101				<Error<T>>::OwnerPermissionsCantBeReverted,1102			),1103			owner_can_destroy => ensure!(1104				old_limit || !new_limit,1105				<Error<T>>::OwnerPermissionsCantBeReverted,1106			),1107			sponsored_data_rate_limit => {},1108			transfers_enabled => {},1109		);1110		Ok(new_limit)1111	}1112	pub fn clamp_permissions(1113		mode: CollectionMode,1114		old_limit: &CollectionPermissions,1115		mut new_limit: CollectionPermissions,1116	) -> Result<CollectionPermissions, DispatchError> {1117		limit_default_clone!(old_limit, new_limit,1118		);1119		Ok(new_limit)1120	}1121}11221123#[macro_export]1124macro_rules! unsupported {1125	() => {1126		Err(<Error<T>>::UnsupportedOperation.into())1127	};1128}11291130/// Worst cases1131pub trait CommonWeightInfo<CrossAccountId> {1132	fn create_item() -> Weight;1133	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1134	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1135	fn burn_item() -> Weight;1136	fn set_collection_properties(amount: u32) -> Weight;1137	fn delete_collection_properties(amount: u32) -> Weight;1138	fn set_token_properties(amount: u32) -> Weight;1139	fn delete_token_properties(amount: u32) -> Weight;1140	fn set_property_permissions(amount: u32) -> Weight;1141	fn transfer() -> Weight;1142	fn approve() -> Weight;1143	fn transfer_from() -> Weight;1144	fn burn_from() -> Weight;1145}11461147pub trait CommonCollectionOperations<T: Config> {1148	fn create_item(1149		&self,1150		sender: T::CrossAccountId,1151		to: T::CrossAccountId,1152		data: CreateItemData,1153		nesting_budget: &dyn Budget,1154	) -> DispatchResultWithPostInfo;1155	fn create_multiple_items(1156		&self,1157		sender: T::CrossAccountId,1158		to: T::CrossAccountId,1159		data: Vec<CreateItemData>,1160		nesting_budget: &dyn Budget,1161	) -> DispatchResultWithPostInfo;1162	fn create_multiple_items_ex(1163		&self,1164		sender: T::CrossAccountId,1165		data: CreateItemExData<T::CrossAccountId>,1166		nesting_budget: &dyn Budget,1167	) -> DispatchResultWithPostInfo;1168	fn burn_item(1169		&self,1170		sender: T::CrossAccountId,1171		token: TokenId,1172		amount: u128,1173	) -> DispatchResultWithPostInfo;1174	fn set_collection_properties(1175		&self,1176		sender: T::CrossAccountId,1177		properties: Vec<Property>,1178	) -> DispatchResultWithPostInfo;1179	fn delete_collection_properties(1180		&self,1181		sender: &T::CrossAccountId,1182		property_keys: Vec<PropertyKey>,1183	) -> DispatchResultWithPostInfo;1184	fn set_token_properties(1185		&self,1186		sender: T::CrossAccountId,1187		token_id: TokenId,1188		property: Vec<Property>,1189	) -> DispatchResultWithPostInfo;1190	fn delete_token_properties(1191		&self,1192		sender: T::CrossAccountId,1193		token_id: TokenId,1194		property_keys: Vec<PropertyKey>,1195	) -> DispatchResultWithPostInfo;1196	fn set_property_permissions(1197		&self,1198		sender: &T::CrossAccountId,1199		property_permissions: Vec<PropertyKeyPermission>,1200	) -> DispatchResultWithPostInfo;1201	fn transfer(1202		&self,1203		sender: T::CrossAccountId,1204		to: T::CrossAccountId,1205		token: TokenId,1206		amount: u128,1207		nesting_budget: &dyn Budget,1208	) -> DispatchResultWithPostInfo;1209	fn approve(1210		&self,1211		sender: T::CrossAccountId,1212		spender: T::CrossAccountId,1213		token: TokenId,1214		amount: u128,1215	) -> DispatchResultWithPostInfo;1216	fn transfer_from(1217		&self,1218		sender: T::CrossAccountId,1219		from: T::CrossAccountId,1220		to: T::CrossAccountId,1221		token: TokenId,1222		amount: u128,1223		nesting_budget: &dyn Budget,1224	) -> DispatchResultWithPostInfo;1225	fn burn_from(1226		&self,1227		sender: T::CrossAccountId,1228		from: T::CrossAccountId,1229		token: TokenId,1230		amount: u128,1231		nesting_budget: &dyn Budget,1232	) -> DispatchResultWithPostInfo;12331234	fn check_nesting(1235		&self,1236		sender: T::CrossAccountId,1237		from: (CollectionId, TokenId),1238		under: TokenId,1239		budget: &dyn Budget,1240	) -> DispatchResult;12411242	fn nest(1243		&self,1244		under: TokenId,1245		to_nest: (CollectionId, TokenId)1246	);12471248	fn unnest(1249		&self,1250		under: TokenId,1251		to_nest: (CollectionId, TokenId)1252	);12531254	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1255	fn collection_tokens(&self) -> Vec<TokenId>;1256	fn token_exists(&self, token: TokenId) -> bool;1257	fn last_token_id(&self) -> TokenId;12581259	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1260	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1261	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1262	/// Amount of unique collection tokens1263	fn total_supply(&self) -> u32;1264	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1265	fn account_balance(&self, account: T::CrossAccountId) -> u32;1266	/// Amount of specific token account have (Applicable to fungible/refungible)1267	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1268	fn allowance(1269		&self,1270		sender: T::CrossAccountId,1271		spender: T::CrossAccountId,1272		token: TokenId,1273	) -> u128;1274}12751276// Flexible enough for implementing CommonCollectionOperations1277pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1278	let post_info = PostDispatchInfo {1279		actual_weight: Some(weight),1280		pays_fee: Pays::Yes,1281	};1282	match res {1283		Ok(()) => Ok(post_info),1284		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1285	}1286}12871288impl<T: Config> From<PropertiesError> for Error<T> {1289	fn from(error: PropertiesError) -> Self {1290		match error {1291			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1292			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1293			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1294			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1295			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1296		}1297	}1298}