git.delta.rocks / unique-network / refs/commits / 0f7bb4adb180

difftreelog

source

pallets/common/src/lib.rs36.1 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, Pallet as PalletEvm};25use evm_coder::ToLog;26use frame_support::{27	dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},28	ensure,29	traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},30	weights::Pays,31	transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35	COLLECTION_NUMBER_LIMIT,36	Collection,37	RpcCollection,38	CollectionId,39	CreateItemData,40	MAX_TOKEN_PREFIX_LENGTH,41	COLLECTION_ADMINS_LIMIT,42	TokenId,43	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	PhantomType,58	Property,59	Properties,60	PropertiesPermissionMap,61	PropertyKey,62	PropertyValue,63	PropertyPermission,64	PropertiesError,65	PropertyKeyPermission,66	TokenData,67	TrySetProperty,68	PropertyScope,69	// RMRK70	RmrkCollectionInfo,71	RmrkInstanceInfo,72	RmrkResourceInfo,73	RmrkPropertyInfo,74	RmrkBaseInfo,75	RmrkPartType,76	RmrkTheme,77	RmrkNftChild,78	CollectionPermissions,79	SchemaVersion,80};8182pub use pallet::*;83use sp_core::H160;84use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};85#[cfg(feature = "runtime-benchmarks")]86pub mod benchmarking;87pub mod dispatch;88pub mod erc;89pub mod eth;90pub mod weights;9192pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9394#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]95pub struct CollectionHandle<T: Config> {96	pub id: CollectionId,97	collection: Collection<T::AccountId>,98	pub recorder: SubstrateRecorder<T>,99}100impl<T: Config> WithRecorder<T> for CollectionHandle<T> {101	fn recorder(&self) -> &SubstrateRecorder<T> {102		&self.recorder103	}104	fn into_recorder(self) -> SubstrateRecorder<T> {105		self.recorder106	}107}108impl<T: Config> CollectionHandle<T> {109	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {110		<CollectionById<T>>::get(id).map(|collection| Self {111			id,112			collection,113			recorder: SubstrateRecorder::new(gas_limit),114		})115	}116117	pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {118		<CollectionById<T>>::get(id).map(|collection| Self {119			id,120			collection,121			recorder,122		})123	}124125	pub fn new(id: CollectionId) -> Option<Self> {126		Self::new_with_gas_limit(id, u64::MAX)127	}128	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {129		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)130	}131	pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {132		self.recorder133			.consume_gas(T::GasWeightMapping::weight_to_gas(134				<T as frame_system::Config>::DbWeight::get()135					.read136					.saturating_mul(reads),137			))138	}139	pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {140		self.recorder141			.consume_gas(T::GasWeightMapping::weight_to_gas(142				<T as frame_system::Config>::DbWeight::get()143					.write144					.saturating_mul(writes),145			))146	}147	pub fn save(self) -> DispatchResult {148		<CollectionById<T>>::insert(self.id, self.collection);149		Ok(())150	}151152	pub fn set_sponsor(&mut self, sponsor: T::AccountId) {153		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);154	}155156	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> bool {157		if self.collection.sponsorship.pending_sponsor() != Some(sender) {158			return false;159		};160161		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());162		true163	}164}165impl<T: Config> Deref for CollectionHandle<T> {166	type Target = Collection<T::AccountId>;167168	fn deref(&self) -> &Self::Target {169		&self.collection170	}171}172173impl<T: Config> DerefMut for CollectionHandle<T> {174	fn deref_mut(&mut self) -> &mut Self::Target {175		&mut self.collection176	}177}178179impl<T: Config> CollectionHandle<T> {180	pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {181		ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);182		Ok(())183	}184	pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {185		*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))186	}187	pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {188		ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);189		Ok(())190	}191	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {192		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)193	}194	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {195		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)196	}197	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {198		ensure!(199			<Allowlist<T>>::get((self.id, user)),200			<Error<T>>::AddressNotInAllowlist201		);202		Ok(())203	}204}205206#[frame_support::pallet]207pub mod pallet {208	use super::*;209	use pallet_evm::account;210	use dispatch::CollectionDispatch;211	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};212	use frame_system::pallet_prelude::*;213	use frame_support::traits::Currency;214	use up_data_structs::{TokenId, mapping::TokenAddressMapping};215	use scale_info::TypeInfo;216	use weights::WeightInfo;217218	#[pallet::config]219	pub trait Config:220		frame_system::Config221		+ pallet_evm_coder_substrate::Config222		+ pallet_evm::Config223		+ TypeInfo224		+ account::Config225	{226		type WeightInfo: WeightInfo;227		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;228229		type Currency: Currency<Self::AccountId>;230231		#[pallet::constant]232		type CollectionCreationPrice: Get<233			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,234		>;235		type CollectionDispatch: CollectionDispatch<Self>;236237		type TreasuryAccountId: Get<Self::AccountId>;238		type ContractAddress: Get<H160>;239240		type EvmTokenAddressMapping: TokenAddressMapping<H160>;241		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;242	}243244	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);245246	#[pallet::pallet]247	#[pallet::storage_version(STORAGE_VERSION)]248	#[pallet::generate_store(pub(super) trait Store)]249	pub struct Pallet<T>(_);250251	#[pallet::extra_constants]252	impl<T: Config> Pallet<T> {253		pub fn collection_admins_limit() -> u32 {254			COLLECTION_ADMINS_LIMIT255		}256	}257258	#[pallet::event]259	#[pallet::generate_deposit(pub fn deposit_event)]260	pub enum Event<T: Config> {261		/// New collection was created262		///263		/// # Arguments264		///265		/// * collection_id: Globally unique identifier of newly created collection.266		///267		/// * mode: [CollectionMode] converted into u8.268		///269		/// * account_id: Collection owner.270		CollectionCreated(CollectionId, u8, T::AccountId),271272		/// New collection was destroyed273		///274		/// # Arguments275		///276		/// * collection_id: Globally unique identifier of collection.277		CollectionDestroyed(CollectionId),278279		/// New item was created.280		///281		/// # Arguments282		///283		/// * collection_id: Id of the collection where item was created.284		///285		/// * item_id: Id of an item. Unique within the collection.286		///287		/// * recipient: Owner of newly created item288		///289		/// * amount: Always 1 for NFT290		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),291292		/// Collection item was burned.293		///294		/// # Arguments295		///296		/// * collection_id.297		///298		/// * item_id: Identifier of burned NFT.299		///300		/// * owner: which user has destroyed its tokens301		///302		/// * amount: Always 1 for NFT303		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),304305		/// Item was transferred306		///307		/// * collection_id: Id of collection to which item is belong308		///309		/// * item_id: Id of an item310		///311		/// * sender: Original owner of item312		///313		/// * recipient: New owner of item314		///315		/// * amount: Always 1 for NFT316		Transfer(317			CollectionId,318			TokenId,319			T::CrossAccountId,320			T::CrossAccountId,321			u128,322		),323324		/// * collection_id325		///326		/// * item_id327		///328		/// * sender329		///330		/// * spender331		///332		/// * amount333		Approved(334			CollectionId,335			TokenId,336			T::CrossAccountId,337			T::CrossAccountId,338			u128,339		),340341		CollectionPropertySet(CollectionId, PropertyKey),342343		CollectionPropertyDeleted(CollectionId, PropertyKey),344345		TokenPropertySet(CollectionId, TokenId, PropertyKey),346347		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),348349		PropertyPermissionSet(CollectionId, PropertyKey),350	}351352	#[pallet::error]353	pub enum Error<T> {354		/// This collection does not exist.355		CollectionNotFound,356		/// Sender parameter and item owner must be equal.357		MustBeTokenOwner,358		/// No permission to perform action359		NoPermission,360		/// Destroying only empty collections is allowed361		CantDestroyNotEmptyCollection,362		/// Collection is not in mint mode.363		PublicMintingNotAllowed,364		/// Address is not in allow list.365		AddressNotInAllowlist,366367		/// Collection name can not be longer than 63 char.368		CollectionNameLimitExceeded,369		/// Collection description can not be longer than 255 char.370		CollectionDescriptionLimitExceeded,371		/// Token prefix can not be longer than 15 char.372		CollectionTokenPrefixLimitExceeded,373		/// Total collections bound exceeded.374		TotalCollectionsLimitExceeded,375		/// Exceeded max admin count376		CollectionAdminCountExceeded,377		/// Collection limit bounds per collection exceeded378		CollectionLimitBoundsExceeded,379		/// Tried to enable permissions which are only permitted to be disabled380		OwnerPermissionsCantBeReverted,381		/// Collection settings not allowing items transferring382		TransferNotAllowed,383		/// Account token limit exceeded per collection384		AccountTokenLimitExceeded,385		/// Collection token limit exceeded386		CollectionTokenLimitExceeded,387		/// Metadata flag frozen388		MetadataFlagFrozen,389390		/// Item not exists.391		TokenNotFound,392		/// Item balance not enough.393		TokenValueTooLow,394		/// Requested value more than approved.395		ApprovedValueTooLow,396		/// Tried to approve more than owned397		CantApproveMoreThanOwned,398399		/// Can't transfer tokens to ethereum zero address400		AddressIsZero,401		/// Target collection doesn't supports this operation402		UnsupportedOperation,403404		/// Not sufficient founds to perform action405		NotSufficientFounds,406407		/// Collection has nesting disabled408		NestingIsDisabled,409		/// Only owner may nest tokens under this collection410		OnlyOwnerAllowedToNest,411		/// Only tokens from specific collections may nest tokens under this412		SourceCollectionIsNotAllowedToNest,413414		/// Tried to store more data than allowed in collection field415		CollectionFieldSizeExceeded,416417		/// Tried to store more property data than allowed418		NoSpaceForProperty,419420		/// Tried to store more property keys than allowed421		PropertyLimitReached,422423		/// Property key is too long424		PropertyKeyIsTooLong,425426		/// Only ASCII letters, digits, and '_', '-' are allowed427		InvalidCharacterInPropertyKey,428429		/// Empty property keys are forbidden430		EmptyPropertyKey,431	}432433	#[pallet::storage]434	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;435	#[pallet::storage]436	pub type DestroyedCollectionCount<T> =437		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;438439	/// Collection info440	#[pallet::storage]441	pub type CollectionById<T> = StorageMap<442		Hasher = Blake2_128Concat,443		Key = CollectionId,444		Value = Collection<<T as frame_system::Config>::AccountId>,445		QueryKind = OptionQuery,446	>;447448	/// Collection properties449	#[pallet::storage]450	#[pallet::getter(fn collection_properties)]451	pub type CollectionProperties<T> = StorageMap<452		Hasher = Blake2_128Concat,453		Key = CollectionId,454		Value = Properties,455		QueryKind = ValueQuery,456		OnEmpty = up_data_structs::CollectionProperties,457	>;458459	#[pallet::storage]460	#[pallet::getter(fn property_permissions)]461	pub type CollectionPropertyPermissions<T> = StorageMap<462		Hasher = Blake2_128Concat,463		Key = CollectionId,464		Value = PropertiesPermissionMap,465		QueryKind = ValueQuery,466	>;467468	#[pallet::storage]469	pub type AdminAmount<T> = StorageMap<470		Hasher = Blake2_128Concat,471		Key = CollectionId,472		Value = u32,473		QueryKind = ValueQuery,474	>;475476	/// List of collection admins477	#[pallet::storage]478	pub type IsAdmin<T: Config> = StorageNMap<479		Key = (480			Key<Blake2_128Concat, CollectionId>,481			Key<Blake2_128Concat, T::CrossAccountId>,482		),483		Value = bool,484		QueryKind = ValueQuery,485	>;486487	/// Allowlisted collection users488	#[pallet::storage]489	pub type Allowlist<T: Config> = StorageNMap<490		Key = (491			Key<Blake2_128Concat, CollectionId>,492			Key<Blake2_128Concat, T::CrossAccountId>,493		),494		Value = bool,495		QueryKind = ValueQuery,496	>;497498	/// Not used by code, exists only to provide some types to metadata499	#[pallet::storage]500	pub type DummyStorageValue<T: Config> = StorageValue<501		Value = (502			CollectionStats,503			CollectionId,504			TokenId,505			PhantomType<(506				TokenData<T::CrossAccountId>,507				RpcCollection<T::AccountId>,508				// RMRK509				RmrkCollectionInfo<T::AccountId>,510				RmrkInstanceInfo<T::AccountId>,511				RmrkResourceInfo,512				RmrkPropertyInfo,513				RmrkBaseInfo<T::AccountId>,514				RmrkPartType,515				RmrkTheme,516				RmrkNftChild,517			)>,518		),519		QueryKind = OptionQuery,520	>;521522	#[pallet::hooks]523	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {524		fn on_runtime_upgrade() -> Weight {525			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {526				use up_data_structs::{CollectionVersion1, CollectionVersion2};527				<CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {528					let mut props = Vec::new();529					if !v.offchain_schema.is_empty() {530						props.push(Property {531							key: b"_old_offchainSchema".to_vec().try_into().unwrap(),532							value: v533								.offchain_schema534								.clone()535								.into_inner()536								.try_into()537								.expect("offchain schema too big"),538						});539					}540					if !v.variable_on_chain_schema.is_empty() {541						props.push(Property {542							key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),543							value: v544								.variable_on_chain_schema545								.clone()546								.into_inner()547								.try_into()548								.expect("offchain schema too big"),549						});550					}551					if !v.const_on_chain_schema.is_empty() {552						props.push(Property {553							key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),554							value: v555								.const_on_chain_schema556								.clone()557								.into_inner()558								.try_into()559								.expect("offchain schema too big"),560						});561					}562					props.push(Property {563						key: b"_old_schemaVersion".to_vec().try_into().unwrap(),564						value: match v.schema_version {565							SchemaVersion::ImageURL => b"ImageUrl".as_slice(),566							SchemaVersion::Unique => b"Unique".as_slice(),567						}568						.to_vec()569						.try_into()570						.unwrap(),571					});572					Self::set_scoped_collection_properties(573						id,574						PropertyScope::None,575						props.into_iter(),576					)577					.expect("existing data larger than properties");578					let mut new = CollectionVersion2::from(v.clone());579					new.permissions.access = Some(v.access);580					new.permissions.mint_mode = Some(v.mint_mode);581					Some(new)582				});583			}584585			0586		}587	}588}589590impl<T: Config> Pallet<T> {591	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens592	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {593		ensure!(594			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,595			<Error<T>>::AddressIsZero596		);597		Ok(())598	}599	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {600		<IsAdmin<T>>::iter_prefix((collection,))601			.map(|(a, _)| a)602			.collect()603	}604	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {605		<Allowlist<T>>::iter_prefix((collection,))606			.map(|(a, _)| a)607			.collect()608	}609	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {610		<Allowlist<T>>::get((collection, user))611	}612	pub fn collection_stats() -> CollectionStats {613		let created = <CreatedCollectionCount<T>>::get();614		let destroyed = <DestroyedCollectionCount<T>>::get();615		CollectionStats {616			created: created.0,617			destroyed: destroyed.0,618			alive: created.0 - destroyed.0,619		}620	}621622	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {623		let collection = <CollectionById<T>>::get(collection);624		if collection.is_none() {625			return None;626		}627628		let collection = collection.unwrap();629		let limits = collection.limits;630		let effective_limits = CollectionLimits {631			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),632			sponsored_data_size: Some(limits.sponsored_data_size()),633			sponsored_data_rate_limit: Some(634				limits635					.sponsored_data_rate_limit636					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),637			),638			token_limit: Some(limits.token_limit()),639			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(640				match collection.mode {641					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,642					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,643					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,644				},645			)),646			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),647			owner_can_transfer: Some(limits.owner_can_transfer()),648			owner_can_destroy: Some(limits.owner_can_destroy()),649			transfers_enabled: Some(limits.transfers_enabled()),650		};651652		Some(effective_limits)653	}654655	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {656		let Collection {657			name,658			description,659			owner,660			mode,661			token_prefix,662			sponsorship,663			limits,664			permissions,665		} = <CollectionById<T>>::get(collection)?;666667		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)668			.into_iter()669			.map(|(key, permission)| PropertyKeyPermission { key, permission })670			.collect();671672		let properties = <CollectionProperties<T>>::get(collection)673			.into_iter()674			.map(|(key, value)| Property { key, value })675			.collect();676677		let permissions = CollectionPermissions {678			access: Some(permissions.access()),679			mint_mode: Some(permissions.mint_mode()),680			nesting: Some(permissions.nesting().clone()),681		};682683		Some(RpcCollection {684			name: name.into_inner(),685			description: description.into_inner(),686			owner,687			mode,688			token_prefix: token_prefix.into_inner(),689			sponsorship,690			limits,691			permissions,692			token_property_permissions,693			properties,694		})695	}696}697698macro_rules! limit_default {699	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{700		$(701			if let Some($new) = $new.$field {702				let $old = $old.$field($($arg)?);703				let _ = $new;704				let _ = $old;705				$check706			} else {707				$new.$field = $old.$field708			}709		)*710	}};711}712macro_rules! limit_default_clone {713	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{714		$(715			if let Some($new) = $new.$field.clone() {716				let $old = $old.$field($($arg)?);717				let _ = $new;718				let _ = $old;719				$check720			} else {721				$new.$field = $old.$field.clone()722			}723		)*724	}};725}726727impl<T: Config> Pallet<T> {728	pub fn init_collection(729		owner: T::CrossAccountId,730		data: CreateCollectionData<T::AccountId>,731	) -> Result<CollectionId, DispatchError> {732		{733			ensure!(734				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,735				Error::<T>::CollectionTokenPrefixLimitExceeded736			);737		}738739		let created_count = <CreatedCollectionCount<T>>::get()740			.0741			.checked_add(1)742			.ok_or(ArithmeticError::Overflow)?;743		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;744		let id = CollectionId(created_count);745746		// bound Total number of collections747		ensure!(748			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,749			<Error<T>>::TotalCollectionsLimitExceeded750		);751752		// =========753754		let collection = Collection {755			owner: owner.as_sub().clone(),756			name: data.name,757			mode: data.mode.clone(),758			description: data.description,759			token_prefix: data.token_prefix,760			sponsorship: data761				.pending_sponsor762				.map(SponsorshipState::Unconfirmed)763				.unwrap_or_default(),764			limits: data765				.limits766				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))767				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,768			permissions: data769				.permissions770				.map(|permissions| {771					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)772				})773				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,774		};775776		let mut collection_properties = up_data_structs::CollectionProperties::get();777		collection_properties778			.try_set_from_iter(data.properties.into_iter())779			.map_err(<Error<T>>::from)?;780781		CollectionProperties::<T>::insert(id, collection_properties);782783		let mut token_props_permissions = PropertiesPermissionMap::new();784		token_props_permissions785			.try_set_from_iter(data.token_property_permissions.into_iter())786			.map_err(<Error<T>>::from)?;787788		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);789790		// Take a (non-refundable) deposit of collection creation791		{792			let mut imbalance =793				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();794			imbalance.subsume(795				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(796					&T::TreasuryAccountId::get(),797					T::CollectionCreationPrice::get(),798				),799			);800			<T as Config>::Currency::settle(801				&owner.as_sub(),802				imbalance,803				WithdrawReasons::TRANSFER,804				ExistenceRequirement::KeepAlive,805			)806			.map_err(|_| Error::<T>::NotSufficientFounds)?;807		}808809		<CreatedCollectionCount<T>>::put(created_count);810		<Pallet<T>>::deposit_event(Event::CollectionCreated(811			id,812			data.mode.id(),813			owner.as_sub().clone(),814		));815		<PalletEvm<T>>::deposit_log(816			erc::CollectionHelpersEvents::CollectionCreated {817				owner: *owner.as_eth(),818				collection_id: eth::collection_id_to_address(id),819			}820			.to_log(T::ContractAddress::get()),821		);822		<CollectionById<T>>::insert(id, collection);823		Ok(id)824	}825826	pub fn destroy_collection(827		collection: CollectionHandle<T>,828		sender: &T::CrossAccountId,829	) -> DispatchResult {830		ensure!(831			collection.limits.owner_can_destroy(),832			<Error<T>>::NoPermission,833		);834		collection.check_is_owner(sender)?;835836		let destroyed_collections = <DestroyedCollectionCount<T>>::get()837			.0838			.checked_add(1)839			.ok_or(ArithmeticError::Overflow)?;840841		// =========842843		<DestroyedCollectionCount<T>>::put(destroyed_collections);844		<CollectionById<T>>::remove(collection.id);845		<AdminAmount<T>>::remove(collection.id);846		<IsAdmin<T>>::remove_prefix((collection.id,), None);847		<Allowlist<T>>::remove_prefix((collection.id,), None);848		<CollectionProperties<T>>::remove(collection.id);849850		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));851		Ok(())852	}853854	pub fn set_collection_property(855		collection: &CollectionHandle<T>,856		sender: &T::CrossAccountId,857		property: Property,858	) -> DispatchResult {859		collection.check_is_owner_or_admin(sender)?;860861		CollectionProperties::<T>::try_mutate(collection.id, |properties| {862			let property = property.clone();863			properties.try_set(property.key, property.value)864		})865		.map_err(<Error<T>>::from)?;866867		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));868869		Ok(())870	}871872	pub fn set_scoped_collection_property(873		collection_id: CollectionId,874		scope: PropertyScope,875		property: Property,876	) -> DispatchResult {877		CollectionProperties::<T>::try_mutate(collection_id, |properties| {878			properties.try_scoped_set(scope, property.key, property.value)879		})880		.map_err(<Error<T>>::from)?;881882		Ok(())883	}884885	pub fn set_scoped_collection_properties(886		collection_id: CollectionId,887		scope: PropertyScope,888		properties: impl Iterator<Item = Property>,889	) -> DispatchResult {890		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {891			stored_properties.try_scoped_set_from_iter(scope, properties)892		})893		.map_err(<Error<T>>::from)?;894895		Ok(())896	}897898	#[transactional]899	pub fn set_collection_properties(900		collection: &CollectionHandle<T>,901		sender: &T::CrossAccountId,902		properties: Vec<Property>,903	) -> DispatchResult {904		for property in properties {905			Self::set_collection_property(collection, sender, property)?;906		}907908		Ok(())909	}910911	pub fn delete_collection_property(912		collection: &CollectionHandle<T>,913		sender: &T::CrossAccountId,914		property_key: PropertyKey,915	) -> DispatchResult {916		collection.check_is_owner_or_admin(sender)?;917918		CollectionProperties::<T>::try_mutate(collection.id, |properties| {919			properties.remove(&property_key)920		})921		.map_err(<Error<T>>::from)?;922923		Self::deposit_event(Event::CollectionPropertyDeleted(924			collection.id,925			property_key,926		));927928		Ok(())929	}930931	#[transactional]932	pub fn delete_collection_properties(933		collection: &CollectionHandle<T>,934		sender: &T::CrossAccountId,935		property_keys: Vec<PropertyKey>,936	) -> DispatchResult {937		for key in property_keys {938			Self::delete_collection_property(collection, sender, key)?;939		}940941		Ok(())942	}943944	// For migrations945	pub fn set_property_permission_unchecked(946		collection: CollectionId,947		property_permission: PropertyKeyPermission,948	) -> DispatchResult {949		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {950			permissions.try_set(property_permission.key, property_permission.permission)951		})952		.map_err(<Error<T>>::from)?;953		Ok(())954	}955956	pub fn set_property_permission(957		collection: &CollectionHandle<T>,958		sender: &T::CrossAccountId,959		property_permission: PropertyKeyPermission,960	) -> DispatchResult {961		collection.check_is_owner_or_admin(sender)?;962963		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);964		let current_permission = all_permissions.get(&property_permission.key);965		if matches![966			current_permission,967			Some(PropertyPermission { mutable: false, .. })968		] {969			return Err(<Error<T>>::NoPermission.into());970		}971972		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {973			let property_permission = property_permission.clone();974			permissions.try_set(property_permission.key, property_permission.permission)975		})976		.map_err(<Error<T>>::from)?;977978		Self::deposit_event(Event::PropertyPermissionSet(979			collection.id,980			property_permission.key,981		));982983		Ok(())984	}985986	#[transactional]987	pub fn set_property_permissions(988		collection: &CollectionHandle<T>,989		sender: &T::CrossAccountId,990		property_permissions: Vec<PropertyKeyPermission>,991	) -> DispatchResult {992		for prop_pemission in property_permissions {993			Self::set_property_permission(collection, sender, prop_pemission)?;994		}995996		Ok(())997	}998999	pub fn get_collection_property(1000		collection_id: CollectionId,1001		key: &PropertyKey,1002	) -> Option<PropertyValue> {1003		Self::collection_properties(collection_id).get(key).cloned()1004	}10051006	pub fn bytes_keys_to_property_keys(1007		keys: Vec<Vec<u8>>,1008	) -> Result<Vec<PropertyKey>, DispatchError> {1009		keys.into_iter()1010			.map(|key| -> Result<PropertyKey, DispatchError> {1011				key.try_into()1012					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1013			})1014			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1015	}10161017	pub fn filter_collection_properties(1018		collection_id: CollectionId,1019		keys: Option<Vec<PropertyKey>>,1020	) -> Result<Vec<Property>, DispatchError> {1021		let properties = Self::collection_properties(collection_id);10221023		let properties = keys1024			.map(|keys| {1025				keys.into_iter()1026					.filter_map(|key| {1027						properties.get(&key).map(|value| Property {1028							key,1029							value: value.clone(),1030						})1031					})1032					.collect()1033			})1034			.unwrap_or_else(|| {1035				properties1036					.into_iter()1037					.map(|(key, value)| Property { key, value })1038					.collect()1039			});10401041		Ok(properties)1042	}10431044	pub fn filter_property_permissions(1045		collection_id: CollectionId,1046		keys: Option<Vec<PropertyKey>>,1047	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1048		let permissions = Self::property_permissions(collection_id);10491050		let key_permissions = keys1051			.map(|keys| {1052				keys.into_iter()1053					.filter_map(|key| {1054						permissions1055							.get(&key)1056							.map(|permission| PropertyKeyPermission {1057								key,1058								permission: permission.clone(),1059							})1060					})1061					.collect()1062			})1063			.unwrap_or_else(|| {1064				permissions1065					.into_iter()1066					.map(|(key, permission)| PropertyKeyPermission { key, permission })1067					.collect()1068			});10691070		Ok(key_permissions)1071	}10721073	pub fn toggle_allowlist(1074		collection: &CollectionHandle<T>,1075		sender: &T::CrossAccountId,1076		user: &T::CrossAccountId,1077		allowed: bool,1078	) -> DispatchResult {1079		collection.check_is_owner_or_admin(sender)?;10801081		// =========10821083		if allowed {1084			<Allowlist<T>>::insert((collection.id, user), true);1085		} else {1086			<Allowlist<T>>::remove((collection.id, user));1087		}10881089		Ok(())1090	}10911092	pub fn toggle_admin(1093		collection: &CollectionHandle<T>,1094		sender: &T::CrossAccountId,1095		user: &T::CrossAccountId,1096		admin: bool,1097	) -> DispatchResult {1098		collection.check_is_owner_or_admin(sender)?;10991100		let was_admin = <IsAdmin<T>>::get((collection.id, user));1101		if was_admin == admin {1102			return Ok(());1103		}1104		let amount = <AdminAmount<T>>::get(collection.id);11051106		if admin {1107			let amount = amount1108				.checked_add(1)1109				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1110			ensure!(1111				amount <= Self::collection_admins_limit(),1112				<Error<T>>::CollectionAdminCountExceeded,1113			);11141115			// =========11161117			<AdminAmount<T>>::insert(collection.id, amount);1118			<IsAdmin<T>>::insert((collection.id, user), true);1119		} else {1120			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1121			<IsAdmin<T>>::remove((collection.id, user));1122		}11231124		Ok(())1125	}11261127	pub fn clamp_limits(1128		mode: CollectionMode,1129		old_limit: &CollectionLimits,1130		mut new_limit: CollectionLimits,1131	) -> Result<CollectionLimits, DispatchError> {1132		limit_default!(old_limit, new_limit,1133			account_token_ownership_limit => ensure!(1134				new_limit <= MAX_TOKEN_OWNERSHIP,1135				<Error<T>>::CollectionLimitBoundsExceeded,1136			),1137			sponsored_data_size => ensure!(1138				new_limit <= CUSTOM_DATA_LIMIT,1139				<Error<T>>::CollectionLimitBoundsExceeded,1140			),11411142			sponsored_data_rate_limit => {},1143			token_limit => ensure!(1144				old_limit >= new_limit && new_limit > 0,1145				<Error<T>>::CollectionTokenLimitExceeded1146			),11471148			sponsor_transfer_timeout(match mode {1149				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1150				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1151				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1152			}) => ensure!(1153				new_limit <= MAX_SPONSOR_TIMEOUT,1154				<Error<T>>::CollectionLimitBoundsExceeded,1155			),1156			sponsor_approve_timeout => {},1157			owner_can_transfer => ensure!(1158				old_limit || !new_limit,1159				<Error<T>>::OwnerPermissionsCantBeReverted,1160			),1161			owner_can_destroy => ensure!(1162				old_limit || !new_limit,1163				<Error<T>>::OwnerPermissionsCantBeReverted,1164			),1165			transfers_enabled => {},1166		);1167		Ok(new_limit)1168	}1169	pub fn clamp_permissions(1170		mode: CollectionMode,1171		old_limit: &CollectionPermissions,1172		mut new_limit: CollectionPermissions,1173	) -> Result<CollectionPermissions, DispatchError> {1174		limit_default_clone!(old_limit, new_limit,1175			access => {},1176			mint_mode => {},1177			nesting => {},1178		);1179		Ok(new_limit)1180	}1181}11821183#[macro_export]1184macro_rules! unsupported {1185	() => {1186		Err(<Error<T>>::UnsupportedOperation.into())1187	};1188}11891190/// Worst cases1191pub trait CommonWeightInfo<CrossAccountId> {1192	fn create_item() -> Weight;1193	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1194	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1195	fn burn_item() -> Weight;1196	fn set_collection_properties(amount: u32) -> Weight;1197	fn delete_collection_properties(amount: u32) -> Weight;1198	fn set_token_properties(amount: u32) -> Weight;1199	fn delete_token_properties(amount: u32) -> Weight;1200	fn set_property_permissions(amount: u32) -> Weight;1201	fn transfer() -> Weight;1202	fn approve() -> Weight;1203	fn transfer_from() -> Weight;1204	fn burn_from() -> Weight;1205}12061207pub trait CommonCollectionOperations<T: Config> {1208	fn create_item(1209		&self,1210		sender: T::CrossAccountId,1211		to: T::CrossAccountId,1212		data: CreateItemData,1213		nesting_budget: &dyn Budget,1214	) -> DispatchResultWithPostInfo;1215	fn create_multiple_items(1216		&self,1217		sender: T::CrossAccountId,1218		to: T::CrossAccountId,1219		data: Vec<CreateItemData>,1220		nesting_budget: &dyn Budget,1221	) -> DispatchResultWithPostInfo;1222	fn create_multiple_items_ex(1223		&self,1224		sender: T::CrossAccountId,1225		data: CreateItemExData<T::CrossAccountId>,1226		nesting_budget: &dyn Budget,1227	) -> DispatchResultWithPostInfo;1228	fn burn_item(1229		&self,1230		sender: T::CrossAccountId,1231		token: TokenId,1232		amount: u128,1233	) -> DispatchResultWithPostInfo;1234	fn set_collection_properties(1235		&self,1236		sender: T::CrossAccountId,1237		properties: Vec<Property>,1238	) -> DispatchResultWithPostInfo;1239	fn delete_collection_properties(1240		&self,1241		sender: &T::CrossAccountId,1242		property_keys: Vec<PropertyKey>,1243	) -> DispatchResultWithPostInfo;1244	fn set_token_properties(1245		&self,1246		sender: T::CrossAccountId,1247		token_id: TokenId,1248		property: Vec<Property>,1249	) -> DispatchResultWithPostInfo;1250	fn delete_token_properties(1251		&self,1252		sender: T::CrossAccountId,1253		token_id: TokenId,1254		property_keys: Vec<PropertyKey>,1255	) -> DispatchResultWithPostInfo;1256	fn set_property_permissions(1257		&self,1258		sender: &T::CrossAccountId,1259		property_permissions: Vec<PropertyKeyPermission>,1260	) -> DispatchResultWithPostInfo;1261	fn transfer(1262		&self,1263		sender: T::CrossAccountId,1264		to: T::CrossAccountId,1265		token: TokenId,1266		amount: u128,1267		nesting_budget: &dyn Budget,1268	) -> DispatchResultWithPostInfo;1269	fn approve(1270		&self,1271		sender: T::CrossAccountId,1272		spender: T::CrossAccountId,1273		token: TokenId,1274		amount: u128,1275	) -> DispatchResultWithPostInfo;1276	fn transfer_from(1277		&self,1278		sender: T::CrossAccountId,1279		from: T::CrossAccountId,1280		to: T::CrossAccountId,1281		token: TokenId,1282		amount: u128,1283		nesting_budget: &dyn Budget,1284	) -> DispatchResultWithPostInfo;1285	fn burn_from(1286		&self,1287		sender: T::CrossAccountId,1288		from: T::CrossAccountId,1289		token: TokenId,1290		amount: u128,1291		nesting_budget: &dyn Budget,1292	) -> DispatchResultWithPostInfo;12931294	fn check_nesting(1295		&self,1296		sender: T::CrossAccountId,1297		from: (CollectionId, TokenId),1298		under: TokenId,1299		budget: &dyn Budget,1300	) -> DispatchResult;13011302	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13031304	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13051306	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1307	fn collection_tokens(&self) -> Vec<TokenId>;1308	fn token_exists(&self, token: TokenId) -> bool;1309	fn last_token_id(&self) -> TokenId;13101311	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1312	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1313	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1314	/// Amount of unique collection tokens1315	fn total_supply(&self) -> u32;1316	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1317	fn account_balance(&self, account: T::CrossAccountId) -> u32;1318	/// Amount of specific token account have (Applicable to fungible/refungible)1319	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1320	fn allowance(1321		&self,1322		sender: T::CrossAccountId,1323		spender: T::CrossAccountId,1324		token: TokenId,1325	) -> u128;1326}13271328// Flexible enough for implementing CommonCollectionOperations1329pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1330	let post_info = PostDispatchInfo {1331		actual_weight: Some(weight),1332		pays_fee: Pays::Yes,1333	};1334	match res {1335		Ok(()) => Ok(post_info),1336		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1337	}1338}13391340impl<T: Config> From<PropertiesError> for Error<T> {1341	fn from(error: PropertiesError) -> Self {1342		match error {1343			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1344			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1345			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1346			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1347			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1348		}1349	}1350}