git.delta.rocks / unique-network / refs/commits / 68aa24e61486

difftreelog

source

pallets/common/src/lib.rs36.9 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};25use evm_coder::ToLog;26use frame_support::{27	dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},28	ensure,29	traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},30	weights::Pays,31	transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35	COLLECTION_NUMBER_LIMIT,36	Collection,37	RpcCollection,38	CollectionId,39	CreateItemData,40	MAX_TOKEN_PREFIX_LENGTH,41	COLLECTION_ADMINS_LIMIT,42	TokenId,43	TokenChild,44	CollectionStats,45	MAX_TOKEN_OWNERSHIP,46	CollectionMode,47	NFT_SPONSOR_TRANSFER_TIMEOUT,48	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,50	MAX_SPONSOR_TIMEOUT,51	CUSTOM_DATA_LIMIT,52	CollectionLimits,53	CreateCollectionData,54	SponsorshipState,55	CreateItemExData,56	SponsoringRateLimit,57	budget::Budget,58	PhantomType,59	Property,60	Properties,61	PropertiesPermissionMap,62	PropertyKey,63	PropertyValue,64	PropertyPermission,65	PropertiesError,66	PropertyKeyPermission,67	TokenData,68	TrySetProperty,69	PropertyScope,70	// RMRK71	RmrkCollectionInfo,72	RmrkInstanceInfo,73	RmrkResourceInfo,74	RmrkPropertyInfo,75	RmrkBaseInfo,76	RmrkPartType,77	RmrkBoundedTheme,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	}129130	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {131		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)132	}133134	pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {135		self.recorder136			.consume_gas(T::GasWeightMapping::weight_to_gas(137				<T as frame_system::Config>::DbWeight::get()138					.read139					.saturating_mul(reads),140			))141	}142143	pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {144		self.recorder145			.consume_gas(T::GasWeightMapping::weight_to_gas(146				<T as frame_system::Config>::DbWeight::get()147					.write148					.saturating_mul(writes),149			))150	}151	pub fn save(self) -> DispatchResult {152		<CollectionById<T>>::insert(self.id, self.collection);153		Ok(())154	}155156	pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {157		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);158		Ok(())159	}160161	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {162		if self.collection.sponsorship.pending_sponsor() != Some(sender) {163			return Ok(false);164		}165166		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());167		Ok(true)168	}169170	/// Checks that the collection was created with, and must be operated upon through **Unique API**.171	/// Now check only the `external_collection` flag and if it's **true**, then return `CollectionIsExternal` error.172	pub fn check_is_internal(&self) -> DispatchResult {173		if self.external_collection {174			return Err(<Error<T>>::CollectionIsExternal)?;175		}176177		Ok(())178	}179180	/// Checks that the collection was created with, and must be operated upon through an **assimilated API**.181	/// Now check only the `external_collection` flag and if it's **false**, then return `CollectionIsInternal` error.182	pub fn check_is_external(&self) -> DispatchResult {183		if !self.external_collection {184			return Err(<Error<T>>::CollectionIsInternal)?;185		}186187		Ok(())188	}189}190191impl<T: Config> Deref for CollectionHandle<T> {192	type Target = Collection<T::AccountId>;193194	fn deref(&self) -> &Self::Target {195		&self.collection196	}197}198199impl<T: Config> DerefMut for CollectionHandle<T> {200	fn deref_mut(&mut self) -> &mut Self::Target {201		&mut self.collection202	}203}204205impl<T: Config> CollectionHandle<T> {206	pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {207		ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);208		Ok(())209	}210	pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {211		*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))212	}213	pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {214		ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);215		Ok(())216	}217	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {218		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)219	}220	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {221		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)222	}223	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {224		ensure!(225			<Allowlist<T>>::get((self.id, user)),226			<Error<T>>::AddressNotInAllowlist227		);228		Ok(())229	}230}231232#[frame_support::pallet]233pub mod pallet {234	use super::*;235	use pallet_evm::account;236	use dispatch::CollectionDispatch;237	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};238	use frame_system::pallet_prelude::*;239	use frame_support::traits::Currency;240	use up_data_structs::{TokenId, mapping::TokenAddressMapping};241	use scale_info::TypeInfo;242	use weights::WeightInfo;243244	#[pallet::config]245	pub trait Config:246		frame_system::Config247		+ pallet_evm_coder_substrate::Config248		+ pallet_evm::Config249		+ TypeInfo250		+ account::Config251	{252		type WeightInfo: WeightInfo;253		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;254255		type Currency: Currency<Self::AccountId>;256257		#[pallet::constant]258		type CollectionCreationPrice: Get<259			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,260		>;261		type CollectionDispatch: CollectionDispatch<Self>;262263		type TreasuryAccountId: Get<Self::AccountId>;264		type ContractAddress: Get<H160>;265266		type EvmTokenAddressMapping: TokenAddressMapping<H160>;267		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;268	}269270	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);271272	#[pallet::pallet]273	#[pallet::storage_version(STORAGE_VERSION)]274	#[pallet::generate_store(pub(super) trait Store)]275	pub struct Pallet<T>(_);276277	#[pallet::extra_constants]278	impl<T: Config> Pallet<T> {279		pub fn collection_admins_limit() -> u32 {280			COLLECTION_ADMINS_LIMIT281		}282	}283284	#[pallet::event]285	#[pallet::generate_deposit(pub fn deposit_event)]286	pub enum Event<T: Config> {287		/// New collection was created288		///289		/// # Arguments290		///291		/// * collection_id: Globally unique identifier of newly created collection.292		///293		/// * mode: [CollectionMode] converted into u8.294		///295		/// * account_id: Collection owner.296		CollectionCreated(CollectionId, u8, T::AccountId),297298		/// New collection was destroyed299		///300		/// # Arguments301		///302		/// * collection_id: Globally unique identifier of collection.303		CollectionDestroyed(CollectionId),304305		/// New item was created.306		///307		/// # Arguments308		///309		/// * collection_id: Id of the collection where item was created.310		///311		/// * item_id: Id of an item. Unique within the collection.312		///313		/// * recipient: Owner of newly created item314		///315		/// * amount: Always 1 for NFT316		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),317318		/// Collection item was burned.319		///320		/// # Arguments321		///322		/// * collection_id.323		///324		/// * item_id: Identifier of burned NFT.325		///326		/// * owner: which user has destroyed its tokens327		///328		/// * amount: Always 1 for NFT329		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),330331		/// Item was transferred332		///333		/// * collection_id: Id of collection to which item is belong334		///335		/// * item_id: Id of an item336		///337		/// * sender: Original owner of item338		///339		/// * recipient: New owner of item340		///341		/// * amount: Always 1 for NFT342		Transfer(343			CollectionId,344			TokenId,345			T::CrossAccountId,346			T::CrossAccountId,347			u128,348		),349350		/// * collection_id351		///352		/// * item_id353		///354		/// * sender355		///356		/// * spender357		///358		/// * amount359		Approved(360			CollectionId,361			TokenId,362			T::CrossAccountId,363			T::CrossAccountId,364			u128,365		),366367		CollectionPropertySet(CollectionId, PropertyKey),368369		CollectionPropertyDeleted(CollectionId, PropertyKey),370371		TokenPropertySet(CollectionId, TokenId, PropertyKey),372373		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),374375		PropertyPermissionSet(CollectionId, PropertyKey),376	}377378	#[pallet::error]379	pub enum Error<T> {380		/// This collection does not exist.381		CollectionNotFound,382		/// Sender parameter and item owner must be equal.383		MustBeTokenOwner,384		/// No permission to perform action385		NoPermission,386		/// Destroying only empty collections is allowed387		CantDestroyNotEmptyCollection,388		/// Collection is not in mint mode.389		PublicMintingNotAllowed,390		/// Address is not in allow list.391		AddressNotInAllowlist,392393		/// Collection name can not be longer than 63 char.394		CollectionNameLimitExceeded,395		/// Collection description can not be longer than 255 char.396		CollectionDescriptionLimitExceeded,397		/// Token prefix can not be longer than 15 char.398		CollectionTokenPrefixLimitExceeded,399		/// Total collections bound exceeded.400		TotalCollectionsLimitExceeded,401		/// Exceeded max admin count402		CollectionAdminCountExceeded,403		/// Collection limit bounds per collection exceeded404		CollectionLimitBoundsExceeded,405		/// Tried to enable permissions which are only permitted to be disabled406		OwnerPermissionsCantBeReverted,407		/// Collection settings not allowing items transferring408		TransferNotAllowed,409		/// Account token limit exceeded per collection410		AccountTokenLimitExceeded,411		/// Collection token limit exceeded412		CollectionTokenLimitExceeded,413		/// Metadata flag frozen414		MetadataFlagFrozen,415416		/// Item not exists.417		TokenNotFound,418		/// Item balance not enough.419		TokenValueTooLow,420		/// Requested value more than approved.421		ApprovedValueTooLow,422		/// Tried to approve more than owned423		CantApproveMoreThanOwned,424425		/// Can't transfer tokens to ethereum zero address426		AddressIsZero,427		/// Target collection doesn't supports this operation428		UnsupportedOperation,429430		/// Not sufficient funds to perform action431		NotSufficientFounds,432433		/// User not passed nesting rule434		UserIsNotAllowedToNest,435		/// Only tokens from specific collections may nest tokens under this436		SourceCollectionIsNotAllowedToNest,437438		/// Tried to store more data than allowed in collection field439		CollectionFieldSizeExceeded,440441		/// Tried to store more property data than allowed442		NoSpaceForProperty,443444		/// Tried to store more property keys than allowed445		PropertyLimitReached,446447		/// Property key is too long448		PropertyKeyIsTooLong,449450		/// Only ASCII letters, digits, and '_', '-' are allowed451		InvalidCharacterInPropertyKey,452453		/// Empty property keys are forbidden454		EmptyPropertyKey,455456		/// Tried to access an external collection with an internal API457		CollectionIsExternal,458459		/// Tried to access an internal collection with an external API460		CollectionIsInternal,461	}462463	#[pallet::storage]464	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;465	#[pallet::storage]466	pub type DestroyedCollectionCount<T> =467		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;468469	/// Collection info470	#[pallet::storage]471	pub type CollectionById<T> = StorageMap<472		Hasher = Blake2_128Concat,473		Key = CollectionId,474		Value = Collection<<T as frame_system::Config>::AccountId>,475		QueryKind = OptionQuery,476	>;477478	/// Collection properties479	#[pallet::storage]480	#[pallet::getter(fn collection_properties)]481	pub type CollectionProperties<T> = StorageMap<482		Hasher = Blake2_128Concat,483		Key = CollectionId,484		Value = Properties,485		QueryKind = ValueQuery,486		OnEmpty = up_data_structs::CollectionProperties,487	>;488489	#[pallet::storage]490	#[pallet::getter(fn property_permissions)]491	pub type CollectionPropertyPermissions<T> = StorageMap<492		Hasher = Blake2_128Concat,493		Key = CollectionId,494		Value = PropertiesPermissionMap,495		QueryKind = ValueQuery,496	>;497498	#[pallet::storage]499	pub type AdminAmount<T> = StorageMap<500		Hasher = Blake2_128Concat,501		Key = CollectionId,502		Value = u32,503		QueryKind = ValueQuery,504	>;505506	/// List of collection admins507	#[pallet::storage]508	pub type IsAdmin<T: Config> = StorageNMap<509		Key = (510			Key<Blake2_128Concat, CollectionId>,511			Key<Blake2_128Concat, T::CrossAccountId>,512		),513		Value = bool,514		QueryKind = ValueQuery,515	>;516517	/// Allowlisted collection users518	#[pallet::storage]519	pub type Allowlist<T: Config> = StorageNMap<520		Key = (521			Key<Blake2_128Concat, CollectionId>,522			Key<Blake2_128Concat, T::CrossAccountId>,523		),524		Value = bool,525		QueryKind = ValueQuery,526	>;527528	/// Not used by code, exists only to provide some types to metadata529	#[pallet::storage]530	pub type DummyStorageValue<T: Config> = StorageValue<531		Value = (532			CollectionStats,533			CollectionId,534			TokenId,535			TokenChild,536			PhantomType<(537				TokenData<T::CrossAccountId>,538				RpcCollection<T::AccountId>,539				// RMRK540				RmrkCollectionInfo<T::AccountId>,541				RmrkInstanceInfo<T::AccountId>,542				RmrkResourceInfo,543				RmrkPropertyInfo,544				RmrkBaseInfo<T::AccountId>,545				RmrkPartType,546				RmrkBoundedTheme,547				RmrkNftChild,548			)>,549		),550		QueryKind = OptionQuery,551	>;552553	#[pallet::hooks]554	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {555		fn on_runtime_upgrade() -> Weight {556			StorageVersion::new(1).put::<Pallet<T>>();557558			0559		}560	}561}562563impl<T: Config> Pallet<T> {564	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens565	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {566		ensure!(567			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,568			<Error<T>>::AddressIsZero569		);570		Ok(())571	}572	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {573		<IsAdmin<T>>::iter_prefix((collection,))574			.map(|(a, _)| a)575			.collect()576	}577	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {578		<Allowlist<T>>::iter_prefix((collection,))579			.map(|(a, _)| a)580			.collect()581	}582	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {583		<Allowlist<T>>::get((collection, user))584	}585	pub fn collection_stats() -> CollectionStats {586		let created = <CreatedCollectionCount<T>>::get();587		let destroyed = <DestroyedCollectionCount<T>>::get();588		CollectionStats {589			created: created.0,590			destroyed: destroyed.0,591			alive: created.0 - destroyed.0,592		}593	}594595	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {596		let collection = <CollectionById<T>>::get(collection);597		if collection.is_none() {598			return None;599		}600601		let collection = collection.unwrap();602		let limits = collection.limits;603		let effective_limits = CollectionLimits {604			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),605			sponsored_data_size: Some(limits.sponsored_data_size()),606			sponsored_data_rate_limit: Some(607				limits608					.sponsored_data_rate_limit609					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),610			),611			token_limit: Some(limits.token_limit()),612			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(613				match collection.mode {614					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,615					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,616					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,617				},618			)),619			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),620			owner_can_transfer: Some(limits.owner_can_transfer()),621			owner_can_destroy: Some(limits.owner_can_destroy()),622			transfers_enabled: Some(limits.transfers_enabled()),623		};624625		Some(effective_limits)626	}627628	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {629		let Collection {630			name,631			description,632			owner,633			mode,634			token_prefix,635			sponsorship,636			limits,637			permissions,638			external_collection,639		} = <CollectionById<T>>::get(collection)?;640641		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)642			.into_iter()643			.map(|(key, permission)| PropertyKeyPermission { key, permission })644			.collect();645646		let properties = <CollectionProperties<T>>::get(collection)647			.into_iter()648			.map(|(key, value)| Property { key, value })649			.collect();650651		let permissions = CollectionPermissions {652			access: Some(permissions.access()),653			mint_mode: Some(permissions.mint_mode()),654			nesting: Some(permissions.nesting().clone()),655		};656657		Some(RpcCollection {658			name: name.into_inner(),659			description: description.into_inner(),660			owner,661			mode,662			token_prefix: token_prefix.into_inner(),663			sponsorship,664			limits,665			permissions,666			token_property_permissions,667			properties,668			read_only: external_collection,669		})670	}671}672673macro_rules! limit_default {674	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{675		$(676			if let Some($new) = $new.$field {677				let $old = $old.$field($($arg)?);678				let _ = $new;679				let _ = $old;680				$check681			} else {682				$new.$field = $old.$field683			}684		)*685	}};686}687macro_rules! limit_default_clone {688	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{689		$(690			if let Some($new) = $new.$field.clone() {691				let $old = $old.$field($($arg)?);692				let _ = $new;693				let _ = $old;694				$check695			} else {696				$new.$field = $old.$field.clone()697			}698		)*699	}};700}701702impl<T: Config> Pallet<T> {703	pub fn init_collection(704		owner: T::CrossAccountId,705		data: CreateCollectionData<T::AccountId>,706		is_external: bool,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.as_sub().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| {747					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)748				})749				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,750			external_collection: is_external,751		};752753		let mut collection_properties = up_data_structs::CollectionProperties::get();754		collection_properties755			.try_set_from_iter(data.properties.into_iter())756			.map_err(<Error<T>>::from)?;757758		CollectionProperties::<T>::insert(id, collection_properties);759760		let mut token_props_permissions = PropertiesPermissionMap::new();761		token_props_permissions762			.try_set_from_iter(data.token_property_permissions.into_iter())763			.map_err(<Error<T>>::from)?;764765		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);766767		// Take a (non-refundable) deposit of collection creation768		{769			let mut imbalance =770				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();771			imbalance.subsume(772				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(773					&T::TreasuryAccountId::get(),774					T::CollectionCreationPrice::get(),775				),776			);777			<T as Config>::Currency::settle(778				&owner.as_sub(),779				imbalance,780				WithdrawReasons::TRANSFER,781				ExistenceRequirement::KeepAlive,782			)783			.map_err(|_| Error::<T>::NotSufficientFounds)?;784		}785786		<CreatedCollectionCount<T>>::put(created_count);787		<Pallet<T>>::deposit_event(Event::CollectionCreated(788			id,789			data.mode.id(),790			owner.as_sub().clone(),791		));792		<PalletEvm<T>>::deposit_log(793			erc::CollectionHelpersEvents::CollectionCreated {794				owner: *owner.as_eth(),795				collection_id: eth::collection_id_to_address(id),796			}797			.to_log(T::ContractAddress::get()),798		);799		<CollectionById<T>>::insert(id, collection);800		Ok(id)801	}802803	pub fn destroy_collection(804		collection: CollectionHandle<T>,805		sender: &T::CrossAccountId,806	) -> DispatchResult {807		ensure!(808			collection.limits.owner_can_destroy(),809			<Error<T>>::NoPermission,810		);811		collection.check_is_owner(sender)?;812813		let destroyed_collections = <DestroyedCollectionCount<T>>::get()814			.0815			.checked_add(1)816			.ok_or(ArithmeticError::Overflow)?;817818		// =========819820		<DestroyedCollectionCount<T>>::put(destroyed_collections);821		<CollectionById<T>>::remove(collection.id);822		<AdminAmount<T>>::remove(collection.id);823		<IsAdmin<T>>::remove_prefix((collection.id,), None);824		<Allowlist<T>>::remove_prefix((collection.id,), None);825		<CollectionProperties<T>>::remove(collection.id);826827		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));828		Ok(())829	}830831	pub fn set_collection_property(832		collection: &CollectionHandle<T>,833		sender: &T::CrossAccountId,834		property: Property,835	) -> DispatchResult {836		collection.check_is_owner_or_admin(sender)?;837838		CollectionProperties::<T>::try_mutate(collection.id, |properties| {839			let property = property.clone();840			properties.try_set(property.key, property.value)841		})842		.map_err(<Error<T>>::from)?;843844		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));845846		Ok(())847	}848849	pub fn set_scoped_collection_property(850		collection_id: CollectionId,851		scope: PropertyScope,852		property: Property,853	) -> DispatchResult {854		CollectionProperties::<T>::try_mutate(collection_id, |properties| {855			properties.try_scoped_set(scope, property.key, property.value)856		})857		.map_err(<Error<T>>::from)?;858859		Ok(())860	}861862	pub fn set_scoped_collection_properties(863		collection_id: CollectionId,864		scope: PropertyScope,865		properties: impl Iterator<Item = Property>,866	) -> DispatchResult {867		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {868			stored_properties.try_scoped_set_from_iter(scope, properties)869		})870		.map_err(<Error<T>>::from)?;871872		Ok(())873	}874875	#[transactional]876	pub fn set_collection_properties(877		collection: &CollectionHandle<T>,878		sender: &T::CrossAccountId,879		properties: Vec<Property>,880	) -> DispatchResult {881		for property in properties {882			Self::set_collection_property(collection, sender, property)?;883		}884885		Ok(())886	}887888	pub fn delete_collection_property(889		collection: &CollectionHandle<T>,890		sender: &T::CrossAccountId,891		property_key: PropertyKey,892	) -> DispatchResult {893		collection.check_is_owner_or_admin(sender)?;894895		CollectionProperties::<T>::try_mutate(collection.id, |properties| {896			properties.remove(&property_key)897		})898		.map_err(<Error<T>>::from)?;899900		Self::deposit_event(Event::CollectionPropertyDeleted(901			collection.id,902			property_key,903		));904905		Ok(())906	}907908	#[transactional]909	pub fn delete_collection_properties(910		collection: &CollectionHandle<T>,911		sender: &T::CrossAccountId,912		property_keys: Vec<PropertyKey>,913	) -> DispatchResult {914		for key in property_keys {915			Self::delete_collection_property(collection, sender, key)?;916		}917918		Ok(())919	}920921	// For migrations922	pub fn set_property_permission_unchecked(923		collection: CollectionId,924		property_permission: PropertyKeyPermission,925	) -> DispatchResult {926		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {927			permissions.try_set(property_permission.key, property_permission.permission)928		})929		.map_err(<Error<T>>::from)?;930		Ok(())931	}932933	pub fn set_property_permission(934		collection: &CollectionHandle<T>,935		sender: &T::CrossAccountId,936		property_permission: PropertyKeyPermission,937	) -> DispatchResult {938		collection.check_is_owner_or_admin(sender)?;939940		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);941		let current_permission = all_permissions.get(&property_permission.key);942		if matches![943			current_permission,944			Some(PropertyPermission { mutable: false, .. })945		] {946			return Err(<Error<T>>::NoPermission.into());947		}948949		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {950			let property_permission = property_permission.clone();951			permissions.try_set(property_permission.key, property_permission.permission)952		})953		.map_err(<Error<T>>::from)?;954955		Self::deposit_event(Event::PropertyPermissionSet(956			collection.id,957			property_permission.key,958		));959960		Ok(())961	}962963	#[transactional]964	pub fn set_token_property_permissions(965		collection: &CollectionHandle<T>,966		sender: &T::CrossAccountId,967		property_permissions: Vec<PropertyKeyPermission>,968	) -> DispatchResult {969		for prop_pemission in property_permissions {970			Self::set_property_permission(collection, sender, prop_pemission)?;971		}972973		Ok(())974	}975976	pub fn get_collection_property(977		collection_id: CollectionId,978		key: &PropertyKey,979	) -> Option<PropertyValue> {980		Self::collection_properties(collection_id).get(key).cloned()981	}982983	pub fn bytes_keys_to_property_keys(984		keys: Vec<Vec<u8>>,985	) -> Result<Vec<PropertyKey>, DispatchError> {986		keys.into_iter()987			.map(|key| -> Result<PropertyKey, DispatchError> {988				key.try_into()989					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())990			})991			.collect::<Result<Vec<PropertyKey>, DispatchError>>()992	}993994	pub fn filter_collection_properties(995		collection_id: CollectionId,996		keys: Option<Vec<PropertyKey>>,997	) -> Result<Vec<Property>, DispatchError> {998		let properties = Self::collection_properties(collection_id);9991000		let properties = keys1001			.map(|keys| {1002				keys.into_iter()1003					.filter_map(|key| {1004						properties.get(&key).map(|value| Property {1005							key,1006							value: value.clone(),1007						})1008					})1009					.collect()1010			})1011			.unwrap_or_else(|| {1012				properties1013					.into_iter()1014					.map(|(key, value)| Property { key, value })1015					.collect()1016			});10171018		Ok(properties)1019	}10201021	pub fn filter_property_permissions(1022		collection_id: CollectionId,1023		keys: Option<Vec<PropertyKey>>,1024	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1025		let permissions = Self::property_permissions(collection_id);10261027		let key_permissions = keys1028			.map(|keys| {1029				keys.into_iter()1030					.filter_map(|key| {1031						permissions1032							.get(&key)1033							.map(|permission| PropertyKeyPermission {1034								key,1035								permission: permission.clone(),1036							})1037					})1038					.collect()1039			})1040			.unwrap_or_else(|| {1041				permissions1042					.into_iter()1043					.map(|(key, permission)| PropertyKeyPermission { key, permission })1044					.collect()1045			});10461047		Ok(key_permissions)1048	}10491050	pub fn toggle_allowlist(1051		collection: &CollectionHandle<T>,1052		sender: &T::CrossAccountId,1053		user: &T::CrossAccountId,1054		allowed: bool,1055	) -> DispatchResult {1056		collection.check_is_owner_or_admin(sender)?;10571058		// =========10591060		if allowed {1061			<Allowlist<T>>::insert((collection.id, user), true);1062		} else {1063			<Allowlist<T>>::remove((collection.id, user));1064		}10651066		Ok(())1067	}10681069	pub fn toggle_admin(1070		collection: &CollectionHandle<T>,1071		sender: &T::CrossAccountId,1072		user: &T::CrossAccountId,1073		admin: bool,1074	) -> DispatchResult {1075		collection.check_is_owner(sender)?;10761077		let was_admin = <IsAdmin<T>>::get((collection.id, user));1078		if was_admin == admin {1079			return Ok(());1080		}1081		let amount = <AdminAmount<T>>::get(collection.id);10821083		if admin {1084			let amount = amount1085				.checked_add(1)1086				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1087			ensure!(1088				amount <= Self::collection_admins_limit(),1089				<Error<T>>::CollectionAdminCountExceeded,1090			);10911092			// =========10931094			<AdminAmount<T>>::insert(collection.id, amount);1095			<IsAdmin<T>>::insert((collection.id, user), true);1096		} else {1097			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1098			<IsAdmin<T>>::remove((collection.id, user));1099		}11001101		Ok(())1102	}11031104	pub fn clamp_limits(1105		mode: CollectionMode,1106		old_limit: &CollectionLimits,1107		mut new_limit: CollectionLimits,1108	) -> Result<CollectionLimits, DispatchError> {1109		let limits = old_limit;1110		limit_default!(old_limit, new_limit,1111			account_token_ownership_limit => ensure!(1112				new_limit <= MAX_TOKEN_OWNERSHIP,1113				<Error<T>>::CollectionLimitBoundsExceeded,1114			),1115			sponsored_data_size => ensure!(1116				new_limit <= CUSTOM_DATA_LIMIT,1117				<Error<T>>::CollectionLimitBoundsExceeded,1118			),11191120			sponsored_data_rate_limit => {},1121			token_limit => ensure!(1122				old_limit >= new_limit && new_limit > 0,1123				<Error<T>>::CollectionTokenLimitExceeded1124			),11251126			sponsor_transfer_timeout(match mode {1127				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1128				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1129				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1130			}) => ensure!(1131				new_limit <= MAX_SPONSOR_TIMEOUT,1132				<Error<T>>::CollectionLimitBoundsExceeded,1133			),1134			sponsor_approve_timeout => {},1135			owner_can_transfer => ensure!(1136				!limits.owner_can_transfer_instaled() ||1137				old_limit || !new_limit,1138				<Error<T>>::OwnerPermissionsCantBeReverted,1139			),1140			owner_can_destroy => ensure!(1141				old_limit || !new_limit,1142				<Error<T>>::OwnerPermissionsCantBeReverted,1143			),1144			transfers_enabled => {},1145		);1146		Ok(new_limit)1147	}11481149	pub fn clamp_permissions(1150		_mode: CollectionMode,1151		old_limit: &CollectionPermissions,1152		mut new_limit: CollectionPermissions,1153	) -> Result<CollectionPermissions, DispatchError> {1154		limit_default_clone!(old_limit, new_limit,1155			access => {},1156			mint_mode => {},1157			nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1158		);1159		Ok(new_limit)1160	}1161}11621163#[macro_export]1164macro_rules! unsupported {1165	() => {1166		Err(<Error<T>>::UnsupportedOperation.into())1167	};1168}11691170/// Worst cases1171pub trait CommonWeightInfo<CrossAccountId> {1172	fn create_item() -> Weight;1173	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1174	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1175	fn burn_item() -> Weight;1176	fn set_collection_properties(amount: u32) -> Weight;1177	fn delete_collection_properties(amount: u32) -> Weight;1178	fn set_token_properties(amount: u32) -> Weight;1179	fn delete_token_properties(amount: u32) -> Weight;1180	fn set_token_property_permissions(amount: u32) -> Weight;1181	fn transfer() -> Weight;1182	fn approve() -> Weight;1183	fn transfer_from() -> Weight;1184	fn burn_from() -> Weight;11851186	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1187	/// whole users's balance1188	///1189	/// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead1190	fn burn_recursively_self_raw() -> Weight;1191	/// Cost of iterating over `amount` children while burning, without counting child burning itself1192	///1193	/// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead1194	fn burn_recursively_breadth_raw(amount: u32) -> Weight;11951196	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1197		Self::burn_recursively_self_raw()1198			.saturating_mul(max_selfs.max(1) as u64)1199			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1200	}1201}12021203pub trait RefungibleExtensionsWeightInfo {1204	fn repartition() -> 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 burn_item_recursively(1235		&self,1236		sender: T::CrossAccountId,1237		token: TokenId,1238		self_budget: &dyn Budget,1239		breadth_budget: &dyn Budget,1240	) -> DispatchResultWithPostInfo;1241	fn set_collection_properties(1242		&self,1243		sender: T::CrossAccountId,1244		properties: Vec<Property>,1245	) -> DispatchResultWithPostInfo;1246	fn delete_collection_properties(1247		&self,1248		sender: &T::CrossAccountId,1249		property_keys: Vec<PropertyKey>,1250	) -> DispatchResultWithPostInfo;1251	fn set_token_properties(1252		&self,1253		sender: T::CrossAccountId,1254		token_id: TokenId,1255		property: Vec<Property>,1256		nesting_budget: &dyn Budget,1257	) -> DispatchResultWithPostInfo;1258	fn delete_token_properties(1259		&self,1260		sender: T::CrossAccountId,1261		token_id: TokenId,1262		property_keys: Vec<PropertyKey>,1263		nesting_budget: &dyn Budget,1264	) -> DispatchResultWithPostInfo;1265	fn set_token_property_permissions(1266		&self,1267		sender: &T::CrossAccountId,1268		property_permissions: Vec<PropertyKeyPermission>,1269	) -> DispatchResultWithPostInfo;1270	fn transfer(1271		&self,1272		sender: T::CrossAccountId,1273		to: T::CrossAccountId,1274		token: TokenId,1275		amount: u128,1276		nesting_budget: &dyn Budget,1277	) -> DispatchResultWithPostInfo;1278	fn approve(1279		&self,1280		sender: T::CrossAccountId,1281		spender: T::CrossAccountId,1282		token: TokenId,1283		amount: u128,1284	) -> DispatchResultWithPostInfo;1285	fn transfer_from(1286		&self,1287		sender: T::CrossAccountId,1288		from: T::CrossAccountId,1289		to: T::CrossAccountId,1290		token: TokenId,1291		amount: u128,1292		nesting_budget: &dyn Budget,1293	) -> DispatchResultWithPostInfo;1294	fn burn_from(1295		&self,1296		sender: T::CrossAccountId,1297		from: T::CrossAccountId,1298		token: TokenId,1299		amount: u128,1300		nesting_budget: &dyn Budget,1301	) -> DispatchResultWithPostInfo;13021303	fn check_nesting(1304		&self,1305		sender: T::CrossAccountId,1306		from: (CollectionId, TokenId),1307		under: TokenId,1308		nesting_budget: &dyn Budget,1309	) -> DispatchResult;13101311	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13121313	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13141315	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1316	fn collection_tokens(&self) -> Vec<TokenId>;1317	fn token_exists(&self, token: TokenId) -> bool;1318	fn last_token_id(&self) -> TokenId;13191320	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1321	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1322	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1323	/// Amount of unique collection tokens1324	fn total_supply(&self) -> u32;1325	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1326	fn account_balance(&self, account: T::CrossAccountId) -> u32;1327	/// Amount of specific token account have (Applicable to fungible/refungible)1328	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1329	fn allowance(1330		&self,1331		sender: T::CrossAccountId,1332		spender: T::CrossAccountId,1333		token: TokenId,1334	) -> u128;1335	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1336}13371338pub trait RefungibleExtensions<T>1339where1340	T: Config,1341{1342	fn repartition(1343		&self,1344		owner: &T::CrossAccountId,1345		token: TokenId,1346		amount: u128,1347	) -> DispatchResultWithPostInfo;1348}13491350// Flexible enough for implementing CommonCollectionOperations1351pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1352	let post_info = PostDispatchInfo {1353		actual_weight: Some(weight),1354		pays_fee: Pays::Yes,1355	};1356	match res {1357		Ok(()) => Ok(post_info),1358		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1359	}1360}13611362impl<T: Config> From<PropertiesError> for Error<T> {1363	fn from(error: PropertiesError) -> Self {1364		match error {1365			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1366			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1367			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1368			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1369			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1370		}1371	}1372}