git.delta.rocks / unique-network / refs/commits / 631b2ed4504b

difftreelog

source

pallets/common/src/lib.rs35.2 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	}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::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config222	{223		type WeightInfo: WeightInfo;224		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;225226		type Currency: Currency<Self::AccountId>;227228		#[pallet::constant]229		type CollectionCreationPrice: Get<230			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,231		>;232		type CollectionDispatch: CollectionDispatch<Self>;233234		type TreasuryAccountId: Get<Self::AccountId>;235236		type EvmTokenAddressMapping: TokenAddressMapping<H160>;237		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;238	}239240	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);241242	#[pallet::pallet]243	#[pallet::storage_version(STORAGE_VERSION)]244	#[pallet::generate_store(pub(super) trait Store)]245	pub struct Pallet<T>(_);246247	#[pallet::extra_constants]248	impl<T: Config> Pallet<T> {249		pub fn collection_admins_limit() -> u32 {250			COLLECTION_ADMINS_LIMIT251		}252	}253254	#[pallet::event]255	#[pallet::generate_deposit(pub fn deposit_event)]256	pub enum Event<T: Config> {257		/// New collection was created258		///259		/// # Arguments260		///261		/// * collection_id: Globally unique identifier of newly created collection.262		///263		/// * mode: [CollectionMode] converted into u8.264		///265		/// * account_id: Collection owner.266		CollectionCreated(CollectionId, u8, T::AccountId),267268		/// New collection was destroyed269		///270		/// # Arguments271		///272		/// * collection_id: Globally unique identifier of collection.273		CollectionDestroyed(CollectionId),274275		/// New item was created.276		///277		/// # Arguments278		///279		/// * collection_id: Id of the collection where item was created.280		///281		/// * item_id: Id of an item. Unique within the collection.282		///283		/// * recipient: Owner of newly created item284		///285		/// * amount: Always 1 for NFT286		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),287288		/// Collection item was burned.289		///290		/// # Arguments291		///292		/// * collection_id.293		///294		/// * item_id: Identifier of burned NFT.295		///296		/// * owner: which user has destroyed its tokens297		///298		/// * amount: Always 1 for NFT299		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),300301		/// Item was transferred302		///303		/// * collection_id: Id of collection to which item is belong304		///305		/// * item_id: Id of an item306		///307		/// * sender: Original owner of item308		///309		/// * recipient: New owner of item310		///311		/// * amount: Always 1 for NFT312		Transfer(313			CollectionId,314			TokenId,315			T::CrossAccountId,316			T::CrossAccountId,317			u128,318		),319320		/// * collection_id321		///322		/// * item_id323		///324		/// * sender325		///326		/// * spender327		///328		/// * amount329		Approved(330			CollectionId,331			TokenId,332			T::CrossAccountId,333			T::CrossAccountId,334			u128,335		),336337		CollectionPropertySet(CollectionId, PropertyKey),338339		CollectionPropertyDeleted(CollectionId, PropertyKey),340341		TokenPropertySet(CollectionId, TokenId, PropertyKey),342343		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),344345		PropertyPermissionSet(CollectionId, PropertyKey),346	}347348	#[pallet::error]349	pub enum Error<T> {350		/// This collection does not exist.351		CollectionNotFound,352		/// Sender parameter and item owner must be equal.353		MustBeTokenOwner,354		/// No permission to perform action355		NoPermission,356		/// Collection is not in mint mode.357		PublicMintingNotAllowed,358		/// Address is not in allow list.359		AddressNotInAllowlist,360361		/// Collection name can not be longer than 63 char.362		CollectionNameLimitExceeded,363		/// Collection description can not be longer than 255 char.364		CollectionDescriptionLimitExceeded,365		/// Token prefix can not be longer than 15 char.366		CollectionTokenPrefixLimitExceeded,367		/// Total collections bound exceeded.368		TotalCollectionsLimitExceeded,369		/// Exceeded max admin count370		CollectionAdminCountExceeded,371		/// Collection limit bounds per collection exceeded372		CollectionLimitBoundsExceeded,373		/// Tried to enable permissions which are only permitted to be disabled374		OwnerPermissionsCantBeReverted,375		/// Collection settings not allowing items transferring376		TransferNotAllowed,377		/// Account token limit exceeded per collection378		AccountTokenLimitExceeded,379		/// Collection token limit exceeded380		CollectionTokenLimitExceeded,381		/// Metadata flag frozen382		MetadataFlagFrozen,383384		/// Item not exists.385		TokenNotFound,386		/// Item balance not enough.387		TokenValueTooLow,388		/// Requested value more than approved.389		ApprovedValueTooLow,390		/// Tried to approve more than owned391		CantApproveMoreThanOwned,392393		/// Can't transfer tokens to ethereum zero address394		AddressIsZero,395		/// Target collection doesn't supports this operation396		UnsupportedOperation,397398		/// Not sufficient founds to perform action399		NotSufficientFounds,400401		/// Collection has nesting disabled402		NestingIsDisabled,403		/// Only owner may nest tokens under this collection404		OnlyOwnerAllowedToNest,405		/// Only tokens from specific collections may nest tokens under this406		SourceCollectionIsNotAllowedToNest,407408		/// Tried to store more data than allowed in collection field409		CollectionFieldSizeExceeded,410411		/// Tried to store more property data than allowed412		NoSpaceForProperty,413414		/// Tried to store more property keys than allowed415		PropertyLimitReached,416417		/// Property key is too long418		PropertyKeyIsTooLong,419420		/// Only ASCII letters, digits, and '_', '-' are allowed421		InvalidCharacterInPropertyKey,422423		/// Empty property keys are forbidden424		EmptyPropertyKey,425	}426427	#[pallet::storage]428	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;429	#[pallet::storage]430	pub type DestroyedCollectionCount<T> =431		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;432433	/// Collection info434	#[pallet::storage]435	pub type CollectionById<T> = StorageMap<436		Hasher = Blake2_128Concat,437		Key = CollectionId,438		Value = Collection<<T as frame_system::Config>::AccountId>,439		QueryKind = OptionQuery,440	>;441442	/// Collection properties443	#[pallet::storage]444	#[pallet::getter(fn collection_properties)]445	pub type CollectionProperties<T> = StorageMap<446		Hasher = Blake2_128Concat,447		Key = CollectionId,448		Value = Properties,449		QueryKind = ValueQuery,450		OnEmpty = up_data_structs::CollectionProperties,451	>;452453	#[pallet::storage]454	#[pallet::getter(fn property_permissions)]455	pub type CollectionPropertyPermissions<T> = StorageMap<456		Hasher = Blake2_128Concat,457		Key = CollectionId,458		Value = PropertiesPermissionMap,459		QueryKind = ValueQuery,460	>;461462	#[pallet::storage]463	pub type AdminAmount<T> = StorageMap<464		Hasher = Blake2_128Concat,465		Key = CollectionId,466		Value = u32,467		QueryKind = ValueQuery,468	>;469470	/// List of collection admins471	#[pallet::storage]472	pub type IsAdmin<T: Config> = StorageNMap<473		Key = (474			Key<Blake2_128Concat, CollectionId>,475			Key<Blake2_128Concat, T::CrossAccountId>,476		),477		Value = bool,478		QueryKind = ValueQuery,479	>;480481	/// Allowlisted collection users482	#[pallet::storage]483	pub type Allowlist<T: Config> = StorageNMap<484		Key = (485			Key<Blake2_128Concat, CollectionId>,486			Key<Blake2_128Concat, T::CrossAccountId>,487		),488		Value = bool,489		QueryKind = ValueQuery,490	>;491492	/// Not used by code, exists only to provide some types to metadata493	#[pallet::storage]494	pub type DummyStorageValue<T: Config> = StorageValue<495		Value = (496			CollectionStats,497			CollectionId,498			TokenId,499			PhantomType<(500				TokenData<T::CrossAccountId>,501				RpcCollection<T::AccountId>,502503				// RMRK504				RmrkCollectionInfo<T::AccountId>,505				RmrkInstanceInfo<T::AccountId>,506				RmrkResourceInfo,507				RmrkPropertyInfo,508				RmrkBaseInfo<T::AccountId>,509				RmrkPartType,510				RmrkTheme,511				RmrkNftChild,512			)>,513		),514		QueryKind = OptionQuery,515	>;516517	#[pallet::hooks]518	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {519		fn on_runtime_upgrade() -> Weight {520			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {521				use up_data_structs::{CollectionVersion1, CollectionVersion2};522				<CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {523					let mut props = Vec::new();524					if !v.offchain_schema.is_empty() {525						props.push(Property {526							key: b"_old_offchainSchema".to_vec().try_into().unwrap(),527							value: v.offchain_schema.clone().into_inner().try_into().expect("offchain schema too big"),528						});529					}530					if !v.variable_on_chain_schema.is_empty() {531						props.push(Property {532							key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),533							value: v.variable_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),534						});535					}536					if !v.const_on_chain_schema.is_empty() {537						props.push(Property {538							key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),539							value: v.const_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),540						});541					}542					props.push(Property {543						key: b"_old_schemaVersion".to_vec().try_into().unwrap(),544						value: match v.schema_version {545							SchemaVersion::ImageURL => b"ImageUrl".as_slice(),546							SchemaVersion::Unique => b"Unique".as_slice(),547						}.to_vec().try_into().unwrap(),548					});549					Self::set_scoped_collection_properties(550						id,551						PropertyScope::None,552						props.into_iter(),553					).expect("existing data larger than properties");554					let mut new = CollectionVersion2::from(v.clone());555					new.permissions.access = Some(v.access);556					new.permissions.mint_mode = Some(v.mint_mode);557					Some(new)558				});559			}560561			0562		}563	}564}565566impl<T: Config> Pallet<T> {567	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens568	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {569		ensure!(570			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,571			<Error<T>>::AddressIsZero572		);573		Ok(())574	}575	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {576		<IsAdmin<T>>::iter_prefix((collection,))577			.map(|(a, _)| a)578			.collect()579	}580	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {581		<Allowlist<T>>::iter_prefix((collection,))582			.map(|(a, _)| a)583			.collect()584	}585	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {586		<Allowlist<T>>::get((collection, user))587	}588	pub fn collection_stats() -> CollectionStats {589		let created = <CreatedCollectionCount<T>>::get();590		let destroyed = <DestroyedCollectionCount<T>>::get();591		CollectionStats {592			created: created.0,593			destroyed: destroyed.0,594			alive: created.0 - destroyed.0,595		}596	}597598	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {599		let collection = <CollectionById<T>>::get(collection);600		if collection.is_none() {601			return None;602		}603604		let collection = collection.unwrap();605		let limits = collection.limits;606		let effective_limits = CollectionLimits {607			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),608			sponsored_data_size: Some(limits.sponsored_data_size()),609			sponsored_data_rate_limit: Some(610				limits611					.sponsored_data_rate_limit612					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),613			),614			token_limit: Some(limits.token_limit()),615			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(616				match collection.mode {617					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,618					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,619					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,620				},621			)),622			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),623			owner_can_transfer: Some(limits.owner_can_transfer()),624			owner_can_destroy: Some(limits.owner_can_destroy()),625			transfers_enabled: Some(limits.transfers_enabled()),626		};627628		Some(effective_limits)629	}630631	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {632		let Collection {633			name,634			description,635			owner,636			mode,637			token_prefix,638			sponsorship,639			limits,640			permissions,641		} = <CollectionById<T>>::get(collection)?;642643		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)644			.into_iter()645			.map(|(key, permission)| PropertyKeyPermission {646				key,647				permission,648			})649			.collect();650651		let properties = <CollectionProperties<T>>::get(collection)652			.into_iter()653			.map(|(key, value)| Property {654				key,655				value,656			})657			.collect();658659		Some(RpcCollection {660			name: name.into_inner(),661			description: description.into_inner(),662			owner,663			mode,664			token_prefix: token_prefix.into_inner(),665			sponsorship,666			limits,667			permissions,668			token_property_permissions,669			properties,670		})671	}672}673674macro_rules! limit_default {675	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{676		$(677			if let Some($new) = $new.$field {678				let $old = $old.$field($($arg)?);679				let _ = $new;680				let _ = $old;681				$check682			} else {683				$new.$field = $old.$field684			}685		)*686	}};687}688macro_rules! limit_default_clone {689	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{690		$(691			if let Some($new) = $new.$field.clone() {692				let $old = $old.$field($($arg)?);693				let _ = $new;694				let _ = $old;695				$check696			} else {697				$new.$field = $old.$field.clone()698			}699		)*700	}};701}702703impl<T: Config> Pallet<T> {704	pub fn init_collection(705		owner: T::AccountId,706		data: CreateCollectionData<T::AccountId>,707	) -> Result<CollectionId, DispatchError> {708		{709			ensure!(710				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,711				Error::<T>::CollectionTokenPrefixLimitExceeded712			);713		}714715		let created_count = <CreatedCollectionCount<T>>::get()716			.0717			.checked_add(1)718			.ok_or(ArithmeticError::Overflow)?;719		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;720		let id = CollectionId(created_count);721722		// bound Total number of collections723		ensure!(724			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,725			<Error<T>>::TotalCollectionsLimitExceeded726		);727728		// =========729730		let collection = Collection {731			owner: owner.clone(),732			name: data.name,733			mode: data.mode.clone(),734			description: data.description,735			token_prefix: data.token_prefix,736			sponsorship: data737				.pending_sponsor738				.map(SponsorshipState::Unconfirmed)739				.unwrap_or_default(),740			limits: data741				.limits742				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))743				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,744			permissions: data745				.permissions746				.map(|permissions| Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions))747				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,748		};749750		let mut collection_properties = up_data_structs::CollectionProperties::get();751		collection_properties752			.try_set_from_iter(data.properties.into_iter())753			.map_err(<Error<T>>::from)?;754755		CollectionProperties::<T>::insert(id, collection_properties);756757		let mut token_props_permissions = PropertiesPermissionMap::new();758		token_props_permissions759			.try_set_from_iter(data.token_property_permissions.into_iter())760			.map_err(<Error<T>>::from)?;761762		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);763764		// Take a (non-refundable) deposit of collection creation765		{766			let mut imbalance =767				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();768			imbalance.subsume(769				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(770					&T::TreasuryAccountId::get(),771					T::CollectionCreationPrice::get(),772				),773			);774			<T as Config>::Currency::settle(775				&owner,776				imbalance,777				WithdrawReasons::TRANSFER,778				ExistenceRequirement::KeepAlive,779			)780			.map_err(|_| Error::<T>::NotSufficientFounds)?;781		}782783		<CreatedCollectionCount<T>>::put(created_count);784		<Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));785		<CollectionById<T>>::insert(id, collection);786		Ok(id)787	}788789	pub fn destroy_collection(790		collection: CollectionHandle<T>,791		sender: &T::CrossAccountId,792	) -> DispatchResult {793		ensure!(794			collection.limits.owner_can_destroy(),795			<Error<T>>::NoPermission,796		);797		collection.check_is_owner(sender)?;798799		let destroyed_collections = <DestroyedCollectionCount<T>>::get()800			.0801			.checked_add(1)802			.ok_or(ArithmeticError::Overflow)?;803804		// =========805806		<DestroyedCollectionCount<T>>::put(destroyed_collections);807		<CollectionById<T>>::remove(collection.id);808		<AdminAmount<T>>::remove(collection.id);809		<IsAdmin<T>>::remove_prefix((collection.id,), None);810		<Allowlist<T>>::remove_prefix((collection.id,), None);811		<CollectionProperties<T>>::remove(collection.id);812813		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));814		Ok(())815	}816817	pub fn set_collection_property(818		collection: &CollectionHandle<T>,819		sender: &T::CrossAccountId,820		property: Property,821	) -> DispatchResult {822		collection.check_is_owner_or_admin(sender)?;823824		CollectionProperties::<T>::try_mutate(collection.id, |properties| {825			let property = property.clone();826			properties.try_set(property.key, property.value)827		})828		.map_err(<Error<T>>::from)?;829830		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));831832		Ok(())833	}834835	pub fn set_scoped_collection_property(836		collection_id: CollectionId,837		scope: PropertyScope,838		property: Property,839	) -> DispatchResult {840		CollectionProperties::<T>::try_mutate(collection_id, |properties| {841			properties.try_scoped_set(scope, property.key, property.value)842		})843		.map_err(<Error<T>>::from)?;844845		Ok(())846	}847848	pub fn set_scoped_collection_properties(849		collection_id: CollectionId,850		scope: PropertyScope,851		properties: impl Iterator<Item = Property>,852	) -> DispatchResult {853		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {854			stored_properties.try_scoped_set_from_iter(scope, properties)855		})856		.map_err(<Error<T>>::from)?;857858		Ok(())859	}860861	#[transactional]862	pub fn set_collection_properties(863		collection: &CollectionHandle<T>,864		sender: &T::CrossAccountId,865		properties: Vec<Property>,866	) -> DispatchResult {867		for property in properties {868			Self::set_collection_property(collection, sender, property)?;869		}870871		Ok(())872	}873874	pub fn delete_collection_property(875		collection: &CollectionHandle<T>,876		sender: &T::CrossAccountId,877		property_key: PropertyKey,878	) -> DispatchResult {879		collection.check_is_owner_or_admin(sender)?;880881		CollectionProperties::<T>::try_mutate(collection.id, |properties| {882			properties.remove(&property_key)883		})884		.map_err(<Error<T>>::from)?;885886		Self::deposit_event(Event::CollectionPropertyDeleted(887			collection.id,888			property_key,889		));890891		Ok(())892	}893894	#[transactional]895	pub fn delete_collection_properties(896		collection: &CollectionHandle<T>,897		sender: &T::CrossAccountId,898		property_keys: Vec<PropertyKey>,899	) -> DispatchResult {900		for key in property_keys {901			Self::delete_collection_property(collection, sender, key)?;902		}903904		Ok(())905	}906907	// For migrations908	pub fn set_property_permission_unchecked(909		collection: CollectionId,910		property_permission: PropertyKeyPermission,911	) -> DispatchResult {912		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {913			permissions.try_set(property_permission.key, property_permission.permission)914		})915		.map_err(<Error<T>>::from)?;916		Ok(())917	}918919	pub fn set_property_permission(920		collection: &CollectionHandle<T>,921		sender: &T::CrossAccountId,922		property_permission: PropertyKeyPermission,923	) -> DispatchResult {924		collection.check_is_owner_or_admin(sender)?;925926		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);927		let current_permission = all_permissions.get(&property_permission.key);928		if matches![929			current_permission,930			Some(PropertyPermission { mutable: false, .. })931		] {932			return Err(<Error<T>>::NoPermission.into());933		}934935		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {936			let property_permission = property_permission.clone();937			permissions.try_set(property_permission.key, property_permission.permission)938		})939		.map_err(<Error<T>>::from)?;940941		Self::deposit_event(Event::PropertyPermissionSet(942			collection.id,943			property_permission.key,944		));945946		Ok(())947	}948949	#[transactional]950	pub fn set_property_permissions(951		collection: &CollectionHandle<T>,952		sender: &T::CrossAccountId,953		property_permissions: Vec<PropertyKeyPermission>,954	) -> DispatchResult {955		for prop_pemission in property_permissions {956			Self::set_property_permission(collection, sender, prop_pemission)?;957		}958959		Ok(())960	}961962	pub fn get_collection_property(963		collection_id: CollectionId,964		key: &PropertyKey,965	) -> Option<PropertyValue> {966		Self::collection_properties(collection_id).get(key).cloned()967	}968969	pub fn bytes_keys_to_property_keys(970		keys: Vec<Vec<u8>>,971	) -> Result<Vec<PropertyKey>, DispatchError> {972		keys.into_iter()973			.map(|key| -> Result<PropertyKey, DispatchError> {974				key.try_into()975					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())976			})977			.collect::<Result<Vec<PropertyKey>, DispatchError>>()978	}979980	pub fn filter_collection_properties(981		collection_id: CollectionId,982		keys: Option<Vec<PropertyKey>>,983	) -> Result<Vec<Property>, DispatchError> {984		let properties = Self::collection_properties(collection_id);985986		let properties = keys987			.map(|keys| {988				keys.into_iter()989					.filter_map(|key| {990						properties.get(&key).map(|value| Property {991							key,992							value: value.clone(),993						})994					})995					.collect()996			})997			.unwrap_or_else(|| {998				properties999					.into_iter()1000					.map(|(key, value)| Property {1001						key,1002						value,1003					})1004					.collect()1005			});10061007		Ok(properties)1008	}10091010	pub fn filter_property_permissions(1011		collection_id: CollectionId,1012		keys: Option<Vec<PropertyKey>>,1013	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1014		let permissions = Self::property_permissions(collection_id);10151016		let key_permissions = keys1017			.map(|keys| {1018				keys.into_iter()1019					.filter_map(|key| {1020						permissions1021							.get(&key)1022							.map(|permission| PropertyKeyPermission {1023								key,1024								permission: permission.clone(),1025							})1026					})1027					.collect()1028			})1029			.unwrap_or_else(|| {1030				permissions1031					.into_iter()1032					.map(|(key, permission)| PropertyKeyPermission {1033						key,1034						permission,1035					})1036					.collect()1037			});10381039		Ok(key_permissions)1040	}10411042	pub fn toggle_allowlist(1043		collection: &CollectionHandle<T>,1044		sender: &T::CrossAccountId,1045		user: &T::CrossAccountId,1046		allowed: bool,1047	) -> DispatchResult {1048		collection.check_is_owner_or_admin(sender)?;10491050		// =========10511052		if allowed {1053			<Allowlist<T>>::insert((collection.id, user), true);1054		} else {1055			<Allowlist<T>>::remove((collection.id, user));1056		}10571058		Ok(())1059	}10601061	pub fn toggle_admin(1062		collection: &CollectionHandle<T>,1063		sender: &T::CrossAccountId,1064		user: &T::CrossAccountId,1065		admin: bool,1066	) -> DispatchResult {1067		collection.check_is_owner_or_admin(sender)?;10681069		let was_admin = <IsAdmin<T>>::get((collection.id, user));1070		if was_admin == admin {1071			return Ok(());1072		}1073		let amount = <AdminAmount<T>>::get(collection.id);10741075		if admin {1076			let amount = amount1077				.checked_add(1)1078				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1079			ensure!(1080				amount <= Self::collection_admins_limit(),1081				<Error<T>>::CollectionAdminCountExceeded,1082			);10831084			// =========10851086			<AdminAmount<T>>::insert(collection.id, amount);1087			<IsAdmin<T>>::insert((collection.id, user), true);1088		} else {1089			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1090			<IsAdmin<T>>::remove((collection.id, user));1091		}10921093		Ok(())1094	}10951096	pub fn clamp_limits(1097		mode: CollectionMode,1098		old_limit: &CollectionLimits,1099		mut new_limit: CollectionLimits,1100	) -> Result<CollectionLimits, DispatchError> {1101		limit_default!(old_limit, new_limit,1102			account_token_ownership_limit => ensure!(1103				new_limit <= MAX_TOKEN_OWNERSHIP,1104				<Error<T>>::CollectionLimitBoundsExceeded,1105			),1106			sponsor_transfer_timeout(match mode {1107				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1108				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1109				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1110			}) => ensure!(1111				new_limit <= MAX_SPONSOR_TIMEOUT,1112				<Error<T>>::CollectionLimitBoundsExceeded,1113			),1114			sponsored_data_size => ensure!(1115				new_limit <= CUSTOM_DATA_LIMIT,1116				<Error<T>>::CollectionLimitBoundsExceeded,1117			),1118			token_limit => ensure!(1119				old_limit >= new_limit && new_limit > 0,1120				<Error<T>>::CollectionTokenLimitExceeded1121			),1122			owner_can_transfer => ensure!(1123				old_limit || !new_limit,1124				<Error<T>>::OwnerPermissionsCantBeReverted,1125			),1126			owner_can_destroy => ensure!(1127				old_limit || !new_limit,1128				<Error<T>>::OwnerPermissionsCantBeReverted,1129			),1130			sponsored_data_rate_limit => {},1131			transfers_enabled => {},1132		);1133		Ok(new_limit)1134	}1135	pub fn clamp_permissions(1136		mode: CollectionMode,1137		old_limit: &CollectionPermissions,1138		mut new_limit: CollectionPermissions,1139	) -> Result<CollectionPermissions, DispatchError> {1140		limit_default_clone!(old_limit, new_limit,1141		);1142		Ok(new_limit)1143	}1144}11451146#[macro_export]1147macro_rules! unsupported {1148	() => {1149		Err(<Error<T>>::UnsupportedOperation.into())1150	};1151}11521153/// Worst cases1154pub trait CommonWeightInfo<CrossAccountId> {1155	fn create_item() -> Weight;1156	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1157	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1158	fn burn_item() -> Weight;1159	fn set_collection_properties(amount: u32) -> Weight;1160	fn delete_collection_properties(amount: u32) -> Weight;1161	fn set_token_properties(amount: u32) -> Weight;1162	fn delete_token_properties(amount: u32) -> Weight;1163	fn set_property_permissions(amount: u32) -> Weight;1164	fn transfer() -> Weight;1165	fn approve() -> Weight;1166	fn transfer_from() -> Weight;1167	fn burn_from() -> Weight;1168}11691170pub trait CommonCollectionOperations<T: Config> {1171	fn create_item(1172		&self,1173		sender: T::CrossAccountId,1174		to: T::CrossAccountId,1175		data: CreateItemData,1176		nesting_budget: &dyn Budget,1177	) -> DispatchResultWithPostInfo;1178	fn create_multiple_items(1179		&self,1180		sender: T::CrossAccountId,1181		to: T::CrossAccountId,1182		data: Vec<CreateItemData>,1183		nesting_budget: &dyn Budget,1184	) -> DispatchResultWithPostInfo;1185	fn create_multiple_items_ex(1186		&self,1187		sender: T::CrossAccountId,1188		data: CreateItemExData<T::CrossAccountId>,1189		nesting_budget: &dyn Budget,1190	) -> DispatchResultWithPostInfo;1191	fn burn_item(1192		&self,1193		sender: T::CrossAccountId,1194		token: TokenId,1195		amount: u128,1196	) -> DispatchResultWithPostInfo;1197	fn set_collection_properties(1198		&self,1199		sender: T::CrossAccountId,1200		properties: Vec<Property>,1201	) -> DispatchResultWithPostInfo;1202	fn delete_collection_properties(1203		&self,1204		sender: &T::CrossAccountId,1205		property_keys: Vec<PropertyKey>,1206	) -> DispatchResultWithPostInfo;1207	fn set_token_properties(1208		&self,1209		sender: T::CrossAccountId,1210		token_id: TokenId,1211		property: Vec<Property>,1212	) -> DispatchResultWithPostInfo;1213	fn delete_token_properties(1214		&self,1215		sender: T::CrossAccountId,1216		token_id: TokenId,1217		property_keys: Vec<PropertyKey>,1218	) -> DispatchResultWithPostInfo;1219	fn set_property_permissions(1220		&self,1221		sender: &T::CrossAccountId,1222		property_permissions: Vec<PropertyKeyPermission>,1223	) -> DispatchResultWithPostInfo;1224	fn transfer(1225		&self,1226		sender: T::CrossAccountId,1227		to: T::CrossAccountId,1228		token: TokenId,1229		amount: u128,1230		nesting_budget: &dyn Budget,1231	) -> DispatchResultWithPostInfo;1232	fn approve(1233		&self,1234		sender: T::CrossAccountId,1235		spender: T::CrossAccountId,1236		token: TokenId,1237		amount: u128,1238	) -> DispatchResultWithPostInfo;1239	fn transfer_from(1240		&self,1241		sender: T::CrossAccountId,1242		from: T::CrossAccountId,1243		to: T::CrossAccountId,1244		token: TokenId,1245		amount: u128,1246		nesting_budget: &dyn Budget,1247	) -> DispatchResultWithPostInfo;1248	fn burn_from(1249		&self,1250		sender: T::CrossAccountId,1251		from: T::CrossAccountId,1252		token: TokenId,1253		amount: u128,1254		nesting_budget: &dyn Budget,1255	) -> DispatchResultWithPostInfo;12561257	fn check_nesting(1258		&self,1259		sender: T::CrossAccountId,1260		from: (CollectionId, TokenId),1261		under: TokenId,1262		budget: &dyn Budget,1263	) -> DispatchResult;12641265	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1266	fn collection_tokens(&self) -> Vec<TokenId>;1267	fn token_exists(&self, token: TokenId) -> bool;1268	fn last_token_id(&self) -> TokenId;12691270	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1271	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1272	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1273	/// Amount of unique collection tokens1274	fn total_supply(&self) -> u32;1275	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1276	fn account_balance(&self, account: T::CrossAccountId) -> u32;1277	/// Amount of specific token account have (Applicable to fungible/refungible)1278	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1279	fn allowance(1280		&self,1281		sender: T::CrossAccountId,1282		spender: T::CrossAccountId,1283		token: TokenId,1284	) -> u128;1285}12861287// Flexible enough for implementing CommonCollectionOperations1288pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1289	let post_info = PostDispatchInfo {1290		actual_weight: Some(weight),1291		pays_fee: Pays::Yes,1292	};1293	match res {1294		Ok(()) => Ok(post_info),1295		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1296	}1297}12981299impl<T: Config> From<PropertiesError> for Error<T> {1300	fn from(error: PropertiesError) -> Self {1301		match error {1302			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1303			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1304			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1305			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1306			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1307		}1308	}1309}