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

difftreelog

source

pallets/common/src/lib.rs35.0 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, rc::Rc};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	}117118	pub fn new_with_recorder(id: CollectionId, recorder: Rc<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	}156}157impl<T: Config> Deref for CollectionHandle<T> {158	type Target = Collection<T::AccountId>;159160	fn deref(&self) -> &Self::Target {161		&self.collection162	}163}164165impl<T: Config> DerefMut for CollectionHandle<T> {166	fn deref_mut(&mut self) -> &mut Self::Target {167		&mut self.collection168	}169}170171impl<T: Config> CollectionHandle<T> {172	pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {173		ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);174		Ok(())175	}176	pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {177		*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))178	}179	pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {180		ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);181		Ok(())182	}183	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {184		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)185	}186	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {187		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)188	}189	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {190		ensure!(191			<Allowlist<T>>::get((self.id, user)),192			<Error<T>>::AddressNotInAllowlist193		);194		Ok(())195	}196}197198#[frame_support::pallet]199pub mod pallet {200	use super::*;201	use pallet_evm::account;202	use dispatch::CollectionDispatch;203	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};204	use frame_system::pallet_prelude::*;205	use frame_support::traits::Currency;206	use up_data_structs::{TokenId, mapping::TokenAddressMapping};207	use scale_info::TypeInfo;208	use weights::WeightInfo;209210	#[pallet::config]211	pub trait Config:212		frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config213	{214		type WeightInfo: WeightInfo;215		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;216217		type Currency: Currency<Self::AccountId>;218219		#[pallet::constant]220		type CollectionCreationPrice: Get<221			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,222		>;223		type CollectionDispatch: CollectionDispatch<Self>;224225		type TreasuryAccountId: Get<Self::AccountId>;226227		type EvmTokenAddressMapping: TokenAddressMapping<H160>;228		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;229	}230231	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);232233	#[pallet::pallet]234	#[pallet::storage_version(STORAGE_VERSION)]235	#[pallet::generate_store(pub(super) trait Store)]236	pub struct Pallet<T>(_);237238	#[pallet::extra_constants]239	impl<T: Config> Pallet<T> {240		pub fn collection_admins_limit() -> u32 {241			COLLECTION_ADMINS_LIMIT242		}243	}244245	#[pallet::event]246	#[pallet::generate_deposit(pub fn deposit_event)]247	pub enum Event<T: Config> {248		/// New collection was created249		///250		/// # Arguments251		///252		/// * collection_id: Globally unique identifier of newly created collection.253		///254		/// * mode: [CollectionMode] converted into u8.255		///256		/// * account_id: Collection owner.257		CollectionCreated(CollectionId, u8, T::AccountId),258259		/// New collection was destroyed260		///261		/// # Arguments262		///263		/// * collection_id: Globally unique identifier of collection.264		CollectionDestroyed(CollectionId),265266		/// New item was created.267		///268		/// # Arguments269		///270		/// * collection_id: Id of the collection where item was created.271		///272		/// * item_id: Id of an item. Unique within the collection.273		///274		/// * recipient: Owner of newly created item275		///276		/// * amount: Always 1 for NFT277		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),278279		/// Collection item was burned.280		///281		/// # Arguments282		///283		/// * collection_id.284		///285		/// * item_id: Identifier of burned NFT.286		///287		/// * owner: which user has destroyed its tokens288		///289		/// * amount: Always 1 for NFT290		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),291292		/// Item was transferred293		///294		/// * collection_id: Id of collection to which item is belong295		///296		/// * item_id: Id of an item297		///298		/// * sender: Original owner of item299		///300		/// * recipient: New owner of item301		///302		/// * amount: Always 1 for NFT303		Transfer(304			CollectionId,305			TokenId,306			T::CrossAccountId,307			T::CrossAccountId,308			u128,309		),310311		/// * collection_id312		///313		/// * item_id314		///315		/// * sender316		///317		/// * spender318		///319		/// * amount320		Approved(321			CollectionId,322			TokenId,323			T::CrossAccountId,324			T::CrossAccountId,325			u128,326		),327328		CollectionPropertySet(CollectionId, PropertyKey),329330		CollectionPropertyDeleted(CollectionId, PropertyKey),331332		TokenPropertySet(CollectionId, TokenId, PropertyKey),333334		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),335336		PropertyPermissionSet(CollectionId, PropertyKey),337	}338339	#[pallet::error]340	pub enum Error<T> {341		/// This collection does not exist.342		CollectionNotFound,343		/// Sender parameter and item owner must be equal.344		MustBeTokenOwner,345		/// No permission to perform action346		NoPermission,347		/// Collection is not in mint mode.348		PublicMintingNotAllowed,349		/// Address is not in allow list.350		AddressNotInAllowlist,351352		/// Collection name can not be longer than 63 char.353		CollectionNameLimitExceeded,354		/// Collection description can not be longer than 255 char.355		CollectionDescriptionLimitExceeded,356		/// Token prefix can not be longer than 15 char.357		CollectionTokenPrefixLimitExceeded,358		/// Total collections bound exceeded.359		TotalCollectionsLimitExceeded,360		/// Exceeded max admin count361		CollectionAdminCountExceeded,362		/// Collection limit bounds per collection exceeded363		CollectionLimitBoundsExceeded,364		/// Tried to enable permissions which are only permitted to be disabled365		OwnerPermissionsCantBeReverted,366		/// Collection settings not allowing items transferring367		TransferNotAllowed,368		/// Account token limit exceeded per collection369		AccountTokenLimitExceeded,370		/// Collection token limit exceeded371		CollectionTokenLimitExceeded,372		/// Metadata flag frozen373		MetadataFlagFrozen,374375		/// Item not exists.376		TokenNotFound,377		/// Item balance not enough.378		TokenValueTooLow,379		/// Requested value more than approved.380		ApprovedValueTooLow,381		/// Tried to approve more than owned382		CantApproveMoreThanOwned,383384		/// Can't transfer tokens to ethereum zero address385		AddressIsZero,386		/// Target collection doesn't supports this operation387		UnsupportedOperation,388389		/// Not sufficient founds to perform action390		NotSufficientFounds,391392		/// Collection has nesting disabled393		NestingIsDisabled,394		/// Only owner may nest tokens under this collection395		OnlyOwnerAllowedToNest,396		/// Only tokens from specific collections may nest tokens under this397		SourceCollectionIsNotAllowedToNest,398399		/// Tried to store more data than allowed in collection field400		CollectionFieldSizeExceeded,401402		/// Tried to store more property data than allowed403		NoSpaceForProperty,404405		/// Tried to store more property keys than allowed406		PropertyLimitReached,407408		/// Property key is too long409		PropertyKeyIsTooLong,410411		/// Only ASCII letters, digits, and '_', '-' are allowed412		InvalidCharacterInPropertyKey,413414		/// Empty property keys are forbidden415		EmptyPropertyKey,416	}417418	#[pallet::storage]419	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;420	#[pallet::storage]421	pub type DestroyedCollectionCount<T> =422		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;423424	/// Collection info425	#[pallet::storage]426	pub type CollectionById<T> = StorageMap<427		Hasher = Blake2_128Concat,428		Key = CollectionId,429		Value = Collection<<T as frame_system::Config>::AccountId>,430		QueryKind = OptionQuery,431	>;432433	/// Collection properties434	#[pallet::storage]435	#[pallet::getter(fn collection_properties)]436	pub type CollectionProperties<T> = StorageMap<437		Hasher = Blake2_128Concat,438		Key = CollectionId,439		Value = Properties,440		QueryKind = ValueQuery,441		OnEmpty = up_data_structs::CollectionProperties,442	>;443444	#[pallet::storage]445	#[pallet::getter(fn property_permissions)]446	pub type CollectionPropertyPermissions<T> = StorageMap<447		Hasher = Blake2_128Concat,448		Key = CollectionId,449		Value = PropertiesPermissionMap,450		QueryKind = ValueQuery,451	>;452453	#[pallet::storage]454	pub type AdminAmount<T> = StorageMap<455		Hasher = Blake2_128Concat,456		Key = CollectionId,457		Value = u32,458		QueryKind = ValueQuery,459	>;460461	/// List of collection admins462	#[pallet::storage]463	pub type IsAdmin<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	/// Allowlisted collection users473	#[pallet::storage]474	pub type Allowlist<T: Config> = StorageNMap<475		Key = (476			Key<Blake2_128Concat, CollectionId>,477			Key<Blake2_128Concat, T::CrossAccountId>,478		),479		Value = bool,480		QueryKind = ValueQuery,481	>;482483	/// Not used by code, exists only to provide some types to metadata484	#[pallet::storage]485	pub type DummyStorageValue<T: Config> = StorageValue<486		Value = (487			CollectionStats,488			CollectionId,489			TokenId,490			PhantomType<TokenData<T::CrossAccountId>>,491			PhantomType<RpcCollection<T::AccountId>>,492			// RMRK493			PhantomType<RmrkCollectionInfo<T::AccountId>>,494			PhantomType<RmrkInstanceInfo<T::AccountId>>,495			PhantomType<RmrkResourceInfo>,496			PhantomType<RmrkPropertyInfo>,497			PhantomType<RmrkBaseInfo<T::AccountId>>,498			PhantomType<RmrkPartType>,499			PhantomType<RmrkTheme>,500			PhantomType<RmrkNftChild>,501		),502		QueryKind = OptionQuery,503	>;504505	#[pallet::hooks]506	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {507		fn on_runtime_upgrade() -> Weight {508			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {509				use up_data_structs::{CollectionVersion1, CollectionVersion2};510				<CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {511					let mut props = Vec::new();512					if !v.offchain_schema.is_empty() {513						props.push(Property {514							key: b"_old_offchainSchema".to_vec().try_into().unwrap(),515							value: v.offchain_schema.clone().into_inner().try_into().expect("offchain schema too big"),516						});517					}518					if !v.variable_on_chain_schema.is_empty() {519						props.push(Property {520							key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),521							value: v.variable_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),522						});523					}524					if !v.const_on_chain_schema.is_empty() {525						props.push(Property {526							key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),527							value: v.const_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),528						});529					}530					props.push(Property {531						key: b"_old_schemaVersion".to_vec().try_into().unwrap(),532						value: match v.schema_version {533							SchemaVersion::ImageURL => b"ImageUrl".as_slice(),534							SchemaVersion::Unique => b"Unique".as_slice(),535						}.to_vec().try_into().unwrap(),536					});537					Self::set_scoped_collection_properties(538						id,539						PropertyScope::None,540						props.into_iter(),541					).expect("existing data larger than properties");542					let mut new = CollectionVersion2::from(v.clone());543					new.permissions.access = Some(v.access);544					new.permissions.mint_mode = Some(v.mint_mode);545					Some(new)546				});547			}548549			0550		}551	}552}553554impl<T: Config> Pallet<T> {555	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens556	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {557		ensure!(558			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,559			<Error<T>>::AddressIsZero560		);561		Ok(())562	}563	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {564		<IsAdmin<T>>::iter_prefix((collection,))565			.map(|(a, _)| a)566			.collect()567	}568	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {569		<Allowlist<T>>::iter_prefix((collection,))570			.map(|(a, _)| a)571			.collect()572	}573	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {574		<Allowlist<T>>::get((collection, user))575	}576	pub fn collection_stats() -> CollectionStats {577		let created = <CreatedCollectionCount<T>>::get();578		let destroyed = <DestroyedCollectionCount<T>>::get();579		CollectionStats {580			created: created.0,581			destroyed: destroyed.0,582			alive: created.0 - destroyed.0,583		}584	}585586	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {587		let collection = <CollectionById<T>>::get(collection);588		if collection.is_none() {589			return None;590		}591592		let collection = collection.unwrap();593		let limits = collection.limits;594		let effective_limits = CollectionLimits {595			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),596			sponsored_data_size: Some(limits.sponsored_data_size()),597			sponsored_data_rate_limit: Some(598				limits599					.sponsored_data_rate_limit600					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),601			),602			token_limit: Some(limits.token_limit()),603			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(604				match collection.mode {605					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,606					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,607					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,608				},609			)),610			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),611			owner_can_transfer: Some(limits.owner_can_transfer()),612			owner_can_destroy: Some(limits.owner_can_destroy()),613			transfers_enabled: Some(limits.transfers_enabled()),614		};615616		Some(effective_limits)617	}618619	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {620		let Collection {621			name,622			description,623			owner,624			mode,625			token_prefix,626			sponsorship,627			limits,628			permissions,629		} = <CollectionById<T>>::get(collection)?;630631		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)632			.into_iter()633			.map(|(key, permission)| PropertyKeyPermission {634				key,635				permission,636			})637			.collect();638639		let properties = <CollectionProperties<T>>::get(collection)640			.into_iter()641			.map(|(key, value)| Property {642				key,643				value,644			})645			.collect();646647		Some(RpcCollection {648			name: name.into_inner(),649			description: description.into_inner(),650			owner,651			mode,652			token_prefix: token_prefix.into_inner(),653			sponsorship,654			limits,655			permissions,656			token_property_permissions,657			properties,658		})659	}660}661662macro_rules! limit_default {663	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{664		$(665			if let Some($new) = $new.$field {666				let $old = $old.$field($($arg)?);667				let _ = $new;668				let _ = $old;669				$check670			} else {671				$new.$field = $old.$field672			}673		)*674	}};675}676macro_rules! limit_default_clone {677	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{678		$(679			if let Some($new) = $new.$field.clone() {680				let $old = $old.$field($($arg)?);681				let _ = $new;682				let _ = $old;683				$check684			} else {685				$new.$field = $old.$field.clone()686			}687		)*688	}};689}690691impl<T: Config> Pallet<T> {692	pub fn init_collection(693		owner: T::AccountId,694		data: CreateCollectionData<T::AccountId>,695	) -> Result<CollectionId, DispatchError> {696		{697			ensure!(698				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,699				Error::<T>::CollectionTokenPrefixLimitExceeded700			);701		}702703		let created_count = <CreatedCollectionCount<T>>::get()704			.0705			.checked_add(1)706			.ok_or(ArithmeticError::Overflow)?;707		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;708		let id = CollectionId(created_count);709710		// bound Total number of collections711		ensure!(712			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,713			<Error<T>>::TotalCollectionsLimitExceeded714		);715716		// =========717718		let collection = Collection {719			owner: owner.clone(),720			name: data.name,721			mode: data.mode.clone(),722			description: data.description,723			token_prefix: data.token_prefix,724			sponsorship: data725				.pending_sponsor726				.map(SponsorshipState::Unconfirmed)727				.unwrap_or_default(),728			limits: data729				.limits730				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))731				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,732			permissions: data733				.permissions734				.map(|permissions| Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions))735				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,736		};737738		let mut collection_properties = up_data_structs::CollectionProperties::get();739		collection_properties740			.try_set_from_iter(data.properties.into_iter())741			.map_err(<Error<T>>::from)?;742743		CollectionProperties::<T>::insert(id, collection_properties);744745		let mut token_props_permissions = PropertiesPermissionMap::new();746		token_props_permissions747			.try_set_from_iter(data.token_property_permissions.into_iter())748			.map_err(<Error<T>>::from)?;749750		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);751752		// Take a (non-refundable) deposit of collection creation753		{754			let mut imbalance =755				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();756			imbalance.subsume(757				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(758					&T::TreasuryAccountId::get(),759					T::CollectionCreationPrice::get(),760				),761			);762			<T as Config>::Currency::settle(763				&owner,764				imbalance,765				WithdrawReasons::TRANSFER,766				ExistenceRequirement::KeepAlive,767			)768			.map_err(|_| Error::<T>::NotSufficientFounds)?;769		}770771		<CreatedCollectionCount<T>>::put(created_count);772		<Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));773		<CollectionById<T>>::insert(id, collection);774		Ok(id)775	}776777	pub fn destroy_collection(778		collection: CollectionHandle<T>,779		sender: &T::CrossAccountId,780	) -> DispatchResult {781		ensure!(782			collection.limits.owner_can_destroy(),783			<Error<T>>::NoPermission,784		);785		collection.check_is_owner(sender)?;786787		let destroyed_collections = <DestroyedCollectionCount<T>>::get()788			.0789			.checked_add(1)790			.ok_or(ArithmeticError::Overflow)?;791792		// =========793794		<DestroyedCollectionCount<T>>::put(destroyed_collections);795		<CollectionById<T>>::remove(collection.id);796		<AdminAmount<T>>::remove(collection.id);797		<IsAdmin<T>>::remove_prefix((collection.id,), None);798		<Allowlist<T>>::remove_prefix((collection.id,), None);799		<CollectionProperties<T>>::remove(collection.id);800801		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));802		Ok(())803	}804805	pub fn set_collection_property(806		collection: &CollectionHandle<T>,807		sender: &T::CrossAccountId,808		property: Property,809	) -> DispatchResult {810		collection.check_is_owner_or_admin(sender)?;811812		CollectionProperties::<T>::try_mutate(collection.id, |properties| {813			let property = property.clone();814			properties.try_set(property.key, property.value)815		})816		.map_err(<Error<T>>::from)?;817818		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));819820		Ok(())821	}822823	pub fn set_scoped_collection_property(824		collection_id: CollectionId,825		scope: PropertyScope,826		property: Property,827	) -> DispatchResult {828		CollectionProperties::<T>::try_mutate(collection_id, |properties| {829			properties.try_scoped_set(scope, property.key, property.value)830		})831		.map_err(<Error<T>>::from)?;832833		Ok(())834	}835836	pub fn set_scoped_collection_properties(837		collection_id: CollectionId,838		scope: PropertyScope,839		properties: impl Iterator<Item = Property>,840	) -> DispatchResult {841		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {842			stored_properties.try_scoped_set_from_iter(scope, properties)843		})844		.map_err(<Error<T>>::from)?;845846		Ok(())847	}848849	#[transactional]850	pub fn set_collection_properties(851		collection: &CollectionHandle<T>,852		sender: &T::CrossAccountId,853		properties: Vec<Property>,854	) -> DispatchResult {855		for property in properties {856			Self::set_collection_property(collection, sender, property)?;857		}858859		Ok(())860	}861862	pub fn delete_collection_property(863		collection: &CollectionHandle<T>,864		sender: &T::CrossAccountId,865		property_key: PropertyKey,866	) -> DispatchResult {867		collection.check_is_owner_or_admin(sender)?;868869		CollectionProperties::<T>::try_mutate(collection.id, |properties| {870			properties.remove(&property_key)871		})872		.map_err(<Error<T>>::from)?;873874		Self::deposit_event(Event::CollectionPropertyDeleted(875			collection.id,876			property_key,877		));878879		Ok(())880	}881882	#[transactional]883	pub fn delete_collection_properties(884		collection: &CollectionHandle<T>,885		sender: &T::CrossAccountId,886		property_keys: Vec<PropertyKey>,887	) -> DispatchResult {888		for key in property_keys {889			Self::delete_collection_property(collection, sender, key)?;890		}891892		Ok(())893	}894895	// For migrations896	pub fn set_property_permission_unchecked(897		collection: CollectionId,898		property_permission: PropertyKeyPermission,899	) -> DispatchResult {900		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {901			permissions.try_set(property_permission.key, property_permission.permission)902		})903		.map_err(<Error<T>>::from)?;904		Ok(())905	}906907	pub fn set_property_permission(908		collection: &CollectionHandle<T>,909		sender: &T::CrossAccountId,910		property_permission: PropertyKeyPermission,911	) -> DispatchResult {912		collection.check_is_owner_or_admin(sender)?;913914		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);915		let current_permission = all_permissions.get(&property_permission.key);916		if matches![917			current_permission,918			Some(PropertyPermission { mutable: false, .. })919		] {920			return Err(<Error<T>>::NoPermission.into());921		}922923		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {924			let property_permission = property_permission.clone();925			permissions.try_set(property_permission.key, property_permission.permission)926		})927		.map_err(<Error<T>>::from)?;928929		Self::deposit_event(Event::PropertyPermissionSet(930			collection.id,931			property_permission.key,932		));933934		Ok(())935	}936937	#[transactional]938	pub fn set_property_permissions(939		collection: &CollectionHandle<T>,940		sender: &T::CrossAccountId,941		property_permissions: Vec<PropertyKeyPermission>,942	) -> DispatchResult {943		for prop_pemission in property_permissions {944			Self::set_property_permission(collection, sender, prop_pemission)?;945		}946947		Ok(())948	}949950	pub fn get_collection_property(951		collection_id: CollectionId,952		key: &PropertyKey,953	) -> Option<PropertyValue> {954		Self::collection_properties(collection_id).get(key).cloned()955	}956957	pub fn bytes_keys_to_property_keys(958		keys: Vec<Vec<u8>>,959	) -> Result<Vec<PropertyKey>, DispatchError> {960		keys.into_iter()961			.map(|key| -> Result<PropertyKey, DispatchError> {962				key.try_into()963					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())964			})965			.collect::<Result<Vec<PropertyKey>, DispatchError>>()966	}967968	pub fn filter_collection_properties(969		collection_id: CollectionId,970		keys: Option<Vec<PropertyKey>>,971	) -> Result<Vec<Property>, DispatchError> {972		let properties = Self::collection_properties(collection_id);973974		let properties = keys975			.map(|keys| {976				keys.into_iter()977					.filter_map(|key| {978						properties.get(&key).map(|value| Property {979							key,980							value: value.clone(),981						})982					})983					.collect()984			})985			.unwrap_or_else(|| {986				properties987					.into_iter()988					.map(|(key, value)| Property {989						key,990						value,991					})992					.collect()993			});994995		Ok(properties)996	}997998	pub fn filter_property_permissions(999		collection_id: CollectionId,1000		keys: Option<Vec<PropertyKey>>,1001	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1002		let permissions = Self::property_permissions(collection_id);10031004		let key_permissions = keys1005			.map(|keys| {1006				keys.into_iter()1007					.filter_map(|key| {1008						permissions1009							.get(&key)1010							.map(|permission| PropertyKeyPermission {1011								key,1012								permission: permission.clone(),1013							})1014					})1015					.collect()1016			})1017			.unwrap_or_else(|| {1018				permissions1019					.into_iter()1020					.map(|(key, permission)| PropertyKeyPermission {1021						key,1022						permission,1023					})1024					.collect()1025			});10261027		Ok(key_permissions)1028	}10291030	pub fn toggle_allowlist(1031		collection: &CollectionHandle<T>,1032		sender: &T::CrossAccountId,1033		user: &T::CrossAccountId,1034		allowed: bool,1035	) -> DispatchResult {1036		collection.check_is_owner_or_admin(sender)?;10371038		// =========10391040		if allowed {1041			<Allowlist<T>>::insert((collection.id, user), true);1042		} else {1043			<Allowlist<T>>::remove((collection.id, user));1044		}10451046		Ok(())1047	}10481049	pub fn toggle_admin(1050		collection: &CollectionHandle<T>,1051		sender: &T::CrossAccountId,1052		user: &T::CrossAccountId,1053		admin: bool,1054	) -> DispatchResult {1055		collection.check_is_owner_or_admin(sender)?;10561057		let was_admin = <IsAdmin<T>>::get((collection.id, user));1058		if was_admin == admin {1059			return Ok(());1060		}1061		let amount = <AdminAmount<T>>::get(collection.id);10621063		if admin {1064			let amount = amount1065				.checked_add(1)1066				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1067			ensure!(1068				amount <= Self::collection_admins_limit(),1069				<Error<T>>::CollectionAdminCountExceeded,1070			);10711072			// =========10731074			<AdminAmount<T>>::insert(collection.id, amount);1075			<IsAdmin<T>>::insert((collection.id, user), true);1076		} else {1077			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1078			<IsAdmin<T>>::remove((collection.id, user));1079		}10801081		Ok(())1082	}10831084	pub fn clamp_limits(1085		mode: CollectionMode,1086		old_limit: &CollectionLimits,1087		mut new_limit: CollectionLimits,1088	) -> Result<CollectionLimits, DispatchError> {1089		limit_default!(old_limit, new_limit,1090			account_token_ownership_limit => ensure!(1091				new_limit <= MAX_TOKEN_OWNERSHIP,1092				<Error<T>>::CollectionLimitBoundsExceeded,1093			),1094			sponsor_transfer_timeout(match mode {1095				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1096				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1097				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1098			}) => ensure!(1099				new_limit <= MAX_SPONSOR_TIMEOUT,1100				<Error<T>>::CollectionLimitBoundsExceeded,1101			),1102			sponsored_data_size => ensure!(1103				new_limit <= CUSTOM_DATA_LIMIT,1104				<Error<T>>::CollectionLimitBoundsExceeded,1105			),1106			token_limit => ensure!(1107				old_limit >= new_limit && new_limit > 0,1108				<Error<T>>::CollectionTokenLimitExceeded1109			),1110			owner_can_transfer => ensure!(1111				old_limit || !new_limit,1112				<Error<T>>::OwnerPermissionsCantBeReverted,1113			),1114			owner_can_destroy => ensure!(1115				old_limit || !new_limit,1116				<Error<T>>::OwnerPermissionsCantBeReverted,1117			),1118			sponsored_data_rate_limit => {},1119			transfers_enabled => {},1120		);1121		Ok(new_limit)1122	}1123	pub fn clamp_permissions(1124		mode: CollectionMode,1125		old_limit: &CollectionPermissions,1126		mut new_limit: CollectionPermissions,1127	) -> Result<CollectionPermissions, DispatchError> {1128		limit_default_clone!(old_limit, new_limit,1129		);1130		Ok(new_limit)1131	}1132}11331134#[macro_export]1135macro_rules! unsupported {1136	() => {1137		Err(<Error<T>>::UnsupportedOperation.into())1138	};1139}11401141/// Worst cases1142pub trait CommonWeightInfo<CrossAccountId> {1143	fn create_item() -> Weight;1144	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1145	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1146	fn burn_item() -> Weight;1147	fn set_collection_properties(amount: u32) -> Weight;1148	fn delete_collection_properties(amount: u32) -> Weight;1149	fn set_token_properties(amount: u32) -> Weight;1150	fn delete_token_properties(amount: u32) -> Weight;1151	fn set_property_permissions(amount: u32) -> Weight;1152	fn transfer() -> Weight;1153	fn approve() -> Weight;1154	fn transfer_from() -> Weight;1155	fn burn_from() -> Weight;1156}11571158pub trait CommonCollectionOperations<T: Config> {1159	fn create_item(1160		&self,1161		sender: T::CrossAccountId,1162		to: T::CrossAccountId,1163		data: CreateItemData,1164		nesting_budget: &dyn Budget,1165	) -> DispatchResultWithPostInfo;1166	fn create_multiple_items(1167		&self,1168		sender: T::CrossAccountId,1169		to: T::CrossAccountId,1170		data: Vec<CreateItemData>,1171		nesting_budget: &dyn Budget,1172	) -> DispatchResultWithPostInfo;1173	fn create_multiple_items_ex(1174		&self,1175		sender: T::CrossAccountId,1176		data: CreateItemExData<T::CrossAccountId>,1177		nesting_budget: &dyn Budget,1178	) -> DispatchResultWithPostInfo;1179	fn burn_item(1180		&self,1181		sender: T::CrossAccountId,1182		token: TokenId,1183		amount: u128,1184	) -> DispatchResultWithPostInfo;1185	fn set_collection_properties(1186		&self,1187		sender: T::CrossAccountId,1188		properties: Vec<Property>,1189	) -> DispatchResultWithPostInfo;1190	fn delete_collection_properties(1191		&self,1192		sender: &T::CrossAccountId,1193		property_keys: Vec<PropertyKey>,1194	) -> DispatchResultWithPostInfo;1195	fn set_token_properties(1196		&self,1197		sender: T::CrossAccountId,1198		token_id: TokenId,1199		property: Vec<Property>,1200	) -> DispatchResultWithPostInfo;1201	fn delete_token_properties(1202		&self,1203		sender: T::CrossAccountId,1204		token_id: TokenId,1205		property_keys: Vec<PropertyKey>,1206	) -> DispatchResultWithPostInfo;1207	fn set_property_permissions(1208		&self,1209		sender: &T::CrossAccountId,1210		property_permissions: Vec<PropertyKeyPermission>,1211	) -> DispatchResultWithPostInfo;1212	fn transfer(1213		&self,1214		sender: T::CrossAccountId,1215		to: T::CrossAccountId,1216		token: TokenId,1217		amount: u128,1218		nesting_budget: &dyn Budget,1219	) -> DispatchResultWithPostInfo;1220	fn approve(1221		&self,1222		sender: T::CrossAccountId,1223		spender: T::CrossAccountId,1224		token: TokenId,1225		amount: u128,1226	) -> DispatchResultWithPostInfo;1227	fn transfer_from(1228		&self,1229		sender: T::CrossAccountId,1230		from: T::CrossAccountId,1231		to: T::CrossAccountId,1232		token: TokenId,1233		amount: u128,1234		nesting_budget: &dyn Budget,1235	) -> DispatchResultWithPostInfo;1236	fn burn_from(1237		&self,1238		sender: T::CrossAccountId,1239		from: T::CrossAccountId,1240		token: TokenId,1241		amount: u128,1242		nesting_budget: &dyn Budget,1243	) -> DispatchResultWithPostInfo;12441245	fn check_nesting(1246		&self,1247		sender: T::CrossAccountId,1248		from: (CollectionId, TokenId),1249		under: TokenId,1250		budget: &dyn Budget,1251	) -> DispatchResult;12521253	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1254	fn collection_tokens(&self) -> Vec<TokenId>;1255	fn token_exists(&self, token: TokenId) -> bool;1256	fn last_token_id(&self) -> TokenId;12571258	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1259	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1260	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1261	/// Amount of unique collection tokens1262	fn total_supply(&self) -> u32;1263	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1264	fn account_balance(&self, account: T::CrossAccountId) -> u32;1265	/// Amount of specific token account have (Applicable to fungible/refungible)1266	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1267	fn allowance(1268		&self,1269		sender: T::CrossAccountId,1270		spender: T::CrossAccountId,1271		token: TokenId,1272	) -> u128;1273}12741275// Flexible enough for implementing CommonCollectionOperations1276pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1277	let post_info = PostDispatchInfo {1278		actual_weight: Some(weight),1279		pays_fee: Pays::Yes,1280	};1281	match res {1282		Ok(()) => Ok(post_info),1283		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1284	}1285}12861287impl<T: Config> From<PropertiesError> for Error<T> {1288	fn from(error: PropertiesError) -> Self {1289		match error {1290			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1291			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1292			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1293			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1294			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1295		}1296	}1297}