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

difftreelog

source

pallets/common/src/lib.rs40.4 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		/// * mode - [CollectionMode] converted into u8.293		/// * account_id - Collection owner.294		CollectionCreated(CollectionId, u8, T::AccountId),295296		/// New collection was destroyed297		///298		/// # Arguments299		///300		/// * collection_id - Globally unique identifier of collection that has been destroyed.301		CollectionDestroyed(CollectionId),302303		/// New item was created.304		///305		/// # Arguments306		///307		/// * collection_id - ID of the collection where the item was created.308		/// * item_id - ID of the item. Unique within the collection.309		/// * recipient - Owner of the newly created item.310		/// * amount - The amount of tokens that were created (always 1 for NFT).311		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),312313		/// Collection item was burned.314		///315		/// # Arguments316		///317		/// * collection_id - Identifier of the collection to which the burned NFT belonged.318		/// * item_id - Identifier of burned NFT.319		/// * owner - Which user has destroyed their tokens.320		/// * amount - Amount of tokens that were destroyed (always 1 for NFT).321		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),322323		/// Item was transferred.324		///325		/// # Arguments326		///327		/// * collection_id - ID of the collection to which the item belongs.328		/// * item_id - ID of the item transferred.329		/// * sender - Original owner of the item.330		/// * recipient - New owner of the item.331		/// * amount - Amount of tokens that were transferred (always 1 for NFT).332		Transfer(333			CollectionId,334			TokenId,335			T::CrossAccountId,336			T::CrossAccountId,337			u128,338		),339340		/// Sponsoring allowance was approved.341		///342		/// # Arguments343		///344		/// * collection_id - todo:doc flesh out345		/// * item_id346		/// * sender347		/// * spender348		/// * amount349		Approved(350			CollectionId,351			TokenId,352			T::CrossAccountId,353			T::CrossAccountId,354			u128,355		),356357		/// Collection property was added or edited.358		///359		/// # Arguments360		///361		/// * collection_id - ID of the collection, whose property was just set.362		/// * property_key - Key of the property that was just set.363		CollectionPropertySet(CollectionId, PropertyKey),364365		/// Collection property was deleted.366		///367		/// # Arguments368		///369		/// * collection_id - ID of the collection, whose property was just deleted.370		/// * property_key - Key of the property that was just deleted.371		CollectionPropertyDeleted(CollectionId, PropertyKey),372373		/// Item property was added or edited.374		///375		/// # Arguments376		///377		/// * collection_id - ID of the collection, whose token's property was just set.378		/// * item_id - ID of the item, whose property was just set.379		/// * property_key - Key of the property that was just set.380		TokenPropertySet(CollectionId, TokenId, PropertyKey),381382		/// Item property was deleted.383		///384		/// # Arguments385		///386		/// * collection_id - ID of the collection, whose token's property was just deleted.387		/// * item_id - ID of the item, whose property was just deleted.388		/// * property_key - Key of the property that was just deleted.389		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),390391		/// Token property permission was added or updated for a collection.392		///393		/// # Arguments394		///395		/// * collection_id - ID of the collection, whose permissions were just set/updated.396		/// * property_key - Key of the property of the set/updated permission.397		PropertyPermissionSet(CollectionId, PropertyKey),398	}399400	#[pallet::error]401	pub enum Error<T> {402		/// This collection does not exist.403		CollectionNotFound,404		/// Sender parameter and item owner must be equal.405		MustBeTokenOwner,406		/// No permission to perform action407		NoPermission,408		/// Destroying only empty collections is allowed409		CantDestroyNotEmptyCollection,410		/// Collection is not in mint mode.411		PublicMintingNotAllowed,412		/// Address is not in allow list.413		AddressNotInAllowlist,414415		/// Collection name can not be longer than 63 char.416		CollectionNameLimitExceeded,417		/// Collection description can not be longer than 255 char.418		CollectionDescriptionLimitExceeded,419		/// Token prefix can not be longer than 15 char.420		CollectionTokenPrefixLimitExceeded,421		/// Total collections bound exceeded.422		TotalCollectionsLimitExceeded,423		/// Exceeded max admin count424		CollectionAdminCountExceeded,425		/// Collection limit bounds per collection exceeded426		CollectionLimitBoundsExceeded,427		/// Tried to enable permissions which are only permitted to be disabled428		OwnerPermissionsCantBeReverted,429		/// Collection settings not allowing items transferring430		TransferNotAllowed,431		/// Account token limit exceeded per collection432		AccountTokenLimitExceeded,433		/// Collection token limit exceeded434		CollectionTokenLimitExceeded,435		/// Metadata flag frozen436		MetadataFlagFrozen,437438		/// Item does not exist439		TokenNotFound,440		/// Item is balance not enough441		TokenValueTooLow,442		/// Requested value is more than the approved443		ApprovedValueTooLow,444		/// Tried to approve more than owned445		CantApproveMoreThanOwned,446447		/// Can't transfer tokens to ethereum zero address448		AddressIsZero,449		/// Target collection doesn't support this operation450		UnsupportedOperation,451452		/// Insufficient funds to perform an action453		NotSufficientFounds,454455		/// User does not satisfy the nesting rule456		UserIsNotAllowedToNest,457		/// Only tokens from specific collections may nest tokens under this one458		SourceCollectionIsNotAllowedToNest,459460		/// Tried to store more data than allowed in collection field461		CollectionFieldSizeExceeded,462463		/// Tried to store more property data than allowed464		NoSpaceForProperty,465466		/// Tried to store more property keys than allowed467		PropertyLimitReached,468469		/// Property key is too long470		PropertyKeyIsTooLong,471472		/// Only ASCII letters, digits, and symbols '_', '-', and '.' are allowed473		InvalidCharacterInPropertyKey,474475		/// Empty property keys are forbidden476		EmptyPropertyKey,477478		/// Tried to access an external collection with an internal API479		CollectionIsExternal,480481		/// Tried to access an internal collection with an external API482		CollectionIsInternal,483	}484485	/// The number of created collections. Essentially contains the last collection ID.486	#[pallet::storage]487	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;488489	/// The number of destroyed collections490	#[pallet::storage]491	pub type DestroyedCollectionCount<T> =492		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;493494	/// Collection info495	#[pallet::storage]496	pub type CollectionById<T> = StorageMap<497		Hasher = Blake2_128Concat,498		Key = CollectionId,499		Value = Collection<<T as frame_system::Config>::AccountId>,500		QueryKind = OptionQuery,501	>;502503	/// Collection properties504	#[pallet::storage]505	#[pallet::getter(fn collection_properties)]506	pub type CollectionProperties<T> = StorageMap<507		Hasher = Blake2_128Concat,508		Key = CollectionId,509		Value = Properties,510		QueryKind = ValueQuery,511		OnEmpty = up_data_structs::CollectionProperties,512	>;513514	/// Token permissions of a collection515	#[pallet::storage]516	#[pallet::getter(fn property_permissions)]517	pub type CollectionPropertyPermissions<T> = StorageMap<518		Hasher = Blake2_128Concat,519		Key = CollectionId,520		Value = PropertiesPermissionMap,521		QueryKind = ValueQuery,522	>;523524	/// Amount of collection admins525	#[pallet::storage]526	pub type AdminAmount<T> = StorageMap<527		Hasher = Blake2_128Concat,528		Key = CollectionId,529		Value = u32,530		QueryKind = ValueQuery,531	>;532533	/// List of collection admins534	#[pallet::storage]535	pub type IsAdmin<T: Config> = StorageNMap<536		Key = (537			Key<Blake2_128Concat, CollectionId>,538			Key<Blake2_128Concat, T::CrossAccountId>,539		),540		Value = bool,541		QueryKind = ValueQuery,542	>;543544	/// Allowlisted collection users545	#[pallet::storage]546	pub type Allowlist<T: Config> = StorageNMap<547		Key = (548			Key<Blake2_128Concat, CollectionId>,549			Key<Blake2_128Concat, T::CrossAccountId>,550		),551		Value = bool,552		QueryKind = ValueQuery,553	>;554555	/// Not used by code, exists only to provide some types to metadata556	#[pallet::storage]557	pub type DummyStorageValue<T: Config> = StorageValue<558		Value = (559			CollectionStats,560			CollectionId,561			TokenId,562			TokenChild,563			PhantomType<(564				TokenData<T::CrossAccountId>,565				RpcCollection<T::AccountId>,566				// RMRK567				RmrkCollectionInfo<T::AccountId>,568				RmrkInstanceInfo<T::AccountId>,569				RmrkResourceInfo,570				RmrkPropertyInfo,571				RmrkBaseInfo<T::AccountId>,572				RmrkPartType,573				RmrkBoundedTheme,574				RmrkNftChild,575			)>,576		),577		QueryKind = OptionQuery,578	>;579580	#[pallet::hooks]581	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {582		fn on_runtime_upgrade() -> Weight {583			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {584				use up_data_structs::{CollectionVersion1, CollectionVersion2};585				<CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {586					let mut props = Vec::new();587					if !v.offchain_schema.is_empty() {588						props.push(Property {589							key: b"_old_offchainSchema".to_vec().try_into().unwrap(),590							value: v591								.offchain_schema592								.clone()593								.into_inner()594								.try_into()595								.expect("offchain schema too big"),596						});597					}598					if !v.variable_on_chain_schema.is_empty() {599						props.push(Property {600							key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),601							value: v602								.variable_on_chain_schema603								.clone()604								.into_inner()605								.try_into()606								.expect("offchain schema too big"),607						});608					}609					if !v.const_on_chain_schema.is_empty() {610						props.push(Property {611							key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),612							value: v613								.const_on_chain_schema614								.clone()615								.into_inner()616								.try_into()617								.expect("offchain schema too big"),618						});619					}620					props.push(Property {621						key: b"_old_schemaVersion".to_vec().try_into().unwrap(),622						value: match v.schema_version {623							SchemaVersion::ImageURL => b"ImageUrl".as_slice(),624							SchemaVersion::Unique => b"Unique".as_slice(),625						}626						.to_vec()627						.try_into()628						.unwrap(),629					});630					Self::set_scoped_collection_properties(631						id,632						PropertyScope::None,633						props.into_iter(),634					)635					.expect("existing data larger than properties");636					let mut new = CollectionVersion2::from(v.clone());637					new.permissions.access = Some(v.access);638					new.permissions.mint_mode = Some(v.mint_mode);639					Some(new)640				});641			}642643			0644		}645	}646}647648impl<T: Config> Pallet<T> {649	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens650	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {651		ensure!(652			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,653			<Error<T>>::AddressIsZero654		);655		Ok(())656	}657	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {658		<IsAdmin<T>>::iter_prefix((collection,))659			.map(|(a, _)| a)660			.collect()661	}662	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {663		<Allowlist<T>>::iter_prefix((collection,))664			.map(|(a, _)| a)665			.collect()666	}667	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {668		<Allowlist<T>>::get((collection, user))669	}670	pub fn collection_stats() -> CollectionStats {671		let created = <CreatedCollectionCount<T>>::get();672		let destroyed = <DestroyedCollectionCount<T>>::get();673		CollectionStats {674			created: created.0,675			destroyed: destroyed.0,676			alive: created.0 - destroyed.0,677		}678	}679680	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {681		let collection = <CollectionById<T>>::get(collection);682		if collection.is_none() {683			return None;684		}685686		let collection = collection.unwrap();687		let limits = collection.limits;688		let effective_limits = CollectionLimits {689			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),690			sponsored_data_size: Some(limits.sponsored_data_size()),691			sponsored_data_rate_limit: Some(692				limits693					.sponsored_data_rate_limit694					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),695			),696			token_limit: Some(limits.token_limit()),697			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(698				match collection.mode {699					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,700					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,701					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,702				},703			)),704			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),705			owner_can_transfer: Some(limits.owner_can_transfer()),706			owner_can_destroy: Some(limits.owner_can_destroy()),707			transfers_enabled: Some(limits.transfers_enabled()),708		};709710		Some(effective_limits)711	}712713	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {714		let Collection {715			name,716			description,717			owner,718			mode,719			token_prefix,720			sponsorship,721			limits,722			permissions,723			external_collection,724		} = <CollectionById<T>>::get(collection)?;725726		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)727			.into_iter()728			.map(|(key, permission)| PropertyKeyPermission { key, permission })729			.collect();730731		let properties = <CollectionProperties<T>>::get(collection)732			.into_iter()733			.map(|(key, value)| Property { key, value })734			.collect();735736		let permissions = CollectionPermissions {737			access: Some(permissions.access()),738			mint_mode: Some(permissions.mint_mode()),739			nesting: Some(permissions.nesting().clone()),740		};741742		Some(RpcCollection {743			name: name.into_inner(),744			description: description.into_inner(),745			owner,746			mode,747			token_prefix: token_prefix.into_inner(),748			sponsorship,749			limits,750			permissions,751			token_property_permissions,752			properties,753			read_only: external_collection,754		})755	}756}757758macro_rules! limit_default {759	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{760		$(761			if let Some($new) = $new.$field {762				let $old = $old.$field($($arg)?);763				let _ = $new;764				let _ = $old;765				$check766			} else {767				$new.$field = $old.$field768			}769		)*770	}};771}772macro_rules! limit_default_clone {773	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{774		$(775			if let Some($new) = $new.$field.clone() {776				let $old = $old.$field($($arg)?);777				let _ = $new;778				let _ = $old;779				$check780			} else {781				$new.$field = $old.$field.clone()782			}783		)*784	}};785}786787impl<T: Config> Pallet<T> {788	pub fn init_collection(789		owner: T::CrossAccountId,790		data: CreateCollectionData<T::AccountId>,791		is_external: bool,792	) -> Result<CollectionId, DispatchError> {793		{794			ensure!(795				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,796				Error::<T>::CollectionTokenPrefixLimitExceeded797			);798		}799800		let created_count = <CreatedCollectionCount<T>>::get()801			.0802			.checked_add(1)803			.ok_or(ArithmeticError::Overflow)?;804		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;805		let id = CollectionId(created_count);806807		// bound Total number of collections808		ensure!(809			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,810			<Error<T>>::TotalCollectionsLimitExceeded811		);812813		// =========814815		let collection = Collection {816			owner: owner.as_sub().clone(),817			name: data.name,818			mode: data.mode.clone(),819			description: data.description,820			token_prefix: data.token_prefix,821			sponsorship: data822				.pending_sponsor823				.map(SponsorshipState::Unconfirmed)824				.unwrap_or_default(),825			limits: data826				.limits827				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))828				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,829			permissions: data830				.permissions831				.map(|permissions| {832					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)833				})834				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,835			external_collection: is_external,836		};837838		let mut collection_properties = up_data_structs::CollectionProperties::get();839		collection_properties840			.try_set_from_iter(data.properties.into_iter())841			.map_err(<Error<T>>::from)?;842843		CollectionProperties::<T>::insert(id, collection_properties);844845		let mut token_props_permissions = PropertiesPermissionMap::new();846		token_props_permissions847			.try_set_from_iter(data.token_property_permissions.into_iter())848			.map_err(<Error<T>>::from)?;849850		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);851852		// Take a (non-refundable) deposit of collection creation853		{854			let mut imbalance =855				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();856			imbalance.subsume(857				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(858					&T::TreasuryAccountId::get(),859					T::CollectionCreationPrice::get(),860				),861			);862			<T as Config>::Currency::settle(863				&owner.as_sub(),864				imbalance,865				WithdrawReasons::TRANSFER,866				ExistenceRequirement::KeepAlive,867			)868			.map_err(|_| Error::<T>::NotSufficientFounds)?;869		}870871		<CreatedCollectionCount<T>>::put(created_count);872		<Pallet<T>>::deposit_event(Event::CollectionCreated(873			id,874			data.mode.id(),875			owner.as_sub().clone(),876		));877		<PalletEvm<T>>::deposit_log(878			erc::CollectionHelpersEvents::CollectionCreated {879				owner: *owner.as_eth(),880				collection_id: eth::collection_id_to_address(id),881			}882			.to_log(T::ContractAddress::get()),883		);884		<CollectionById<T>>::insert(id, collection);885		Ok(id)886	}887888	pub fn destroy_collection(889		collection: CollectionHandle<T>,890		sender: &T::CrossAccountId,891	) -> DispatchResult {892		ensure!(893			collection.limits.owner_can_destroy(),894			<Error<T>>::NoPermission,895		);896		collection.check_is_owner(sender)?;897898		let destroyed_collections = <DestroyedCollectionCount<T>>::get()899			.0900			.checked_add(1)901			.ok_or(ArithmeticError::Overflow)?;902903		// =========904905		<DestroyedCollectionCount<T>>::put(destroyed_collections);906		<CollectionById<T>>::remove(collection.id);907		<AdminAmount<T>>::remove(collection.id);908		<IsAdmin<T>>::remove_prefix((collection.id,), None);909		<Allowlist<T>>::remove_prefix((collection.id,), None);910		<CollectionProperties<T>>::remove(collection.id);911912		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));913		Ok(())914	}915916	pub fn set_collection_property(917		collection: &CollectionHandle<T>,918		sender: &T::CrossAccountId,919		property: Property,920	) -> DispatchResult {921		collection.check_is_owner_or_admin(sender)?;922923		CollectionProperties::<T>::try_mutate(collection.id, |properties| {924			let property = property.clone();925			properties.try_set(property.key, property.value)926		})927		.map_err(<Error<T>>::from)?;928929		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));930931		Ok(())932	}933934	pub fn set_scoped_collection_property(935		collection_id: CollectionId,936		scope: PropertyScope,937		property: Property,938	) -> DispatchResult {939		CollectionProperties::<T>::try_mutate(collection_id, |properties| {940			properties.try_scoped_set(scope, property.key, property.value)941		})942		.map_err(<Error<T>>::from)?;943944		Ok(())945	}946947	pub fn set_scoped_collection_properties(948		collection_id: CollectionId,949		scope: PropertyScope,950		properties: impl Iterator<Item = Property>,951	) -> DispatchResult {952		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {953			stored_properties.try_scoped_set_from_iter(scope, properties)954		})955		.map_err(<Error<T>>::from)?;956957		Ok(())958	}959960	#[transactional]961	pub fn set_collection_properties(962		collection: &CollectionHandle<T>,963		sender: &T::CrossAccountId,964		properties: Vec<Property>,965	) -> DispatchResult {966		for property in properties {967			Self::set_collection_property(collection, sender, property)?;968		}969970		Ok(())971	}972973	pub fn delete_collection_property(974		collection: &CollectionHandle<T>,975		sender: &T::CrossAccountId,976		property_key: PropertyKey,977	) -> DispatchResult {978		collection.check_is_owner_or_admin(sender)?;979980		CollectionProperties::<T>::try_mutate(collection.id, |properties| {981			properties.remove(&property_key)982		})983		.map_err(<Error<T>>::from)?;984985		Self::deposit_event(Event::CollectionPropertyDeleted(986			collection.id,987			property_key,988		));989990		Ok(())991	}992993	#[transactional]994	pub fn delete_collection_properties(995		collection: &CollectionHandle<T>,996		sender: &T::CrossAccountId,997		property_keys: Vec<PropertyKey>,998	) -> DispatchResult {999		for key in property_keys {1000			Self::delete_collection_property(collection, sender, key)?;1001		}10021003		Ok(())1004	}10051006	// For migrations1007	pub fn set_property_permission_unchecked(1008		collection: CollectionId,1009		property_permission: PropertyKeyPermission,1010	) -> DispatchResult {1011		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1012			permissions.try_set(property_permission.key, property_permission.permission)1013		})1014		.map_err(<Error<T>>::from)?;1015		Ok(())1016	}10171018	pub fn set_property_permission(1019		collection: &CollectionHandle<T>,1020		sender: &T::CrossAccountId,1021		property_permission: PropertyKeyPermission,1022	) -> DispatchResult {1023		collection.check_is_owner_or_admin(sender)?;10241025		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1026		let current_permission = all_permissions.get(&property_permission.key);1027		if matches![1028			current_permission,1029			Some(PropertyPermission { mutable: false, .. })1030		] {1031			return Err(<Error<T>>::NoPermission.into());1032		}10331034		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1035			let property_permission = property_permission.clone();1036			permissions.try_set(property_permission.key, property_permission.permission)1037		})1038		.map_err(<Error<T>>::from)?;10391040		Self::deposit_event(Event::PropertyPermissionSet(1041			collection.id,1042			property_permission.key,1043		));10441045		Ok(())1046	}10471048	#[transactional]1049	pub fn set_token_property_permissions(1050		collection: &CollectionHandle<T>,1051		sender: &T::CrossAccountId,1052		property_permissions: Vec<PropertyKeyPermission>,1053	) -> DispatchResult {1054		for prop_pemission in property_permissions {1055			Self::set_property_permission(collection, sender, prop_pemission)?;1056		}10571058		Ok(())1059	}10601061	pub fn get_collection_property(1062		collection_id: CollectionId,1063		key: &PropertyKey,1064	) -> Option<PropertyValue> {1065		Self::collection_properties(collection_id).get(key).cloned()1066	}10671068	pub fn bytes_keys_to_property_keys(1069		keys: Vec<Vec<u8>>,1070	) -> Result<Vec<PropertyKey>, DispatchError> {1071		keys.into_iter()1072			.map(|key| -> Result<PropertyKey, DispatchError> {1073				key.try_into()1074					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1075			})1076			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1077	}10781079	pub fn filter_collection_properties(1080		collection_id: CollectionId,1081		keys: Option<Vec<PropertyKey>>,1082	) -> Result<Vec<Property>, DispatchError> {1083		let properties = Self::collection_properties(collection_id);10841085		let properties = keys1086			.map(|keys| {1087				keys.into_iter()1088					.filter_map(|key| {1089						properties.get(&key).map(|value| Property {1090							key,1091							value: value.clone(),1092						})1093					})1094					.collect()1095			})1096			.unwrap_or_else(|| {1097				properties1098					.into_iter()1099					.map(|(key, value)| Property { key, value })1100					.collect()1101			});11021103		Ok(properties)1104	}11051106	pub fn filter_property_permissions(1107		collection_id: CollectionId,1108		keys: Option<Vec<PropertyKey>>,1109	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1110		let permissions = Self::property_permissions(collection_id);11111112		let key_permissions = keys1113			.map(|keys| {1114				keys.into_iter()1115					.filter_map(|key| {1116						permissions1117							.get(&key)1118							.map(|permission| PropertyKeyPermission {1119								key,1120								permission: permission.clone(),1121							})1122					})1123					.collect()1124			})1125			.unwrap_or_else(|| {1126				permissions1127					.into_iter()1128					.map(|(key, permission)| PropertyKeyPermission { key, permission })1129					.collect()1130			});11311132		Ok(key_permissions)1133	}11341135	pub fn toggle_allowlist(1136		collection: &CollectionHandle<T>,1137		sender: &T::CrossAccountId,1138		user: &T::CrossAccountId,1139		allowed: bool,1140	) -> DispatchResult {1141		collection.check_is_owner_or_admin(sender)?;11421143		// =========11441145		if allowed {1146			<Allowlist<T>>::insert((collection.id, user), true);1147		} else {1148			<Allowlist<T>>::remove((collection.id, user));1149		}11501151		Ok(())1152	}11531154	pub fn toggle_admin(1155		collection: &CollectionHandle<T>,1156		sender: &T::CrossAccountId,1157		user: &T::CrossAccountId,1158		admin: bool,1159	) -> DispatchResult {1160		collection.check_is_owner(sender)?;11611162		let was_admin = <IsAdmin<T>>::get((collection.id, user));1163		if was_admin == admin {1164			return Ok(());1165		}1166		let amount = <AdminAmount<T>>::get(collection.id);11671168		if admin {1169			let amount = amount1170				.checked_add(1)1171				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1172			ensure!(1173				amount <= Self::collection_admins_limit(),1174				<Error<T>>::CollectionAdminCountExceeded,1175			);11761177			// =========11781179			<AdminAmount<T>>::insert(collection.id, amount);1180			<IsAdmin<T>>::insert((collection.id, user), true);1181		} else {1182			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1183			<IsAdmin<T>>::remove((collection.id, user));1184		}11851186		Ok(())1187	}11881189	pub fn clamp_limits(1190		mode: CollectionMode,1191		old_limit: &CollectionLimits,1192		mut new_limit: CollectionLimits,1193	) -> Result<CollectionLimits, DispatchError> {1194		let limits = old_limit;1195		limit_default!(old_limit, new_limit,1196			account_token_ownership_limit => ensure!(1197				new_limit <= MAX_TOKEN_OWNERSHIP,1198				<Error<T>>::CollectionLimitBoundsExceeded,1199			),1200			sponsored_data_size => ensure!(1201				new_limit <= CUSTOM_DATA_LIMIT,1202				<Error<T>>::CollectionLimitBoundsExceeded,1203			),12041205			sponsored_data_rate_limit => {},1206			token_limit => ensure!(1207				old_limit >= new_limit && new_limit > 0,1208				<Error<T>>::CollectionTokenLimitExceeded1209			),12101211			sponsor_transfer_timeout(match mode {1212				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1213				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1214				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1215			}) => ensure!(1216				new_limit <= MAX_SPONSOR_TIMEOUT,1217				<Error<T>>::CollectionLimitBoundsExceeded,1218			),1219			sponsor_approve_timeout => {},1220			owner_can_transfer => ensure!(1221				!limits.owner_can_transfer_instaled() ||1222				old_limit || !new_limit,1223				<Error<T>>::OwnerPermissionsCantBeReverted,1224			),1225			owner_can_destroy => ensure!(1226				old_limit || !new_limit,1227				<Error<T>>::OwnerPermissionsCantBeReverted,1228			),1229			transfers_enabled => {},1230		);1231		Ok(new_limit)1232	}12331234	pub fn clamp_permissions(1235		_mode: CollectionMode,1236		old_limit: &CollectionPermissions,1237		mut new_limit: CollectionPermissions,1238	) -> Result<CollectionPermissions, DispatchError> {1239		limit_default_clone!(old_limit, new_limit,1240			access => {},1241			mint_mode => {},1242			nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1243		);1244		Ok(new_limit)1245	}1246}12471248#[macro_export]1249macro_rules! unsupported {1250	() => {1251		Err(<Error<T>>::UnsupportedOperation.into())1252	};1253}12541255/// Worst cases1256pub trait CommonWeightInfo<CrossAccountId> {1257	fn create_item() -> Weight;1258	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1259	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1260	fn burn_item() -> Weight;1261	fn set_collection_properties(amount: u32) -> Weight;1262	fn delete_collection_properties(amount: u32) -> Weight;1263	fn set_token_properties(amount: u32) -> Weight;1264	fn delete_token_properties(amount: u32) -> Weight;1265	fn set_token_property_permissions(amount: u32) -> Weight;1266	fn transfer() -> Weight;1267	fn approve() -> Weight;1268	fn transfer_from() -> Weight;1269	fn burn_from() -> Weight;12701271	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1272	/// whole users's balance1273	///1274	/// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead1275	fn burn_recursively_self_raw() -> Weight;1276	/// Cost of iterating over `amount` children while burning, without counting child burning itself1277	///1278	/// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead1279	fn burn_recursively_breadth_raw(amount: u32) -> Weight;12801281	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1282		Self::burn_recursively_self_raw()1283			.saturating_mul(max_selfs.max(1) as u64)1284			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1285	}1286}12871288pub trait RefungibleExtensionsWeightInfo {1289	fn repartition() -> Weight;1290}12911292pub trait CommonCollectionOperations<T: Config> {1293	fn create_item(1294		&self,1295		sender: T::CrossAccountId,1296		to: T::CrossAccountId,1297		data: CreateItemData,1298		nesting_budget: &dyn Budget,1299	) -> DispatchResultWithPostInfo;1300	fn create_multiple_items(1301		&self,1302		sender: T::CrossAccountId,1303		to: T::CrossAccountId,1304		data: Vec<CreateItemData>,1305		nesting_budget: &dyn Budget,1306	) -> DispatchResultWithPostInfo;1307	fn create_multiple_items_ex(1308		&self,1309		sender: T::CrossAccountId,1310		data: CreateItemExData<T::CrossAccountId>,1311		nesting_budget: &dyn Budget,1312	) -> DispatchResultWithPostInfo;1313	fn burn_item(1314		&self,1315		sender: T::CrossAccountId,1316		token: TokenId,1317		amount: u128,1318	) -> DispatchResultWithPostInfo;1319	fn burn_item_recursively(1320		&self,1321		sender: T::CrossAccountId,1322		token: TokenId,1323		self_budget: &dyn Budget,1324		breadth_budget: &dyn Budget,1325	) -> DispatchResultWithPostInfo;1326	fn set_collection_properties(1327		&self,1328		sender: T::CrossAccountId,1329		properties: Vec<Property>,1330	) -> DispatchResultWithPostInfo;1331	fn delete_collection_properties(1332		&self,1333		sender: &T::CrossAccountId,1334		property_keys: Vec<PropertyKey>,1335	) -> DispatchResultWithPostInfo;1336	fn set_token_properties(1337		&self,1338		sender: T::CrossAccountId,1339		token_id: TokenId,1340		property: Vec<Property>,1341		nesting_budget: &dyn Budget,1342	) -> DispatchResultWithPostInfo;1343	fn delete_token_properties(1344		&self,1345		sender: T::CrossAccountId,1346		token_id: TokenId,1347		property_keys: Vec<PropertyKey>,1348		nesting_budget: &dyn Budget,1349	) -> DispatchResultWithPostInfo;1350	fn set_token_property_permissions(1351		&self,1352		sender: &T::CrossAccountId,1353		property_permissions: Vec<PropertyKeyPermission>,1354	) -> DispatchResultWithPostInfo;1355	fn transfer(1356		&self,1357		sender: T::CrossAccountId,1358		to: T::CrossAccountId,1359		token: TokenId,1360		amount: u128,1361		nesting_budget: &dyn Budget,1362	) -> DispatchResultWithPostInfo;1363	fn approve(1364		&self,1365		sender: T::CrossAccountId,1366		spender: T::CrossAccountId,1367		token: TokenId,1368		amount: u128,1369	) -> DispatchResultWithPostInfo;1370	fn transfer_from(1371		&self,1372		sender: T::CrossAccountId,1373		from: T::CrossAccountId,1374		to: T::CrossAccountId,1375		token: TokenId,1376		amount: u128,1377		nesting_budget: &dyn Budget,1378	) -> DispatchResultWithPostInfo;1379	fn burn_from(1380		&self,1381		sender: T::CrossAccountId,1382		from: T::CrossAccountId,1383		token: TokenId,1384		amount: u128,1385		nesting_budget: &dyn Budget,1386	) -> DispatchResultWithPostInfo;13871388	fn check_nesting(1389		&self,1390		sender: T::CrossAccountId,1391		from: (CollectionId, TokenId),1392		under: TokenId,1393		nesting_budget: &dyn Budget,1394	) -> DispatchResult;13951396	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13971398	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13991400	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1401	fn collection_tokens(&self) -> Vec<TokenId>;1402	fn token_exists(&self, token: TokenId) -> bool;1403	fn last_token_id(&self) -> TokenId;14041405	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1406	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1407	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1408	/// Amount of unique collection tokens1409	fn total_supply(&self) -> u32;1410	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1411	fn account_balance(&self, account: T::CrossAccountId) -> u32;1412	/// Amount of specific token account have (Applicable to fungible/refungible)1413	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1414	/// Amount of token pieces1415	fn total_pieces(&self, token: TokenId) -> Option<u128>;1416	fn allowance(1417		&self,1418		sender: T::CrossAccountId,1419		spender: T::CrossAccountId,1420		token: TokenId,1421	) -> u128;1422	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1423}14241425pub trait RefungibleExtensions<T>1426where1427	T: Config,1428{1429	fn repartition(1430		&self,1431		owner: &T::CrossAccountId,1432		token: TokenId,1433		amount: u128,1434	) -> DispatchResultWithPostInfo;1435}14361437// Flexible enough for implementing CommonCollectionOperations1438pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1439	let post_info = PostDispatchInfo {1440		actual_weight: Some(weight),1441		pays_fee: Pays::Yes,1442	};1443	match res {1444		Ok(()) => Ok(post_info),1445		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1446	}1447}14481449impl<T: Config> From<PropertiesError> for Error<T> {1450	fn from(error: PropertiesError) -> Self {1451		match error {1452			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1453			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1454			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1455			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1456			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1457		}1458	}1459}