git.delta.rocks / unique-network / refs/commits / 13a972624863

difftreelog

Merge branch 'feature/switch-from-currecy-trait-to-fungible-v2' into feature/update-polkadot-v0.9.42

Grigoriy Simonov2023-05-23parents: #6203316 #a8f92f7.patch.diff
in: master

7 files changed

modifiedpallets/collator-selection/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/collator-selection/src/benchmarking.rs
+++ b/pallets/collator-selection/src/benchmarking.rs
@@ -40,7 +40,11 @@
 use frame_support::{
 	assert_ok,
 	codec::Decode,
-	traits::{Currency, EnsureOrigin, Get},
+	traits::{
+		EnsureOrigin,
+		fungible::{Inspect, Mutate},
+		Get,
+	},
 };
 use frame_system::{EventRecord, RawOrigin};
 use pallet_authorship::EventHandler;
@@ -78,7 +82,7 @@
 ) -> T::AccountId {
 	let user = account(string, n, SEED);
 	let balance = balance_unit::<T>() * balance_factor.into();
-	let _ = T::Currency::make_free_balance_be(&user, balance);
+	let _ = T::Currency::set_balance(&user, balance);
 	user
 }
 
@@ -137,7 +141,7 @@
 	);
 
 	for who in candidates {
-		T::Currency::make_free_balance_be(&who, <LicenseBond<T>>::get() * 2u32.into());
+		T::Currency::set_balance(&who, <LicenseBond<T>>::get() * 2u32.into());
 		<CollatorSelection<T>>::get_license(RawOrigin::Signed(who.clone()).into()).unwrap();
 		<CollatorSelection<T>>::onboard(RawOrigin::Signed(who).into()).unwrap();
 	}
@@ -153,14 +157,14 @@
 	);
 
 	for who in candidates {
-		T::Currency::make_free_balance_be(&who, <LicenseBond<T>>::get() * 2u32.into());
+		T::Currency::set_balance(&who, <LicenseBond<T>>::get() * 2u32.into());
 		<CollatorSelection<T>>::get_license(RawOrigin::Signed(who.clone()).into()).unwrap();
 	}
 }
 
 /// `Currency::minimum_balance` was used originally, but in unique-chain, we have
 /// zero existential deposit, thus triggering zero bond assertion.
-fn balance_unit<T: Config>() -> <T::Currency as Currency<T::AccountId>>::Balance {
+fn balance_unit<T: Config>() -> BalanceOf<T> {
 	200u32.into()
 }
 
@@ -168,7 +172,9 @@
 const INITIAL_INVULNERABLES: u32 = 2;
 
 benchmarks! {
-	where_clause { where T: pallet_authorship::Config + session::Config + configuration::Config }
+	where_clause { where
+		T: pallet_authorship::Config + session::Config + configuration::Config
+	}
 
 	// todo:collator this and all the following do not work for some reason, going all the way up to 10 in length
 	// Both invulnerables and candidates count together against MaxCollators.
@@ -182,7 +188,7 @@
 
 		let new_invulnerable: T::AccountId = whitelisted_caller();
 		let bond: BalanceOf<T> = balance_unit::<T>() * 2u32.into();
-		T::Currency::make_free_balance_be(&new_invulnerable, bond.clone());
+		T::Currency::set_balance(&new_invulnerable, bond.clone());
 
 		<session::Pallet<T>>::set_keys(
 			RawOrigin::Signed(new_invulnerable.clone()).into(),
@@ -227,7 +233,7 @@
 
 		let caller: T::AccountId = whitelisted_caller();
 		let bond: BalanceOf<T> = balance_unit::<T>() * 2u32.into();
-		T::Currency::make_free_balance_be(&caller, bond.clone());
+		T::Currency::set_balance(&caller, bond.clone());
 
 		<session::Pallet<T>>::set_keys(
 			RawOrigin::Signed(caller.clone()).into(),
@@ -253,7 +259,7 @@
 
 		let caller: T::AccountId = whitelisted_caller();
 		let bond: BalanceOf<T> = balance_unit::<T>() * 2u32.into();
-		T::Currency::make_free_balance_be(&caller, bond.clone());
+		T::Currency::set_balance(&caller, bond.clone());
 
 		let origin = RawOrigin::Signed(caller.clone());
 
@@ -329,7 +335,7 @@
 	// worst case is paying a non-existing candidate account.
 	note_author {
 		<LicenseBond<T>>::put(balance_unit::<T>());
-		T::Currency::make_free_balance_be(
+		T::Currency::set_balance(
 			&<CollatorSelection<T>>::account_id(),
 			balance_unit::<T>() * 4u32.into(),
 		);
@@ -337,11 +343,11 @@
 		let new_block: T::BlockNumber = 10u32.into();
 
 		frame_system::Pallet::<T>::set_block_number(new_block);
-		assert!(T::Currency::free_balance(&author) == 0u32.into());
+		assert!(T::Currency::balance(&author) == 0u32.into());
 	}: {
 		<CollatorSelection<T> as EventHandler<_, _>>::note_author(author.clone())
 	} verify {
-		assert!(T::Currency::free_balance(&author) > 0u32.into());
+		assert!(T::Currency::balance(&author) > 0u32.into());
 		assert_eq!(frame_system::Pallet::<T>::block_number(), new_block);
 	}
 
modifiedpallets/collator-selection/src/lib.rsdiffbeforeafterboth
--- a/pallets/collator-selection/src/lib.rs
+++ b/pallets/collator-selection/src/lib.rs
@@ -92,6 +92,7 @@
 
 #[frame_support::pallet]
 pub mod pallet {
+	use super::*;
 	pub use crate::weights::WeightInfo;
 	use core::ops::Div;
 	use frame_support::{
@@ -100,8 +101,10 @@
 		pallet_prelude::*,
 		sp_runtime::traits::{AccountIdConversion, CheckedSub, Saturating, Zero},
 		traits::{
-			Currency, EnsureOrigin, ExistenceRequirement::KeepAlive, ReservableCurrency,
+			EnsureOrigin,
+			fungible::{Balanced, BalancedHold, Inspect, InspectHold, Mutate, MutateHold},
 			ValidatorRegistration,
+			tokens::{Precision, Preservation},
 		},
 		BoundedVec, PalletId,
 	};
@@ -158,6 +161,9 @@
 
 		/// The weight information of this pallet.
 		type WeightInfo: WeightInfo;
+
+		#[pallet::constant]
+		type LicenceBondIdentifier: Get<<<Self as pallet_configuration::Config>::Currency as InspectHold<Self::AccountId>>::Reason>;
 	}
 
 	#[pallet::pallet]
@@ -361,7 +367,7 @@
 
 			let deposit = <LicenseBond<T>>::get();
 
-			T::Currency::reserve(&who, deposit)?;
+			T::Currency::hold(&T::LicenceBondIdentifier::get(), &who, deposit)?;
 			LicenseDepositOf::<T>::insert(who.clone(), deposit);
 
 			Self::deposit_event(Event::LicenseObtained {
@@ -523,17 +529,24 @@
 						let slashed = T::SlashRatio::get() * deposit;
 						let remaining = deposit - slashed;
 
-						let (imbalance, _) = T::Currency::slash_reserved(who, slashed);
+						let (imbalance, _) =
+							T::Currency::slash(&T::LicenceBondIdentifier::get(), who, slashed);
 						//T::Currency::unreserve(who, remaining);
 						deposit_returned = remaining;
 
-						T::Currency::resolve_creating(&T::TreasuryAccountId::get(), imbalance);
+						T::Currency::resolve(&T::TreasuryAccountId::get(), imbalance)
+							.map_err(|_| DispatchError::Other("Failed to deposit imbalance"))?;
 					} else {
 						//T::Currency::unreserve(who, deposit);
 						deposit_returned = deposit;
 					}
 
-					T::Currency::unreserve(who, deposit_returned);
+					T::Currency::release(
+						&T::LicenceBondIdentifier::get(),
+						who,
+						deposit_returned,
+						Precision::Exact,
+					)?;
 					Ok(())
 				} else {
 					Err(Error::<T>::NoLicense.into())
@@ -594,12 +607,12 @@
 		fn note_author(author: T::AccountId) {
 			let pot = Self::account_id();
 			// assumes an ED will be sent to pot.
-			let reward = T::Currency::free_balance(&pot)
+			let reward = T::Currency::balance(&pot)
 				.checked_sub(&T::Currency::minimum_balance())
 				.unwrap_or_else(Zero::zero)
 				.div(2u32.into());
 			// `reward` is half of pot account minus ED, this should never fail.
-			let _success = T::Currency::transfer(&pot, &author, reward, KeepAlive);
+			let _success = T::Currency::transfer(&pot, &author, reward, Preservation::Preserve);
 			debug_assert!(_success.is_ok());
 			<LastAuthoredBlock<T>>::insert(author, frame_system::Pallet::<T>::block_number());
 
modifiedpallets/collator-selection/src/tests.rsdiffbeforeafterboth
--- a/pallets/collator-selection/src/tests.rs
+++ b/pallets/collator-selection/src/tests.rs
@@ -417,7 +417,10 @@
 fn authorship_event_handler() {
 	new_test_ext().execute_with(|| {
 		// put 100 in the pot + 5 for ED
-		Balances::make_free_balance_be(&CollatorSelection::account_id(), 105);
+		<pallet_balances::Pallet<T> as fungible::Mutate<T::AccountId>>::set_balance(
+			&CollatorSelection::account_id(),
+			105,
+		);
 
 		// 4 is the default author.
 		assert_eq!(Balances::free_balance(4), 100);
@@ -441,7 +444,10 @@
 		// Nothing panics, no reward when no ED in balance
 		Authorship::on_initialize(1);
 		// put some money into the pot at ED
-		Balances::make_free_balance_be(&CollatorSelection::account_id(), 5);
+		<pallet_balances::Pallet<T> as fungible::Mutate<T::AccountId>>::set_balance(
+			&CollatorSelection::account_id(),
+			5,
+		);
 		// 4 is the default author.
 		assert_eq!(Balances::free_balance(4), 100);
 		get_license_and_onboard(4);
modifiedpallets/common/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/common/src/benchmarking.rs
+++ b/pallets/common/src/benchmarking.rs
@@ -27,12 +27,12 @@
 	MAX_PROPERTIES_PER_ITEM,
 };
 use frame_support::{
-	traits::{Currency, Get},
+	traits::{Get, fungible::Balanced, Imbalance, tokens::Precision},
 	pallet_prelude::ConstU32,
 	BoundedVec,
 };
 use core::convert::TryInto;
-use sp_runtime::DispatchError;
+use sp_runtime::{DispatchError, traits::Zero};
 
 const SEED: u32 = 1;
 
@@ -85,7 +85,12 @@
 	) -> Result<CollectionId, DispatchError>,
 	cast: impl FnOnce(CollectionHandle<T>) -> R,
 ) -> Result<R, DispatchError> {
-	<T as Config>::Currency::deposit_creating(&owner.as_sub(), T::CollectionCreationPrice::get());
+	let imbalance = <T as Config>::Currency::deposit(
+		&owner.as_sub(),
+		T::CollectionCreationPrice::get(),
+		Precision::Exact,
+	)?;
+	debug_assert!(imbalance.peek().is_zero());
 	let name = create_u16_data::<MAX_COLLECTION_NAME_LENGTH>();
 	let description = create_u16_data::<MAX_COLLECTION_DESCRIPTION_LENGTH>();
 	let token_prefix = create_data::<MAX_TOKEN_PREFIX_LENGTH>();
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
before · pallets/common/src/lib.rs
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//! # Common pallet18//!19//! The Common pallet provides functionality for handling collections.20//!21//! ## Overview22//!23//! The Common pallet provides an interface for common collection operations for different collection types24//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.25//! It also provides this functionality to EVM, see [erc] and [eth] modules.26//!27//! The Common pallet provides functions for:28//!29//! - Setting and approving collection sponsor.30//! - Get\set\delete allow list.31//! - Get\set\delete collection properties.32//! - Get\set\delete collection property permissions.33//! - Get\set\delete token property permissions.34//! - Get\set\delete collection administrators.35//! - Checking access permissions.36//!37//! ### Terminology38//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will39//! be possible to mint tokens.40//!41//! **Allow list** - List of users who have the right to minting tokens.42//!43//! **Collection properties** - Collection properties are simply key-value stores where various44//! metadata can be placed.45//!46//! **Permissions on token properties** - For each property in the token can be set permission47//! to change, see [`PropertyPermission`].48//!49//! **Collection administrator** - For a collection, you can set administrators who have the right50//! to most actions on the collection.5152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::{57	ops::{Deref, DerefMut},58	slice::from_ref,59};60use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};61use sp_std::vec::Vec;62use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};63use evm_coder::ToLog;64use frame_support::{65	dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},66	ensure,67	traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},68	dispatch::Pays,69	transactional, fail,70};71use pallet_evm::GasWeightMapping;72use up_data_structs::{73	AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionFlags,74	RpcCollectionFlags, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,75	COLLECTION_ADMINS_LIMIT, TokenId, TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP,76	CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,77	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,78	CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit, budget::Budget,79	PhantomType, Property, CollectionProperties as CollectionPropertiesT, TokenProperties,80	PropertiesPermissionMap, PropertyKey, PropertyValue, PropertyPermission, PropertiesError,81	TokenOwnerError, PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope,82	CollectionPermissions,83};84use up_pov_estimate_rpc::PovInfo;8586pub use pallet::*;87use sp_core::H160;88use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};8990#[cfg(feature = "runtime-benchmarks")]91pub mod benchmarking;92pub mod dispatch;93pub mod erc;94pub mod eth;95pub mod helpers;96#[allow(missing_docs)]97pub mod weights;98/// Weight info.99pub type SelfWeightOf<T> = <T as Config>::WeightInfo;100101/// Collection handle contains information about collection data and id.102/// Also provides functionality to count consumed gas.103///104/// CollectionHandle is used as a generic wrapper for collections of all types.105/// It allows to perform common operations and queries on any collection type,106/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].107#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]108pub struct CollectionHandle<T: Config> {109	/// Collection id110	pub id: CollectionId,111	collection: Collection<T::AccountId>,112	/// Substrate recorder for counting consumed gas113	pub recorder: SubstrateRecorder<T>,114}115116impl<T: Config> WithRecorder<T> for CollectionHandle<T> {117	fn recorder(&self) -> &SubstrateRecorder<T> {118		&self.recorder119	}120	fn into_recorder(self) -> SubstrateRecorder<T> {121		self.recorder122	}123}124125impl<T: Config> CollectionHandle<T> {126	/// Same as [CollectionHandle::new] but with an explicit gas limit.127	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {128		<CollectionById<T>>::get(id).map(|collection| Self {129			id,130			collection,131			recorder: SubstrateRecorder::new(gas_limit),132		})133	}134135	/// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].136	pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {137		<CollectionById<T>>::get(id).map(|collection| Self {138			id,139			collection,140			recorder,141		})142	}143144	/// Retrives collection data from storage and creates collection handle with default parameters.145	/// If collection not found return `None`146	pub fn new(id: CollectionId) -> Option<Self> {147		Self::new_with_gas_limit(id, u64::MAX)148	}149150	/// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.151	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {152		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)153	}154155	/// Consume gas for reading.156	pub fn consume_store_reads(157		&self,158		reads: u64,159	) -> pallet_evm_coder_substrate::execution::Result<()> {160		self.recorder161			.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(162				<T as frame_system::Config>::DbWeight::get()163					.read164					.saturating_mul(reads),165				// TODO: measure proof166				0,167			)))168	}169170	/// Consume gas for writing.171	pub fn consume_store_writes(172		&self,173		writes: u64,174	) -> pallet_evm_coder_substrate::execution::Result<()> {175		self.recorder176			.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(177				<T as frame_system::Config>::DbWeight::get()178					.write179					.saturating_mul(writes),180				// TODO: measure proof181				0,182			)))183	}184185	/// Consume gas for reading and writing.186	pub fn consume_store_reads_and_writes(187		&self,188		reads: u64,189		writes: u64,190	) -> pallet_evm_coder_substrate::execution::Result<()> {191		let weight = <T as frame_system::Config>::DbWeight::get();192		let reads = weight.read.saturating_mul(reads);193		let writes = weight.read.saturating_mul(writes);194		self.recorder195			.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(196				reads.saturating_add(writes),197				// TODO: measure proof198				0,199			)))200	}201202	/// Save collection to storage.203	pub fn save(&self) -> DispatchResult {204		<CollectionById<T>>::insert(self.id, &self.collection);205		Ok(())206	}207208	/// Set collection sponsor.209	///210	/// Unique collections allows sponsoring for certain actions.211	/// This method allows you to set the sponsor of the collection.212	/// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].213	pub fn set_sponsor(214		&mut self,215		sender: &T::CrossAccountId,216		sponsor: T::AccountId,217	) -> DispatchResult {218		self.check_is_internal()?;219		self.check_is_owner_or_admin(sender)?;220221		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());222223		<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));224		<PalletEvm<T>>::deposit_log(225			erc::CollectionHelpersEvents::CollectionChanged {226				collection_id: eth::collection_id_to_address(self.id),227			}228			.to_log(T::ContractAddress::get()),229		);230231		self.save()232	}233234	/// Force set `sponsor`.235	///236	/// Differs from [`set_sponsor`][`Self::set_sponsor`] in that confirmation237	/// from the `sponsor` is not required.238	///239	/// # Arguments240	///241	/// * `sender`: Caller's account.242	/// * `sponsor`: ID of the account of the sponsor-to-be.243	pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {244		self.check_is_internal()?;245246		self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());247248		<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));249		<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));250		<PalletEvm<T>>::deposit_log(251			erc::CollectionHelpersEvents::CollectionChanged {252				collection_id: eth::collection_id_to_address(self.id),253			}254			.to_log(T::ContractAddress::get()),255		);256257		self.save()258	}259260	/// Confirm sponsorship261	///262	/// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.263	/// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].264	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {265		self.check_is_internal()?;266		ensure!(267			self.collection.sponsorship.pending_sponsor() == Some(sender),268			Error::<T>::ConfirmSponsorshipFail269		);270271		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());272273		<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));274		<PalletEvm<T>>::deposit_log(275			erc::CollectionHelpersEvents::CollectionChanged {276				collection_id: eth::collection_id_to_address(self.id),277			}278			.to_log(T::ContractAddress::get()),279		);280281		self.save()282	}283284	/// Remove collection sponsor.285	pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {286		self.check_is_internal()?;287		self.check_is_owner_or_admin(sender)?;288289		self.collection.sponsorship = SponsorshipState::Disabled;290291		<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));292		<PalletEvm<T>>::deposit_log(293			erc::CollectionHelpersEvents::CollectionChanged {294				collection_id: eth::collection_id_to_address(self.id),295			}296			.to_log(T::ContractAddress::get()),297		);298		self.save()299	}300301	/// Force remove `sponsor`.302	///303	/// Differs from `remove_sponsor` in that304	/// it doesn't require consent from the `owner` of the collection.305	pub fn force_remove_sponsor(&mut self) -> DispatchResult {306		self.check_is_internal()?;307308		self.collection.sponsorship = SponsorshipState::Disabled;309310		<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));311		<PalletEvm<T>>::deposit_log(312			erc::CollectionHelpersEvents::CollectionChanged {313				collection_id: eth::collection_id_to_address(self.id),314			}315			.to_log(T::ContractAddress::get()),316		);317		self.save()318	}319320	/// Checks that the collection was created with, and must be operated upon through **Unique API**.321	/// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.322	pub fn check_is_internal(&self) -> DispatchResult {323		if self.flags.external {324			return Err(<Error<T>>::CollectionIsExternal)?;325		}326327		Ok(())328	}329330	/// Checks that the collection was created with, and must be operated upon through an **assimilated API**.331	/// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.332	pub fn check_is_external(&self) -> DispatchResult {333		if !self.flags.external {334			return Err(<Error<T>>::CollectionIsInternal)?;335		}336337		Ok(())338	}339}340341impl<T: Config> Deref for CollectionHandle<T> {342	type Target = Collection<T::AccountId>;343344	fn deref(&self) -> &Self::Target {345		&self.collection346	}347}348349impl<T: Config> DerefMut for CollectionHandle<T> {350	fn deref_mut(&mut self) -> &mut Self::Target {351		&mut self.collection352	}353}354355impl<T: Config> CollectionHandle<T> {356	/// Checks if the `user` is the owner of the collection.357	pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {358		ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);359		Ok(())360	}361362	/// Returns **true** if the `user` is the owner or administrator of the collection.363	pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {364		*user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))365	}366367	/// Checks if the `user` is the owner or administrator of the collection.368	pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {369		ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);370		Ok(())371	}372373	/// Returns **true** if374	/// * the `user`is a collection owner or admin375	/// * the collection limits allow the owner/admins to transfer/burn any collection token376	pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {377		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)378	}379380	/// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.381	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {382		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)383	}384385	/// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.386	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {387		ensure!(388			<Allowlist<T>>::get((self.id, user)),389			<Error<T>>::AddressNotInAllowlist390		);391		Ok(())392	}393394	/// Changes collection owner to another account395	/// #### Store read/writes396	/// 1 writes397	pub fn change_owner(398		&mut self,399		caller: T::CrossAccountId,400		new_owner: T::CrossAccountId,401	) -> DispatchResult {402		self.check_is_internal()?;403		self.check_is_owner(&caller)?;404		self.collection.owner = new_owner.as_sub().clone();405406		<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(407			self.id,408			new_owner.as_sub().clone(),409		));410		<PalletEvm<T>>::deposit_log(411			erc::CollectionHelpersEvents::CollectionChanged {412				collection_id: eth::collection_id_to_address(self.id),413			}414			.to_log(T::ContractAddress::get()),415		);416417		self.save()418	}419}420421#[frame_support::pallet]422pub mod pallet {423424	use super::*;425	use dispatch::CollectionDispatch;426	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};427	use frame_support::traits::Currency;428	use up_data_structs::{TokenId, mapping::TokenAddressMapping};429	use scale_info::TypeInfo;430	use weights::WeightInfo;431432	#[pallet::config]433	pub trait Config:434		frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo435	{436		/// Weight information for functions of this pallet.437		type WeightInfo: WeightInfo;438439		/// Events compatible with [`frame_system::Config::Event`].440		type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;441442		/// Handler of accounts and payment.443		type Currency: Currency<Self::AccountId>;444445		/// Set price to create a collection.446		#[pallet::constant]447		type CollectionCreationPrice: Get<448			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,449		>;450451		/// Dispatcher of operations on collections.452		type CollectionDispatch: CollectionDispatch<Self>;453454		/// Account which holds the chain's treasury.455		type TreasuryAccountId: Get<Self::AccountId>;456457		/// Address under which the CollectionHelper contract would be available.458		#[pallet::constant]459		type ContractAddress: Get<H160>;460461		/// Mapper for token addresses to Ethereum addresses.462		type EvmTokenAddressMapping: TokenAddressMapping<H160>;463464		/// Mapper for token addresses to [`CrossAccountId`].465		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;466	}467468	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);469470	#[pallet::pallet]471	#[pallet::storage_version(STORAGE_VERSION)]472	pub struct Pallet<T>(_);473474	#[pallet::extra_constants]475	impl<T: Config> Pallet<T> {476		/// Maximum admins per collection.477		pub fn collection_admins_limit() -> u32 {478			COLLECTION_ADMINS_LIMIT479		}480	}481482	#[pallet::genesis_config]483	pub struct GenesisConfig<T>(PhantomData<T>);484485	#[cfg(feature = "std")]486	impl<T: Config> Default for GenesisConfig<T> {487		fn default() -> Self {488			Self(Default::default())489		}490	}491492	#[pallet::genesis_build]493	impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {494		fn build(&self) {495			StorageVersion::new(1).put::<Pallet<T>>();496		}497	}498499	impl<T: Config> Pallet<T> {500		/// Helper function that handles deposit events501		pub fn deposit_event(event: Event<T>) {502			let event = <T as Config>::RuntimeEvent::from(event);503			let event = event.into();504			<frame_system::Pallet<T>>::deposit_event(event)505		}506	}507508	#[pallet::event]509	pub enum Event<T: Config> {510		/// New collection was created511		CollectionCreated(512			/// Globally unique identifier of newly created collection.513			CollectionId,514			/// [`CollectionMode`] converted into _u8_.515			u8,516			/// Collection owner.517			T::AccountId,518		),519520		/// New collection was destroyed521		CollectionDestroyed(522			/// Globally unique identifier of collection.523			CollectionId,524		),525526		/// New item was created.527		ItemCreated(528			/// Id of the collection where item was created.529			CollectionId,530			/// Id of an item. Unique within the collection.531			TokenId,532			/// Owner of newly created item533			T::CrossAccountId,534			/// Always 1 for NFT535			u128,536		),537538		/// Collection item was burned.539		ItemDestroyed(540			/// Id of the collection where item was destroyed.541			CollectionId,542			/// Identifier of burned NFT.543			TokenId,544			/// Which user has destroyed its tokens.545			T::CrossAccountId,546			/// Amount of token pieces destroed. Always 1 for NFT.547			u128,548		),549550		/// Item was transferred551		Transfer(552			/// Id of collection to which item is belong.553			CollectionId,554			/// Id of an item.555			TokenId,556			/// Original owner of item.557			T::CrossAccountId,558			/// New owner of item.559			T::CrossAccountId,560			/// Amount of token pieces transfered. Always 1 for NFT.561			u128,562		),563564		/// Amount pieces of token owned by `sender` was approved for `spender`.565		Approved(566			/// Id of collection to which item is belong.567			CollectionId,568			/// Id of an item.569			TokenId,570			/// Original owner of item.571			T::CrossAccountId,572			/// Id for which the approval was granted.573			T::CrossAccountId,574			/// Amount of token pieces transfered. Always 1 for NFT.575			u128,576		),577578		/// A `sender` approves operations on all owned tokens for `spender`.579		ApprovedForAll(580			/// Id of collection to which item is belong.581			CollectionId,582			/// Owner of a wallet.583			T::CrossAccountId,584			/// Id for which operator status was granted or rewoked.585			T::CrossAccountId,586			/// Is operator status granted or revoked?587			bool,588		),589590		/// The colletion property has been added or edited.591		CollectionPropertySet(592			/// Id of collection to which property has been set.593			CollectionId,594			/// The property that was set.595			PropertyKey,596		),597598		/// The property has been deleted.599		CollectionPropertyDeleted(600			/// Id of collection to which property has been deleted.601			CollectionId,602			/// The property that was deleted.603			PropertyKey,604		),605606		/// The token property has been added or edited.607		TokenPropertySet(608			/// Identifier of the collection whose token has the property set.609			CollectionId,610			/// The token for which the property was set.611			TokenId,612			/// The property that was set.613			PropertyKey,614		),615616		/// The token property has been deleted.617		TokenPropertyDeleted(618			/// Identifier of the collection whose token has the property deleted.619			CollectionId,620			/// The token for which the property was deleted.621			TokenId,622			/// The property that was deleted.623			PropertyKey,624		),625626		/// The token property permission of a collection has been set.627		PropertyPermissionSet(628			/// ID of collection to which property permission has been set.629			CollectionId,630			/// The property permission that was set.631			PropertyKey,632		),633634		/// Address was added to the allow list.635		AllowListAddressAdded(636			/// ID of the affected collection.637			CollectionId,638			/// Address of the added account.639			T::CrossAccountId,640		),641642		/// Address was removed from the allow list.643		AllowListAddressRemoved(644			/// ID of the affected collection.645			CollectionId,646			/// Address of the removed account.647			T::CrossAccountId,648		),649650		/// Collection admin was added.651		CollectionAdminAdded(652			/// ID of the affected collection.653			CollectionId,654			/// Admin address.655			T::CrossAccountId,656		),657658		/// Collection admin was removed.659		CollectionAdminRemoved(660			/// ID of the affected collection.661			CollectionId,662			/// Removed admin address.663			T::CrossAccountId,664		),665666		/// Collection limits were set.667		CollectionLimitSet(668			/// ID of the affected collection.669			CollectionId,670		),671672		/// Collection owned was changed.673		CollectionOwnerChanged(674			/// ID of the affected collection.675			CollectionId,676			/// New owner address.677			T::AccountId,678		),679680		/// Collection permissions were set.681		CollectionPermissionSet(682			/// ID of the affected collection.683			CollectionId,684		),685686		/// Collection sponsor was set.687		CollectionSponsorSet(688			/// ID of the affected collection.689			CollectionId,690			/// New sponsor address.691			T::AccountId,692		),693694		/// New sponsor was confirm.695		SponsorshipConfirmed(696			/// ID of the affected collection.697			CollectionId,698			/// New sponsor address.699			T::AccountId,700		),701702		/// Collection sponsor was removed.703		CollectionSponsorRemoved(704			/// ID of the affected collection.705			CollectionId,706		),707	}708709	#[pallet::error]710	pub enum Error<T> {711		/// This collection does not exist.712		CollectionNotFound,713		/// Sender parameter and item owner must be equal.714		MustBeTokenOwner,715		/// No permission to perform action716		NoPermission,717		/// Destroying only empty collections is allowed718		CantDestroyNotEmptyCollection,719		/// Collection is not in mint mode.720		PublicMintingNotAllowed,721		/// Address is not in allow list.722		AddressNotInAllowlist,723724		/// Collection name can not be longer than 63 char.725		CollectionNameLimitExceeded,726		/// Collection description can not be longer than 255 char.727		CollectionDescriptionLimitExceeded,728		/// Token prefix can not be longer than 15 char.729		CollectionTokenPrefixLimitExceeded,730		/// Total collections bound exceeded.731		TotalCollectionsLimitExceeded,732		/// Exceeded max admin count733		CollectionAdminCountExceeded,734		/// Collection limit bounds per collection exceeded735		CollectionLimitBoundsExceeded,736		/// Tried to enable permissions which are only permitted to be disabled737		OwnerPermissionsCantBeReverted,738		/// Collection settings not allowing items transferring739		TransferNotAllowed,740		/// Account token limit exceeded per collection741		AccountTokenLimitExceeded,742		/// Collection token limit exceeded743		CollectionTokenLimitExceeded,744		/// Metadata flag frozen745		MetadataFlagFrozen,746747		/// Item does not exist748		TokenNotFound,749		/// Item is balance not enough750		TokenValueTooLow,751		/// Requested value is more than the approved752		ApprovedValueTooLow,753		/// Tried to approve more than owned754		CantApproveMoreThanOwned,755		/// Only spending from eth mirror could be approved756		AddressIsNotEthMirror,757758		/// Can't transfer tokens to ethereum zero address759		AddressIsZero,760761		/// The operation is not supported762		UnsupportedOperation,763764		/// Insufficient funds to perform an action765		NotSufficientFounds,766767		/// User does not satisfy the nesting rule768		UserIsNotAllowedToNest,769		/// Only tokens from specific collections may nest tokens under this one770		SourceCollectionIsNotAllowedToNest,771772		/// Tried to store more data than allowed in collection field773		CollectionFieldSizeExceeded,774775		/// Tried to store more property data than allowed776		NoSpaceForProperty,777778		/// Tried to store more property keys than allowed779		PropertyLimitReached,780781		/// Property key is too long782		PropertyKeyIsTooLong,783784		/// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed785		InvalidCharacterInPropertyKey,786787		/// Empty property keys are forbidden788		EmptyPropertyKey,789790		/// Tried to access an external collection with an internal API791		CollectionIsExternal,792793		/// Tried to access an internal collection with an external API794		CollectionIsInternal,795796		/// This address is not set as sponsor, use setCollectionSponsor first.797		ConfirmSponsorshipFail,798799		/// The user is not an administrator.800		UserIsNotCollectionAdmin,801	}802803	/// Storage of the count of created collections. Essentially contains the last collection ID.804	#[pallet::storage]805	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;806807	/// Storage of the count of deleted collections.808	#[pallet::storage]809	pub type DestroyedCollectionCount<T> =810		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;811812	/// Storage of collection info.813	#[pallet::storage]814	pub type CollectionById<T> = StorageMap<815		Hasher = Blake2_128Concat,816		Key = CollectionId,817		Value = Collection<<T as frame_system::Config>::AccountId>,818		QueryKind = OptionQuery,819	>;820821	/// Storage of collection properties.822	#[pallet::storage]823	#[pallet::getter(fn collection_properties)]824	pub type CollectionProperties<T> = StorageMap<825		Hasher = Blake2_128Concat,826		Key = CollectionId,827		Value = CollectionPropertiesT,828		QueryKind = ValueQuery,829	>;830831	/// Storage of token property permissions of a collection.832	#[pallet::storage]833	#[pallet::getter(fn property_permissions)]834	pub type CollectionPropertyPermissions<T> = StorageMap<835		Hasher = Blake2_128Concat,836		Key = CollectionId,837		Value = PropertiesPermissionMap,838		QueryKind = ValueQuery,839	>;840841	/// Storage of the amount of collection admins.842	#[pallet::storage]843	pub type AdminAmount<T> = StorageMap<844		Hasher = Blake2_128Concat,845		Key = CollectionId,846		Value = u32,847		QueryKind = ValueQuery,848	>;849850	/// List of collection admins.851	#[pallet::storage]852	pub type IsAdmin<T: Config> = StorageNMap<853		Key = (854			Key<Blake2_128Concat, CollectionId>,855			Key<Blake2_128Concat, T::CrossAccountId>,856		),857		Value = bool,858		QueryKind = ValueQuery,859	>;860861	/// Allowlisted collection users.862	#[pallet::storage]863	pub type Allowlist<T: Config> = StorageNMap<864		Key = (865			Key<Blake2_128Concat, CollectionId>,866			Key<Blake2_128Concat, T::CrossAccountId>,867		),868		Value = bool,869		QueryKind = ValueQuery,870	>;871872	/// Not used by code, exists only to provide some types to metadata.873	#[pallet::storage]874	pub type DummyStorageValue<T: Config> = StorageValue<875		Value = (876			CollectionStats,877			CollectionId,878			TokenId,879			TokenChild,880			PhantomType<(881				TokenData<T::CrossAccountId>,882				RpcCollection<T::AccountId>,883				// PoV Estimate Info884				PovInfo,885			)>,886		),887		QueryKind = OptionQuery,888	>;889}890891impl<T: Config> Pallet<T> {892	/// Enshure that receiver address is correct.893	///894	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.895	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {896		ensure!(897			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,898			<Error<T>>::AddressIsZero899		);900		Ok(())901	}902903	/// Get a vector of collection admins.904	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {905		<IsAdmin<T>>::iter_prefix((collection,))906			.map(|(a, _)| a)907			.collect()908	}909910	/// Get a vector of users allowed to mint tokens.911	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {912		<Allowlist<T>>::iter_prefix((collection,))913			.map(|(a, _)| a)914			.collect()915	}916917	/// Is `user` allowed to mint token in `collection`.918	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {919		<Allowlist<T>>::get((collection, user))920	}921922	/// Get statistics of collections.923	pub fn collection_stats() -> CollectionStats {924		let created = <CreatedCollectionCount<T>>::get();925		let destroyed = <DestroyedCollectionCount<T>>::get();926		CollectionStats {927			created: created.0,928			destroyed: destroyed.0,929			alive: created.0 - destroyed.0,930		}931	}932933	/// Get the effective limits for the collection.934	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {935		let collection = <CollectionById<T>>::get(collection)?;936		let limits = collection.limits;937		let effective_limits = CollectionLimits {938			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),939			sponsored_data_size: Some(limits.sponsored_data_size()),940			sponsored_data_rate_limit: Some(941				limits942					.sponsored_data_rate_limit943					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),944			),945			token_limit: Some(limits.token_limit()),946			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(947				match collection.mode {948					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,949					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,950					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,951				},952			)),953			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),954			owner_can_transfer: Some(limits.owner_can_transfer()),955			owner_can_destroy: Some(limits.owner_can_destroy()),956			transfers_enabled: Some(limits.transfers_enabled()),957		};958959		Some(effective_limits)960	}961962	/// Returns information about the `collection` adapted for rpc.963	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {964		let Collection {965			name,966			description,967			owner,968			mode,969			token_prefix,970			sponsorship,971			limits,972			permissions,973			flags,974		} = <CollectionById<T>>::get(collection)?;975976		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)977			.into_iter()978			.map(|(key, permission)| PropertyKeyPermission { key, permission })979			.collect();980981		let properties = <CollectionProperties<T>>::get(collection)982			.into_iter()983			.map(|(key, value)| Property { key, value })984			.collect();985986		let permissions = CollectionPermissions {987			access: Some(permissions.access()),988			mint_mode: Some(permissions.mint_mode()),989			nesting: Some(permissions.nesting().clone()),990		};991992		Some(RpcCollection {993			name: name.into_inner(),994			description: description.into_inner(),995			owner,996			mode,997			token_prefix: token_prefix.into_inner(),998			sponsorship,999			limits,1000			permissions,1001			token_property_permissions,1002			properties,1003			read_only: flags.external,10041005			flags: RpcCollectionFlags {1006				foreign: flags.foreign,1007				erc721metadata: flags.erc721metadata,1008			},1009		})1010	}1011}10121013macro_rules! limit_default {1014	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1015		$(1016			if let Some($new) = $new.$field {1017				let $old = $old.$field($($arg)?);1018				let _ = $new;1019				let _ = $old;1020				$check1021			} else {1022				$new.$field = $old.$field1023			}1024		)*1025	}};1026}1027macro_rules! limit_default_clone {1028	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1029		$(1030			if let Some($new) = $new.$field.clone() {1031				let $old = $old.$field($($arg)?);1032				let _ = $new;1033				let _ = $old;1034				$check1035			} else {1036				$new.$field = $old.$field.clone()1037			}1038		)*1039	}};1040}10411042impl<T: Config> Pallet<T> {1043	/// Create new collection.1044	///1045	/// * `owner` - The owner of the collection.1046	/// * `data` - Description of the created collection.1047	/// * `flags` - Extra flags to store.1048	pub fn init_collection(1049		owner: T::CrossAccountId,1050		payer: T::CrossAccountId,1051		data: CreateCollectionData<T::AccountId>,1052		flags: CollectionFlags,1053	) -> Result<CollectionId, DispatchError> {1054		{1055			ensure!(1056				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1057				Error::<T>::CollectionTokenPrefixLimitExceeded1058			);1059		}10601061		let created_count = <CreatedCollectionCount<T>>::get()1062			.01063			.checked_add(1)1064			.ok_or(ArithmeticError::Overflow)?;1065		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1066		let id = CollectionId(created_count);10671068		// bound Total number of collections1069		ensure!(1070			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1071			<Error<T>>::TotalCollectionsLimitExceeded1072		);10731074		// =========10751076		let collection = Collection {1077			owner: owner.as_sub().clone(),1078			name: data.name,1079			mode: data.mode.clone(),1080			description: data.description,1081			token_prefix: data.token_prefix,1082			sponsorship: data1083				.pending_sponsor1084				.map(SponsorshipState::Unconfirmed)1085				.unwrap_or_default(),1086			limits: data1087				.limits1088				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1089				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,1090			permissions: data1091				.permissions1092				.map(|permissions| {1093					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1094				})1095				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1096			flags,1097		};10981099		let mut collection_properties = CollectionPropertiesT::new();1100		collection_properties1101			.try_set_from_iter(data.properties.into_iter())1102			.map_err(<Error<T>>::from)?;11031104		CollectionProperties::<T>::insert(id, collection_properties);11051106		let mut token_props_permissions = PropertiesPermissionMap::new();1107		token_props_permissions1108			.try_set_from_iter(data.token_property_permissions.into_iter())1109			.map_err(<Error<T>>::from)?;11101111		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11121113		// Take a (non-refundable) deposit of collection creation1114		{1115			let mut imbalance =1116				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();1117			imbalance.subsume(1118				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(1119					&T::TreasuryAccountId::get(),1120					T::CollectionCreationPrice::get(),1121				),1122			);1123			<T as Config>::Currency::settle(1124				payer.as_sub(),1125				imbalance,1126				WithdrawReasons::TRANSFER,1127				ExistenceRequirement::KeepAlive,1128			)1129			.map_err(|_| Error::<T>::NotSufficientFounds)?;1130		}11311132		<CreatedCollectionCount<T>>::put(created_count);1133		<Pallet<T>>::deposit_event(Event::CollectionCreated(1134			id,1135			data.mode.id(),1136			owner.as_sub().clone(),1137		));1138		<PalletEvm<T>>::deposit_log(1139			erc::CollectionHelpersEvents::CollectionCreated {1140				owner: *owner.as_eth(),1141				collection_id: eth::collection_id_to_address(id),1142			}1143			.to_log(T::ContractAddress::get()),1144		);1145		<CollectionById<T>>::insert(id, collection);1146		Ok(id)1147	}11481149	/// Destroy collection.1150	///1151	/// * `collection` - Collection handler.1152	/// * `sender` - The owner or administrator of the collection.1153	pub fn destroy_collection(1154		collection: CollectionHandle<T>,1155		sender: &T::CrossAccountId,1156	) -> DispatchResult {1157		ensure!(1158			collection.limits.owner_can_destroy(),1159			<Error<T>>::NoPermission,1160		);1161		collection.check_is_owner(sender)?;11621163		let destroyed_collections = <DestroyedCollectionCount<T>>::get()1164			.01165			.checked_add(1)1166			.ok_or(ArithmeticError::Overflow)?;11671168		// =========11691170		<DestroyedCollectionCount<T>>::put(destroyed_collections);1171		<CollectionById<T>>::remove(collection.id);1172		<AdminAmount<T>>::remove(collection.id);1173		let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1174		let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1175		<CollectionProperties<T>>::remove(collection.id);11761177		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));11781179		<PalletEvm<T>>::deposit_log(1180			erc::CollectionHelpersEvents::CollectionDestroyed {1181				collection_id: eth::collection_id_to_address(collection.id),1182			}1183			.to_log(T::ContractAddress::get()),1184		);1185		Ok(())1186	}11871188	/// This function sets or removes a collection properties according to1189	/// `properties_updates` contents:1190	/// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1191	/// * removes a property under the <key> if the value is `None` `(<key>, None)`.1192	///1193	/// This function fires an event for each property change.1194	/// In case of an error, all the changes (including the events) will be reverted1195	/// since the function is transactional.1196	#[transactional]1197	fn modify_collection_properties(1198		collection: &CollectionHandle<T>,1199		sender: &T::CrossAccountId,1200		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1201	) -> DispatchResult {1202		collection.check_is_owner_or_admin(sender)?;12031204		let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12051206		for (key, value) in properties_updates {1207			match value {1208				Some(value) => {1209					stored_properties1210						.try_set(key.clone(), value)1211						.map_err(<Error<T>>::from)?;12121213					Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1214					<PalletEvm<T>>::deposit_log(1215						erc::CollectionHelpersEvents::CollectionChanged {1216							collection_id: eth::collection_id_to_address(collection.id),1217						}1218						.to_log(T::ContractAddress::get()),1219					);1220				}1221				None => {1222					stored_properties.remove(&key).map_err(<Error<T>>::from)?;12231224					Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1225					<PalletEvm<T>>::deposit_log(1226						erc::CollectionHelpersEvents::CollectionChanged {1227							collection_id: eth::collection_id_to_address(collection.id),1228						}1229						.to_log(T::ContractAddress::get()),1230					);1231				}1232			}1233		}12341235		<CollectionProperties<T>>::set(collection.id, stored_properties);12361237		Ok(())1238	}12391240	/// A batch operation to add, edit or remove properties for a token.1241	/// It sets or removes a token's properties according to1242	/// `properties_updates` contents:1243	/// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1244	/// * removes a property under the <key> if the value is `None` `(<key>, None)`.1245	///1246	/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.1247	/// - `is_token_create`: Indicates that method is called during token initialization.1248	///   Allows to bypass ownership check.1249	///1250	/// All affected properties should have `mutable` permission1251	/// to be **deleted** or to be **set more than once**,1252	/// and the sender should have permission to edit those properties.1253	///1254	/// This function fires an event for each property change.1255	/// In case of an error, all the changes (including the events) will be reverted1256	/// since the function is transactional.1257	pub fn modify_token_properties(1258		collection: &CollectionHandle<T>,1259		sender: &T::CrossAccountId,1260		token_id: TokenId,1261		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1262		is_token_create: bool,1263		mut stored_properties: TokenProperties,1264		is_token_owner: impl Fn() -> Result<bool, DispatchError>,1265		set_token_properties: impl FnOnce(TokenProperties),1266		log: evm_coder::ethereum::Log,1267	) -> DispatchResult {1268		let is_collection_admin = collection.is_owner_or_admin(sender);1269		let permissions = Self::property_permissions(collection.id);12701271		let mut token_owner_result = None;1272		let mut is_token_owner = || -> Result<bool, DispatchError> {1273			*token_owner_result.get_or_insert_with(&is_token_owner)1274		};12751276		for (key, value) in properties_updates {1277			let permission = permissions1278				.get(&key)1279				.cloned()1280				.unwrap_or_else(PropertyPermission::none);12811282			let is_property_exists = stored_properties.get(&key).is_some();12831284			match permission {1285				PropertyPermission { mutable: false, .. } if is_property_exists => {1286					return Err(<Error<T>>::NoPermission.into());1287				}12881289				PropertyPermission {1290					collection_admin,1291					token_owner,1292					..1293				} => {1294					//TODO: investigate threats during public minting.1295					let is_token_create =1296						is_token_create && (collection_admin || token_owner) && value.is_some();1297					if !(is_token_create1298						|| (collection_admin && is_collection_admin)1299						|| (token_owner && is_token_owner()?))1300					{1301						fail!(<Error<T>>::NoPermission);1302					}1303				}1304			}13051306			match value {1307				Some(value) => {1308					stored_properties1309						.try_set(key.clone(), value)1310						.map_err(<Error<T>>::from)?;13111312					Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1313				}1314				None => {1315					stored_properties.remove(&key).map_err(<Error<T>>::from)?;13161317					Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1318				}1319			}13201321			<PalletEvm<T>>::deposit_log(log.clone());1322		}13231324		set_token_properties(stored_properties);13251326		Ok(())1327	}13281329	/// Sets or unsets the approval of a given operator.1330	///1331	/// The `operator` is allowed to transfer all token pieces of the `owner` on their behalf.1332	/// - `owner`: Token owner1333	/// - `operator`: Operator1334	/// - `approve`: Should operator status be granted or revoked?1335	pub fn set_allowance_for_all(1336		collection: &CollectionHandle<T>,1337		owner: &T::CrossAccountId,1338		operator: &T::CrossAccountId,1339		approve: bool,1340		set_allowance: impl FnOnce(),1341		log: evm_coder::ethereum::Log,1342	) -> DispatchResult {1343		if collection.permissions.access() == AccessMode::AllowList {1344			collection.check_allowlist(owner)?;1345			collection.check_allowlist(operator)?;1346		}13471348		Self::ensure_correct_receiver(operator)?;13491350		set_allowance();13511352		<PalletEvm<T>>::deposit_log(log);1353		Self::deposit_event(Event::ApprovedForAll(1354			collection.id,1355			owner.clone(),1356			operator.clone(),1357			approve,1358		));1359		Ok(())1360	}13611362	/// Set collection property.1363	///1364	/// * `collection` - Collection handler.1365	/// * `sender` - The owner or administrator of the collection.1366	/// * `property` - The property to set.1367	pub fn set_collection_property(1368		collection: &CollectionHandle<T>,1369		sender: &T::CrossAccountId,1370		property: Property,1371	) -> DispatchResult {1372		Self::set_collection_properties(collection, sender, [property].into_iter())1373	}13741375	/// Set a scoped collection property, where the scope is a special prefix1376	/// prohibiting a user access to change the property directly.1377	///1378	/// * `collection_id` - ID of the collection for which the property is being set.1379	/// * `scope` - Property scope.1380	/// * `property` - The property to set.1381	pub fn set_scoped_collection_property(1382		collection_id: CollectionId,1383		scope: PropertyScope,1384		property: Property,1385	) -> DispatchResult {1386		CollectionProperties::<T>::try_mutate(collection_id, |properties| {1387			properties.try_scoped_set(scope, property.key, property.value)1388		})1389		.map_err(<Error<T>>::from)?;13901391		Ok(())1392	}13931394	/// Set scoped collection properties, where the scope is a special prefix1395	/// prohibiting a user access to change the properties directly.1396	///1397	/// * `collection_id` - ID of the collection for which the properties is being set.1398	/// * `scope` - Property scope.1399	/// * `properties` - The properties to set.1400	pub fn set_scoped_collection_properties(1401		collection_id: CollectionId,1402		scope: PropertyScope,1403		properties: impl Iterator<Item = Property>,1404	) -> DispatchResult {1405		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1406			stored_properties.try_scoped_set_from_iter(scope, properties)1407		})1408		.map_err(<Error<T>>::from)?;14091410		Ok(())1411	}14121413	/// Set collection properties.1414	///1415	/// * `collection` - Collection handler.1416	/// * `sender` - The owner or administrator of the collection.1417	/// * `properties` - The properties to set.1418	pub fn set_collection_properties(1419		collection: &CollectionHandle<T>,1420		sender: &T::CrossAccountId,1421		properties: impl Iterator<Item = Property>,1422	) -> DispatchResult {1423		Self::modify_collection_properties(1424			collection,1425			sender,1426			properties.map(|property| (property.key, Some(property.value))),1427		)1428	}14291430	/// Delete collection property.1431	///1432	/// * `collection` - Collection handler.1433	/// * `sender` - The owner or administrator of the collection.1434	/// * `property` - The property to delete.1435	pub fn delete_collection_property(1436		collection: &CollectionHandle<T>,1437		sender: &T::CrossAccountId,1438		property_key: PropertyKey,1439	) -> DispatchResult {1440		Self::delete_collection_properties(collection, sender, [property_key].into_iter())1441	}14421443	/// Delete collection properties.1444	///1445	/// * `collection` - Collection handler.1446	/// * `sender` - The owner or administrator of the collection.1447	/// * `properties` - The properties to delete.1448	pub fn delete_collection_properties(1449		collection: &CollectionHandle<T>,1450		sender: &T::CrossAccountId,1451		property_keys: impl Iterator<Item = PropertyKey>,1452	) -> DispatchResult {1453		Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1454	}14551456	/// Set collection propetry permission without any checks.1457	///1458	/// Used for migrations.1459	///1460	/// * `collection` - Collection handler.1461	/// * `property_permissions` - Property permissions.1462	pub fn set_property_permission_unchecked(1463		collection: CollectionId,1464		property_permission: PropertyKeyPermission,1465	) -> DispatchResult {1466		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1467			permissions.try_set(property_permission.key, property_permission.permission)1468		})1469		.map_err(<Error<T>>::from)?;1470		Ok(())1471	}14721473	/// Set collection property permission.1474	///1475	/// * `collection` - Collection handler.1476	/// * `sender` - The owner or administrator of the collection.1477	/// * `property_permission` - Property permission.1478	pub fn set_property_permission(1479		collection: &CollectionHandle<T>,1480		sender: &T::CrossAccountId,1481		property_permission: PropertyKeyPermission,1482	) -> DispatchResult {1483		Self::set_scoped_property_permission(1484			collection,1485			sender,1486			PropertyScope::None,1487			property_permission,1488		)1489	}14901491	/// Set collection property permission with scope.1492	///1493	/// * `collection` - Collection handler.1494	/// * `sender` - The owner or administrator of the collection.1495	/// * `scope` - Property scope.1496	/// * `property_permission` - Property permission.1497	pub fn set_scoped_property_permission(1498		collection: &CollectionHandle<T>,1499		sender: &T::CrossAccountId,1500		scope: PropertyScope,1501		property_permission: PropertyKeyPermission,1502	) -> DispatchResult {1503		collection.check_is_owner_or_admin(sender)?;15041505		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1506		let current_permission = all_permissions.get(&property_permission.key);1507		if matches![1508			current_permission,1509			Some(PropertyPermission { mutable: false, .. })1510		] {1511			return Err(<Error<T>>::NoPermission.into());1512		}15131514		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1515			let property_permission = property_permission.clone();1516			permissions.try_scoped_set(1517				scope,1518				property_permission.key,1519				property_permission.permission,1520			)1521		})1522		.map_err(<Error<T>>::from)?;15231524		Self::deposit_event(Event::PropertyPermissionSet(1525			collection.id,1526			property_permission.key,1527		));1528		<PalletEvm<T>>::deposit_log(1529			erc::CollectionHelpersEvents::CollectionChanged {1530				collection_id: eth::collection_id_to_address(collection.id),1531			}1532			.to_log(T::ContractAddress::get()),1533		);15341535		Ok(())1536	}15371538	/// Set token property permission.1539	///1540	/// * `collection` - Collection handler.1541	/// * `sender` - The owner or administrator of the collection.1542	/// * `property_permissions` - Property permissions.1543	#[transactional]1544	pub fn set_token_property_permissions(1545		collection: &CollectionHandle<T>,1546		sender: &T::CrossAccountId,1547		property_permissions: Vec<PropertyKeyPermission>,1548	) -> DispatchResult {1549		Self::set_scoped_token_property_permissions(1550			collection,1551			sender,1552			PropertyScope::None,1553			property_permissions,1554		)1555	}15561557	/// Set token property permission with scope.1558	///1559	/// * `collection` - Collection handler.1560	/// * `sender` - The owner or administrator of the collection.1561	/// * `scope` - Property scope.1562	/// * `property_permissions` - Property permissions.1563	#[transactional]1564	pub fn set_scoped_token_property_permissions(1565		collection: &CollectionHandle<T>,1566		sender: &T::CrossAccountId,1567		scope: PropertyScope,1568		property_permissions: Vec<PropertyKeyPermission>,1569	) -> DispatchResult {1570		for prop_pemission in property_permissions {1571			Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1572		}15731574		Ok(())1575	}15761577	/// Get collection property.1578	pub fn get_collection_property(1579		collection_id: CollectionId,1580		key: &PropertyKey,1581	) -> Option<PropertyValue> {1582		Self::collection_properties(collection_id).get(key).cloned()1583	}15841585	/// Convert byte vector to property key vector.1586	pub fn bytes_keys_to_property_keys(1587		keys: Vec<Vec<u8>>,1588	) -> Result<Vec<PropertyKey>, DispatchError> {1589		keys.into_iter()1590			.map(|key| -> Result<PropertyKey, DispatchError> {1591				key.try_into()1592					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1593			})1594			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1595	}15961597	/// Get properties according to given keys.1598	pub fn filter_collection_properties(1599		collection_id: CollectionId,1600		keys: Option<Vec<PropertyKey>>,1601	) -> Result<Vec<Property>, DispatchError> {1602		let properties = Self::collection_properties(collection_id);16031604		let properties = keys1605			.map(|keys| {1606				keys.into_iter()1607					.filter_map(|key| {1608						properties.get(&key).map(|value| Property {1609							key,1610							value: value.clone(),1611						})1612					})1613					.collect()1614			})1615			.unwrap_or_else(|| {1616				properties1617					.into_iter()1618					.map(|(key, value)| Property { key, value })1619					.collect()1620			});16211622		Ok(properties)1623	}16241625	/// Get property permissions according to given keys.1626	pub fn filter_property_permissions(1627		collection_id: CollectionId,1628		keys: Option<Vec<PropertyKey>>,1629	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1630		let permissions = Self::property_permissions(collection_id);16311632		let key_permissions = keys1633			.map(|keys| {1634				keys.into_iter()1635					.filter_map(|key| {1636						permissions1637							.get(&key)1638							.map(|permission| PropertyKeyPermission {1639								key,1640								permission: permission.clone(),1641							})1642					})1643					.collect()1644			})1645			.unwrap_or_else(|| {1646				permissions1647					.into_iter()1648					.map(|(key, permission)| PropertyKeyPermission { key, permission })1649					.collect()1650			});16511652		Ok(key_permissions)1653	}16541655	/// Toggle `user` participation in the `collection`'s allow list.1656	/// #### Store read/writes1657	/// 1 writes1658	pub fn toggle_allowlist(1659		collection: &CollectionHandle<T>,1660		sender: &T::CrossAccountId,1661		user: &T::CrossAccountId,1662		allowed: bool,1663	) -> DispatchResult {1664		collection.check_is_owner_or_admin(sender)?;16651666		// =========16671668		if allowed {1669			<Allowlist<T>>::insert((collection.id, user), true);1670			Self::deposit_event(Event::<T>::AllowListAddressAdded(1671				collection.id,1672				user.clone(),1673			));1674		} else {1675			<Allowlist<T>>::remove((collection.id, user));1676			Self::deposit_event(Event::<T>::AllowListAddressRemoved(1677				collection.id,1678				user.clone(),1679			));1680		}16811682		<PalletEvm<T>>::deposit_log(1683			erc::CollectionHelpersEvents::CollectionChanged {1684				collection_id: eth::collection_id_to_address(collection.id),1685			}1686			.to_log(T::ContractAddress::get()),1687		);16881689		Ok(())1690	}16911692	/// Toggle `user` participation in the `collection`'s admin list.1693	/// #### Store read/writes1694	/// 2 reads, 2 writes1695	pub fn toggle_admin(1696		collection: &CollectionHandle<T>,1697		sender: &T::CrossAccountId,1698		user: &T::CrossAccountId,1699		admin: bool,1700	) -> DispatchResult {1701		collection.check_is_internal()?;1702		collection.check_is_owner(sender)?;17031704		let is_admin = <IsAdmin<T>>::get((collection.id, user));1705		if is_admin == admin {1706			if admin {1707				return Ok(());1708			} else {1709				return Err(Error::<T>::UserIsNotCollectionAdmin.into());1710			}1711		}1712		let amount = <AdminAmount<T>>::get(collection.id);17131714		// =========17151716		if admin {1717			let amount = amount1718				.checked_add(1)1719				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1720			ensure!(1721				amount <= Self::collection_admins_limit(),1722				<Error<T>>::CollectionAdminCountExceeded,1723			);17241725			<AdminAmount<T>>::insert(collection.id, amount);1726			<IsAdmin<T>>::insert((collection.id, user), true);17271728			Self::deposit_event(Event::<T>::CollectionAdminAdded(1729				collection.id,1730				user.clone(),1731			));1732		} else {1733			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1734			<IsAdmin<T>>::remove((collection.id, user));17351736			Self::deposit_event(Event::<T>::CollectionAdminRemoved(1737				collection.id,1738				user.clone(),1739			));1740		}17411742		<PalletEvm<T>>::deposit_log(1743			erc::CollectionHelpersEvents::CollectionChanged {1744				collection_id: eth::collection_id_to_address(collection.id),1745			}1746			.to_log(T::ContractAddress::get()),1747		);17481749		Ok(())1750	}17511752	/// Update collection limits.1753	pub fn update_limits(1754		user: &T::CrossAccountId,1755		collection: &mut CollectionHandle<T>,1756		new_limit: CollectionLimits,1757	) -> DispatchResult {1758		collection.check_is_internal()?;1759		collection.check_is_owner_or_admin(user)?;17601761		collection.limits =1762			Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17631764		Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1765		<PalletEvm<T>>::deposit_log(1766			erc::CollectionHelpersEvents::CollectionChanged {1767				collection_id: eth::collection_id_to_address(collection.id),1768			}1769			.to_log(T::ContractAddress::get()),1770		);17711772		collection.save()1773	}17741775	/// Merge set fields from `new_limit` to `old_limit`.1776	fn clamp_limits(1777		mode: CollectionMode,1778		old_limit: &CollectionLimits,1779		mut new_limit: CollectionLimits,1780	) -> Result<CollectionLimits, DispatchError> {1781		let limits = old_limit;1782		limit_default!(old_limit, new_limit,1783			account_token_ownership_limit => ensure!(1784				new_limit <= MAX_TOKEN_OWNERSHIP,1785				<Error<T>>::CollectionLimitBoundsExceeded,1786			),1787			sponsored_data_size => ensure!(1788				new_limit <= CUSTOM_DATA_LIMIT,1789				<Error<T>>::CollectionLimitBoundsExceeded,1790			),17911792			sponsored_data_rate_limit => {},1793			token_limit => ensure!(1794				old_limit >= new_limit && new_limit > 0,1795				<Error<T>>::CollectionTokenLimitExceeded1796			),17971798			sponsor_transfer_timeout(match mode {1799				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1800				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1801				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1802			}) => ensure!(1803				new_limit <= MAX_SPONSOR_TIMEOUT,1804				<Error<T>>::CollectionLimitBoundsExceeded,1805			),1806			sponsor_approve_timeout => {},1807			owner_can_transfer => ensure!(1808				!limits.owner_can_transfer_instaled() ||1809				old_limit || !new_limit,1810				<Error<T>>::OwnerPermissionsCantBeReverted,1811			),1812			owner_can_destroy => ensure!(1813				old_limit || !new_limit,1814				<Error<T>>::OwnerPermissionsCantBeReverted,1815			),1816			transfers_enabled => {},1817		);1818		Ok(new_limit)1819	}18201821	/// Update collection permissions.1822	pub fn update_permissions(1823		user: &T::CrossAccountId,1824		collection: &mut CollectionHandle<T>,1825		new_permission: CollectionPermissions,1826	) -> DispatchResult {1827		collection.check_is_internal()?;1828		collection.check_is_owner_or_admin(user)?;1829		collection.permissions = Self::clamp_permissions(1830			collection.mode.clone(),1831			&collection.permissions,1832			new_permission,1833		)?;18341835		Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1836		<PalletEvm<T>>::deposit_log(1837			erc::CollectionHelpersEvents::CollectionChanged {1838				collection_id: eth::collection_id_to_address(collection.id),1839			}1840			.to_log(T::ContractAddress::get()),1841		);18421843		collection.save()1844	}18451846	/// Merge set fields from `new_permission` to `old_permission`.1847	fn clamp_permissions(1848		_mode: CollectionMode,1849		old_permission: &CollectionPermissions,1850		mut new_permission: CollectionPermissions,1851	) -> Result<CollectionPermissions, DispatchError> {1852		limit_default_clone!(old_permission, new_permission,1853			access => {},1854			mint_mode => {},1855			nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1856		);1857		Ok(new_permission)1858	}18591860	/// Repair possibly broken properties of a collection.1861	pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1862		CollectionProperties::<T>::mutate(collection_id, |properties| {1863			properties.recompute_consumed_space();1864		});18651866		Ok(())1867	}1868}18691870/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1871#[macro_export]1872macro_rules! unsupported {1873	($runtime:path) => {1874		Err($crate::Error::<$runtime>::UnsupportedOperation.into())1875	};1876}18771878/// Return weights for various worst-case operations.1879pub trait CommonWeightInfo<CrossAccountId> {1880	/// Weight of item creation.1881	fn create_item(data: &CreateItemData) -> Weight {1882		Self::create_multiple_items(from_ref(data))1883	}18841885	/// Weight of items creation.1886	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18871888	/// Weight of items creation.1889	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18901891	/// The weight of the burning item.1892	fn burn_item() -> Weight;18931894	/// Property setting weight.1895	///1896	/// * `amount`- The number of properties to set.1897	fn set_collection_properties(amount: u32) -> Weight;18981899	/// Collection property deletion weight.1900	///1901	/// * `amount`- The number of properties to set.1902	fn delete_collection_properties(amount: u32) -> Weight;19031904	/// Token property setting weight.1905	///1906	/// * `amount`- The number of properties to set.1907	fn set_token_properties(amount: u32) -> Weight;19081909	/// Token property deletion weight.1910	///1911	/// * `amount`- The number of properties to delete.1912	fn delete_token_properties(amount: u32) -> Weight;19131914	/// Token property permissions set weight.1915	///1916	/// * `amount`- The number of property permissions to set.1917	fn set_token_property_permissions(amount: u32) -> Weight;19181919	/// Transfer price of the token or its parts.1920	fn transfer() -> Weight;19211922	/// The price of setting the permission of the operation from another user.1923	fn approve() -> Weight;19241925	/// The price of setting the permission of the operation from another user for eth mirror.1926	fn approve_from() -> Weight;19271928	/// Transfer price from another user.1929	fn transfer_from() -> Weight;19301931	/// The price of burning a token from another user.1932	fn burn_from() -> Weight;19331934	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1935	/// whole users's balance.1936	///1937	/// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1938	fn burn_recursively_self_raw() -> Weight;19391940	/// Cost of iterating over `amount` children while burning, without counting child burning itself.1941	///1942	/// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1943	fn burn_recursively_breadth_raw(amount: u32) -> Weight;19441945	/// The price of recursive burning a token.1946	///1947	/// `max_selfs` - The maximum burning weight of the token itself.1948	/// `max_breadth` - The maximum number of nested tokens to burn.1949	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1950		Self::burn_recursively_self_raw()1951			.saturating_mul(max_selfs.max(1) as u64)1952			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1953	}19541955	/// The price of retrieving token owner1956	fn token_owner() -> Weight;19571958	/// The price of setting approval for all1959	fn set_allowance_for_all() -> Weight;19601961	/// The price of repairing an item.1962	fn force_repair_item() -> Weight;1963}19641965/// Weight info extension trait for refungible pallet.1966pub trait RefungibleExtensionsWeightInfo {1967	/// Weight of token repartition.1968	fn repartition() -> Weight;1969}19701971/// Common collection operations.1972///1973/// It wraps methods in Fungible, Nonfungible and Refungible pallets1974/// and adds weight info.1975pub trait CommonCollectionOperations<T: Config> {1976	/// Create token.1977	///1978	/// * `sender` - The user who mint the token and pays for the transaction.1979	/// * `to` - The user who will own the token.1980	/// * `data` - Token data.1981	/// * `nesting_budget` - A budget that can be spent on nesting tokens.1982	fn create_item(1983		&self,1984		sender: T::CrossAccountId,1985		to: T::CrossAccountId,1986		data: CreateItemData,1987		nesting_budget: &dyn Budget,1988	) -> DispatchResultWithPostInfo;19891990	/// Create multiple tokens.1991	///1992	/// * `sender` - The user who mint the token and pays for the transaction.1993	/// * `to` - The user who will own the token.1994	/// * `data` - Token data.1995	/// * `nesting_budget` - A budget that can be spent on nesting tokens.1996	fn create_multiple_items(1997		&self,1998		sender: T::CrossAccountId,1999		to: T::CrossAccountId,2000		data: Vec<CreateItemData>,2001		nesting_budget: &dyn Budget,2002	) -> DispatchResultWithPostInfo;20032004	/// Create multiple tokens.2005	///2006	/// * `sender` - The user who mint the token and pays for the transaction.2007	/// * `to` - The user who will own the token.2008	/// * `data` - Token data.2009	/// * `nesting_budget` - A budget that can be spent on nesting tokens.2010	fn create_multiple_items_ex(2011		&self,2012		sender: T::CrossAccountId,2013		data: CreateItemExData<T::CrossAccountId>,2014		nesting_budget: &dyn Budget,2015	) -> DispatchResultWithPostInfo;20162017	/// Burn token.2018	///2019	/// * `sender` - The user who owns the token.2020	/// * `token` - Token id that will burned.2021	/// * `amount` - The number of parts of the token that will be burned.2022	fn burn_item(2023		&self,2024		sender: T::CrossAccountId,2025		token: TokenId,2026		amount: u128,2027	) -> DispatchResultWithPostInfo;20282029	/// Burn token and all nested tokens recursievly.2030	///2031	/// * `sender` - The user who owns the token.2032	/// * `token` - Token id that will burned.2033	/// * `self_budget` - The budget that can be spent on burning tokens.2034	/// * `breadth_budget` - The budget that can be spent on burning nested tokens.2035	fn burn_item_recursively(2036		&self,2037		sender: T::CrossAccountId,2038		token: TokenId,2039		self_budget: &dyn Budget,2040		breadth_budget: &dyn Budget,2041	) -> DispatchResultWithPostInfo;20422043	/// Set collection properties.2044	///2045	/// * `sender` - Must be either the owner of the collection or its admin.2046	/// * `properties` - Properties to be set.2047	fn set_collection_properties(2048		&self,2049		sender: T::CrossAccountId,2050		properties: Vec<Property>,2051	) -> DispatchResultWithPostInfo;20522053	/// Delete collection properties.2054	///2055	/// * `sender` - Must be either the owner of the collection or its admin.2056	/// * `properties` - The properties to be removed.2057	fn delete_collection_properties(2058		&self,2059		sender: &T::CrossAccountId,2060		property_keys: Vec<PropertyKey>,2061	) -> DispatchResultWithPostInfo;20622063	/// Set token properties.2064	///2065	/// The appropriate [`PropertyPermission`] for the token property2066	/// must be set with [`Self::set_token_property_permissions`].2067	///2068	/// * `sender` - Must be either the owner of the token or its admin.2069	/// * `token_id` - The token for which the properties are being set.2070	/// * `properties` - Properties to be set.2071	/// * `budget` - Budget for setting properties.2072	fn set_token_properties(2073		&self,2074		sender: T::CrossAccountId,2075		token_id: TokenId,2076		properties: Vec<Property>,2077		budget: &dyn Budget,2078	) -> DispatchResultWithPostInfo;20792080	/// Remove token properties.2081	///2082	/// The appropriate [`PropertyPermission`] for the token property2083	/// must be set with [`Self::set_token_property_permissions`].2084	///2085	/// * `sender` - Must be either the owner of the token or its admin.2086	/// * `token_id` - The token for which the properties are being remove.2087	/// * `property_keys` - Keys to remove corresponding properties.2088	/// * `budget` - Budget for removing properties.2089	fn delete_token_properties(2090		&self,2091		sender: T::CrossAccountId,2092		token_id: TokenId,2093		property_keys: Vec<PropertyKey>,2094		budget: &dyn Budget,2095	) -> DispatchResultWithPostInfo;20962097	/// Set token property permissions.2098	///2099	/// * `sender` - Must be either the owner of the token or its admin.2100	/// * `token_id` - The token for which the properties are being set.2101	/// * `property_permissions` - Property permissions to be set.2102	/// * `budget` - Budget for setting properties.2103	fn set_token_property_permissions(2104		&self,2105		sender: &T::CrossAccountId,2106		property_permissions: Vec<PropertyKeyPermission>,2107	) -> DispatchResultWithPostInfo;21082109	/// Transfer amount of token pieces.2110	///2111	/// * `sender` - Donor user.2112	/// * `to` - Recepient user.2113	/// * `token` - The token of which parts are being sent.2114	/// * `amount` - The number of parts of the token that will be transferred.2115	/// * `budget` - The maximum budget that can be spent on the transfer.2116	fn transfer(2117		&self,2118		sender: T::CrossAccountId,2119		to: T::CrossAccountId,2120		token: TokenId,2121		amount: u128,2122		budget: &dyn Budget,2123	) -> DispatchResultWithPostInfo;21242125	/// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].2126	///2127	/// * `sender` - The user who grants access to the token.2128	/// * `spender` - The user to whom the rights are granted.2129	/// * `token` - The token to which access is granted.2130	/// * `amount` - The amount of pieces that another user can dispose of.2131	fn approve(2132		&self,2133		sender: T::CrossAccountId,2134		spender: T::CrossAccountId,2135		token: TokenId,2136		amount: u128,2137	) -> DispatchResultWithPostInfo;21382139	/// Grant access to another account to transfer parts of the token owned by the calling user's eth mirror via [Self::transfer_from].2140	///2141	/// * `sender` - The user who grants access to the token.2142	/// * `from` - Spender's eth mirror.2143	/// * `to` - The user to whom the rights are granted.2144	/// * `token` - The token to which access is granted.2145	/// * `amount` - The amount of pieces that another user can dispose of.2146	fn approve_from(2147		&self,2148		sender: T::CrossAccountId,2149		from: T::CrossAccountId,2150		to: T::CrossAccountId,2151		token: TokenId,2152		amount: u128,2153	) -> DispatchResultWithPostInfo;21542155	/// Send parts of a token owned by another user.2156	///2157	/// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2158	///2159	/// * `sender` - The user who must have access to the token (see [`Self::approve`]).2160	/// * `from` - The user who owns the token.2161	/// * `to` - Recepient user.2162	/// * `token` - The token of which parts are being sent.2163	/// * `amount` - The number of parts of the token that will be transferred.2164	/// * `budget` - The maximum budget that can be spent on the transfer.2165	fn transfer_from(2166		&self,2167		sender: T::CrossAccountId,2168		from: T::CrossAccountId,2169		to: T::CrossAccountId,2170		token: TokenId,2171		amount: u128,2172		budget: &dyn Budget,2173	) -> DispatchResultWithPostInfo;21742175	/// Burn parts of a token owned by another user.2176	///2177	/// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2178	///2179	/// * `sender` - The user who must have access to the token (see [`Self::approve`]).2180	/// * `from` - The user who owns the token.2181	/// * `token` - The token of which parts are being sent.2182	/// * `amount` - The number of parts of the token that will be transferred.2183	/// * `budget` - The maximum budget that can be spent on the burn.2184	fn burn_from(2185		&self,2186		sender: T::CrossAccountId,2187		from: T::CrossAccountId,2188		token: TokenId,2189		amount: u128,2190		budget: &dyn Budget,2191	) -> DispatchResultWithPostInfo;21922193	/// Check permission to nest token.2194	///2195	/// * `sender` - The user who initiated the check.2196	/// * `from` - The token that is checked for embedding.2197	/// * `under` - Token under which to check.2198	/// * `budget` - The maximum budget that can be spent on the check.2199	fn check_nesting(2200		&self,2201		sender: T::CrossAccountId,2202		from: (CollectionId, TokenId),2203		under: TokenId,2204		budget: &dyn Budget,2205	) -> DispatchResult;22062207	/// Nest one token into another.2208	///2209	/// * `under` - Token holder.2210	/// * `to_nest` - Nested token.2211	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22122213	/// Unnest token.2214	///2215	/// * `under` - Token holder.2216	/// * `to_nest` - Token to unnest.2217	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22182219	/// Get all user tokens.2220	///2221	/// * `account` - Account for which you need to get tokens.2222	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22232224	/// Get all the tokens in the collection.2225	fn collection_tokens(&self) -> Vec<TokenId>;22262227	/// Check if the token exists.2228	///2229	/// * `token` - Id token to check.2230	fn token_exists(&self, token: TokenId) -> bool;22312232	/// Get the id of the last minted token.2233	fn last_token_id(&self) -> TokenId;22342235	/// Get the owner of the token.2236	///2237	/// * `token` - The token for which you need to find out the owner.2238	fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22392240	/// Returns 10 tokens owners in no particular order.2241	///2242	/// * `token` - The token for which you need to find out the owners.2243	fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22442245	/// Get the value of the token property by key.2246	///2247	/// * `token` - Token with the property to get.2248	/// * `key` - Property name.2249	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22502251	/// Get a set of token properties by key vector.2252	///2253	/// * `token` - Token with the property to get.2254	/// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),2255	/// then all properties are returned.2256	fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22572258	/// Amount of unique collection tokens2259	fn total_supply(&self) -> u32;22602261	/// Amount of different tokens account has.2262	///2263	/// * `account` - The account for which need to get the balance.2264	fn account_balance(&self, account: T::CrossAccountId) -> u32;22652266	/// Amount of specific token account have.2267	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22682269	/// Amount of token pieces2270	fn total_pieces(&self, token: TokenId) -> Option<u128>;22712272	/// Get the number of parts of the token that a trusted user can manage.2273	///2274	/// * `sender` - Trusted user.2275	/// * `spender` - Owner of the token.2276	/// * `token` - The token for which to get the value.2277	fn allowance(2278		&self,2279		sender: T::CrossAccountId,2280		spender: T::CrossAccountId,2281		token: TokenId,2282	) -> u128;22832284	/// Get extension for RFT collection.2285	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22862287	/// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.2288	/// * `owner` - Token owner2289	/// * `operator` - Operator2290	/// * `approve` - Should operator status be granted or revoked?2291	fn set_allowance_for_all(2292		&self,2293		owner: T::CrossAccountId,2294		operator: T::CrossAccountId,2295		approve: bool,2296	) -> DispatchResultWithPostInfo;22972298	/// Tells whether the given `owner` approves the `operator`.2299	fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23002301	/// Repairs a possibly broken item.2302	fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2303}23042305/// Extension for RFT collection.2306pub trait RefungibleExtensions<T>2307where2308	T: Config,2309{2310	/// Change the number of parts of the token.2311	///2312	/// When the value changes down, this function is equivalent to burning parts of the token.2313	///2314	/// * `sender` - The user calling the repartition operation. Must be the owner of the token.2315	/// * `token` - The token for which you want to change the number of parts.2316	/// * `amount` - The new value of the parts of the token.2317	fn repartition(2318		&self,2319		sender: &T::CrossAccountId,2320		token: TokenId,2321		amount: u128,2322	) -> DispatchResultWithPostInfo;2323}23242325/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].2326///2327/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.2328pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2329	let post_info = PostDispatchInfo {2330		actual_weight: Some(weight),2331		pays_fee: Pays::Yes,2332	};2333	match res {2334		Ok(()) => Ok(post_info),2335		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2336	}2337}23382339impl<T: Config> From<PropertiesError> for Error<T> {2340	fn from(error: PropertiesError) -> Self {2341		match error {2342			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2343			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2344			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2345			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2346			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2347		}2348	}2349}
after · pallets/common/src/lib.rs
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//! # Common pallet18//!19//! The Common pallet provides functionality for handling collections.20//!21//! ## Overview22//!23//! The Common pallet provides an interface for common collection operations for different collection types24//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.25//! It also provides this functionality to EVM, see [erc] and [eth] modules.26//!27//! The Common pallet provides functions for:28//!29//! - Setting and approving collection sponsor.30//! - Get\set\delete allow list.31//! - Get\set\delete collection properties.32//! - Get\set\delete collection property permissions.33//! - Get\set\delete token property permissions.34//! - Get\set\delete collection administrators.35//! - Checking access permissions.36//!37//! ### Terminology38//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will39//! be possible to mint tokens.40//!41//! **Allow list** - List of users who have the right to minting tokens.42//!43//! **Collection properties** - Collection properties are simply key-value stores where various44//! metadata can be placed.45//!46//! **Permissions on token properties** - For each property in the token can be set permission47//! to change, see [`PropertyPermission`].48//!49//! **Collection administrator** - For a collection, you can set administrators who have the right50//! to most actions on the collection.5152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::{57	ops::{Deref, DerefMut},58	slice::from_ref,59};60use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};61use sp_std::vec::Vec;62use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};63use evm_coder::ToLog;64use frame_support::{65	dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},66	ensure,67	traits::{68		Get,69		fungible::{Balanced, Debt, Inspect},70		tokens::{Imbalance, Precision, Preservation},71	},72	dispatch::Pays,73	transactional, fail,74};75use pallet_evm::GasWeightMapping;76use up_data_structs::{77	AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionFlags,78	RpcCollectionFlags, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,79	COLLECTION_ADMINS_LIMIT, TokenId, TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP,80	CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,81	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,82	CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit, budget::Budget,83	PhantomType, Property, CollectionProperties as CollectionPropertiesT, TokenProperties,84	PropertiesPermissionMap, PropertyKey, PropertyValue, PropertyPermission, PropertiesError,85	TokenOwnerError, PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope,86	CollectionPermissions,87};88use up_pov_estimate_rpc::PovInfo;8990pub use pallet::*;91use sp_core::H160;92use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, traits::Zero};9394#[cfg(feature = "runtime-benchmarks")]95pub mod benchmarking;96pub mod dispatch;97pub mod erc;98pub mod eth;99pub mod helpers;100#[allow(missing_docs)]101pub mod weights;102/// Weight info.103pub type SelfWeightOf<T> = <T as Config>::WeightInfo;104105/// Collection handle contains information about collection data and id.106/// Also provides functionality to count consumed gas.107///108/// CollectionHandle is used as a generic wrapper for collections of all types.109/// It allows to perform common operations and queries on any collection type,110/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].111#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]112pub struct CollectionHandle<T: Config> {113	/// Collection id114	pub id: CollectionId,115	collection: Collection<T::AccountId>,116	/// Substrate recorder for counting consumed gas117	pub recorder: SubstrateRecorder<T>,118}119120impl<T: Config> WithRecorder<T> for CollectionHandle<T> {121	fn recorder(&self) -> &SubstrateRecorder<T> {122		&self.recorder123	}124	fn into_recorder(self) -> SubstrateRecorder<T> {125		self.recorder126	}127}128129impl<T: Config> CollectionHandle<T> {130	/// Same as [CollectionHandle::new] but with an explicit gas limit.131	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {132		<CollectionById<T>>::get(id).map(|collection| Self {133			id,134			collection,135			recorder: SubstrateRecorder::new(gas_limit),136		})137	}138139	/// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].140	pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {141		<CollectionById<T>>::get(id).map(|collection| Self {142			id,143			collection,144			recorder,145		})146	}147148	/// Retrives collection data from storage and creates collection handle with default parameters.149	/// If collection not found return `None`150	pub fn new(id: CollectionId) -> Option<Self> {151		Self::new_with_gas_limit(id, u64::MAX)152	}153154	/// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.155	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {156		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)157	}158159	/// Consume gas for reading.160	pub fn consume_store_reads(161		&self,162		reads: u64,163	) -> pallet_evm_coder_substrate::execution::Result<()> {164		self.recorder165			.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(166				<T as frame_system::Config>::DbWeight::get()167					.read168					.saturating_mul(reads),169				// TODO: measure proof170				0,171			)))172	}173174	/// Consume gas for writing.175	pub fn consume_store_writes(176		&self,177		writes: u64,178	) -> pallet_evm_coder_substrate::execution::Result<()> {179		self.recorder180			.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(181				<T as frame_system::Config>::DbWeight::get()182					.write183					.saturating_mul(writes),184				// TODO: measure proof185				0,186			)))187	}188189	/// Consume gas for reading and writing.190	pub fn consume_store_reads_and_writes(191		&self,192		reads: u64,193		writes: u64,194	) -> pallet_evm_coder_substrate::execution::Result<()> {195		let weight = <T as frame_system::Config>::DbWeight::get();196		let reads = weight.read.saturating_mul(reads);197		let writes = weight.read.saturating_mul(writes);198		self.recorder199			.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(200				reads.saturating_add(writes),201				// TODO: measure proof202				0,203			)))204	}205206	/// Save collection to storage.207	pub fn save(&self) -> DispatchResult {208		<CollectionById<T>>::insert(self.id, &self.collection);209		Ok(())210	}211212	/// Set collection sponsor.213	///214	/// Unique collections allows sponsoring for certain actions.215	/// This method allows you to set the sponsor of the collection.216	/// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].217	pub fn set_sponsor(218		&mut self,219		sender: &T::CrossAccountId,220		sponsor: T::AccountId,221	) -> DispatchResult {222		self.check_is_internal()?;223		self.check_is_owner_or_admin(sender)?;224225		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());226227		<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));228		<PalletEvm<T>>::deposit_log(229			erc::CollectionHelpersEvents::CollectionChanged {230				collection_id: eth::collection_id_to_address(self.id),231			}232			.to_log(T::ContractAddress::get()),233		);234235		self.save()236	}237238	/// Force set `sponsor`.239	///240	/// Differs from [`set_sponsor`][`Self::set_sponsor`] in that confirmation241	/// from the `sponsor` is not required.242	///243	/// # Arguments244	///245	/// * `sender`: Caller's account.246	/// * `sponsor`: ID of the account of the sponsor-to-be.247	pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {248		self.check_is_internal()?;249250		self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());251252		<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));253		<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));254		<PalletEvm<T>>::deposit_log(255			erc::CollectionHelpersEvents::CollectionChanged {256				collection_id: eth::collection_id_to_address(self.id),257			}258			.to_log(T::ContractAddress::get()),259		);260261		self.save()262	}263264	/// Confirm sponsorship265	///266	/// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.267	/// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].268	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {269		self.check_is_internal()?;270		ensure!(271			self.collection.sponsorship.pending_sponsor() == Some(sender),272			Error::<T>::ConfirmSponsorshipFail273		);274275		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());276277		<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));278		<PalletEvm<T>>::deposit_log(279			erc::CollectionHelpersEvents::CollectionChanged {280				collection_id: eth::collection_id_to_address(self.id),281			}282			.to_log(T::ContractAddress::get()),283		);284285		self.save()286	}287288	/// Remove collection sponsor.289	pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {290		self.check_is_internal()?;291		self.check_is_owner_or_admin(sender)?;292293		self.collection.sponsorship = SponsorshipState::Disabled;294295		<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));296		<PalletEvm<T>>::deposit_log(297			erc::CollectionHelpersEvents::CollectionChanged {298				collection_id: eth::collection_id_to_address(self.id),299			}300			.to_log(T::ContractAddress::get()),301		);302		self.save()303	}304305	/// Force remove `sponsor`.306	///307	/// Differs from `remove_sponsor` in that308	/// it doesn't require consent from the `owner` of the collection.309	pub fn force_remove_sponsor(&mut self) -> DispatchResult {310		self.check_is_internal()?;311312		self.collection.sponsorship = SponsorshipState::Disabled;313314		<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));315		<PalletEvm<T>>::deposit_log(316			erc::CollectionHelpersEvents::CollectionChanged {317				collection_id: eth::collection_id_to_address(self.id),318			}319			.to_log(T::ContractAddress::get()),320		);321		self.save()322	}323324	/// Checks that the collection was created with, and must be operated upon through **Unique API**.325	/// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.326	pub fn check_is_internal(&self) -> DispatchResult {327		if self.flags.external {328			return Err(<Error<T>>::CollectionIsExternal)?;329		}330331		Ok(())332	}333334	/// Checks that the collection was created with, and must be operated upon through an **assimilated API**.335	/// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.336	pub fn check_is_external(&self) -> DispatchResult {337		if !self.flags.external {338			return Err(<Error<T>>::CollectionIsInternal)?;339		}340341		Ok(())342	}343}344345impl<T: Config> Deref for CollectionHandle<T> {346	type Target = Collection<T::AccountId>;347348	fn deref(&self) -> &Self::Target {349		&self.collection350	}351}352353impl<T: Config> DerefMut for CollectionHandle<T> {354	fn deref_mut(&mut self) -> &mut Self::Target {355		&mut self.collection356	}357}358359impl<T: Config> CollectionHandle<T> {360	/// Checks if the `user` is the owner of the collection.361	pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {362		ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);363		Ok(())364	}365366	/// Returns **true** if the `user` is the owner or administrator of the collection.367	pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {368		*user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))369	}370371	/// Checks if the `user` is the owner or administrator of the collection.372	pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {373		ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);374		Ok(())375	}376377	/// Returns **true** if378	/// * the `user`is a collection owner or admin379	/// * the collection limits allow the owner/admins to transfer/burn any collection token380	pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {381		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)382	}383384	/// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.385	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {386		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)387	}388389	/// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.390	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {391		ensure!(392			<Allowlist<T>>::get((self.id, user)),393			<Error<T>>::AddressNotInAllowlist394		);395		Ok(())396	}397398	/// Changes collection owner to another account399	/// #### Store read/writes400	/// 1 writes401	pub fn change_owner(402		&mut self,403		caller: T::CrossAccountId,404		new_owner: T::CrossAccountId,405	) -> DispatchResult {406		self.check_is_internal()?;407		self.check_is_owner(&caller)?;408		self.collection.owner = new_owner.as_sub().clone();409410		<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(411			self.id,412			new_owner.as_sub().clone(),413		));414		<PalletEvm<T>>::deposit_log(415			erc::CollectionHelpersEvents::CollectionChanged {416				collection_id: eth::collection_id_to_address(self.id),417			}418			.to_log(T::ContractAddress::get()),419		);420421		self.save()422	}423}424425#[frame_support::pallet]426pub mod pallet {427428	use super::*;429	use dispatch::CollectionDispatch;430	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};431	use up_data_structs::{TokenId, mapping::TokenAddressMapping};432	use scale_info::TypeInfo;433	use weights::WeightInfo;434435	#[pallet::config]436	pub trait Config:437		frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo438	{439		/// Weight information for functions of this pallet.440		type WeightInfo: WeightInfo;441442		/// Events compatible with [`frame_system::Config::Event`].443		type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;444445		/// Handler of accounts and payment.446		type Currency: Balanced<Self::AccountId> + Inspect<Self::AccountId>;447448		/// Set price to create a collection.449		#[pallet::constant]450		type CollectionCreationPrice: Get<451			<<Self as Config>::Currency as Inspect<Self::AccountId>>::Balance,452		>;453454		/// Dispatcher of operations on collections.455		type CollectionDispatch: CollectionDispatch<Self>;456457		/// Account which holds the chain's treasury.458		type TreasuryAccountId: Get<Self::AccountId>;459460		/// Address under which the CollectionHelper contract would be available.461		#[pallet::constant]462		type ContractAddress: Get<H160>;463464		/// Mapper for token addresses to Ethereum addresses.465		type EvmTokenAddressMapping: TokenAddressMapping<H160>;466467		/// Mapper for token addresses to [`CrossAccountId`].468		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;469	}470471	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);472473	#[pallet::pallet]474	#[pallet::storage_version(STORAGE_VERSION)]475	pub struct Pallet<T>(_);476477	#[pallet::extra_constants]478	impl<T: Config> Pallet<T> {479		/// Maximum admins per collection.480		pub fn collection_admins_limit() -> u32 {481			COLLECTION_ADMINS_LIMIT482		}483	}484485	#[pallet::genesis_config]486	pub struct GenesisConfig<T>(PhantomData<T>);487488	#[cfg(feature = "std")]489	impl<T: Config> Default for GenesisConfig<T> {490		fn default() -> Self {491			Self(Default::default())492		}493	}494495	#[pallet::genesis_build]496	impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {497		fn build(&self) {498			StorageVersion::new(1).put::<Pallet<T>>();499		}500	}501502	impl<T: Config> Pallet<T> {503		/// Helper function that handles deposit events504		pub fn deposit_event(event: Event<T>) {505			let event = <T as Config>::RuntimeEvent::from(event);506			let event = event.into();507			<frame_system::Pallet<T>>::deposit_event(event)508		}509	}510511	#[pallet::event]512	pub enum Event<T: Config> {513		/// New collection was created514		CollectionCreated(515			/// Globally unique identifier of newly created collection.516			CollectionId,517			/// [`CollectionMode`] converted into _u8_.518			u8,519			/// Collection owner.520			T::AccountId,521		),522523		/// New collection was destroyed524		CollectionDestroyed(525			/// Globally unique identifier of collection.526			CollectionId,527		),528529		/// New item was created.530		ItemCreated(531			/// Id of the collection where item was created.532			CollectionId,533			/// Id of an item. Unique within the collection.534			TokenId,535			/// Owner of newly created item536			T::CrossAccountId,537			/// Always 1 for NFT538			u128,539		),540541		/// Collection item was burned.542		ItemDestroyed(543			/// Id of the collection where item was destroyed.544			CollectionId,545			/// Identifier of burned NFT.546			TokenId,547			/// Which user has destroyed its tokens.548			T::CrossAccountId,549			/// Amount of token pieces destroed. Always 1 for NFT.550			u128,551		),552553		/// Item was transferred554		Transfer(555			/// Id of collection to which item is belong.556			CollectionId,557			/// Id of an item.558			TokenId,559			/// Original owner of item.560			T::CrossAccountId,561			/// New owner of item.562			T::CrossAccountId,563			/// Amount of token pieces transfered. Always 1 for NFT.564			u128,565		),566567		/// Amount pieces of token owned by `sender` was approved for `spender`.568		Approved(569			/// Id of collection to which item is belong.570			CollectionId,571			/// Id of an item.572			TokenId,573			/// Original owner of item.574			T::CrossAccountId,575			/// Id for which the approval was granted.576			T::CrossAccountId,577			/// Amount of token pieces transfered. Always 1 for NFT.578			u128,579		),580581		/// A `sender` approves operations on all owned tokens for `spender`.582		ApprovedForAll(583			/// Id of collection to which item is belong.584			CollectionId,585			/// Owner of a wallet.586			T::CrossAccountId,587			/// Id for which operator status was granted or rewoked.588			T::CrossAccountId,589			/// Is operator status granted or revoked?590			bool,591		),592593		/// The colletion property has been added or edited.594		CollectionPropertySet(595			/// Id of collection to which property has been set.596			CollectionId,597			/// The property that was set.598			PropertyKey,599		),600601		/// The property has been deleted.602		CollectionPropertyDeleted(603			/// Id of collection to which property has been deleted.604			CollectionId,605			/// The property that was deleted.606			PropertyKey,607		),608609		/// The token property has been added or edited.610		TokenPropertySet(611			/// Identifier of the collection whose token has the property set.612			CollectionId,613			/// The token for which the property was set.614			TokenId,615			/// The property that was set.616			PropertyKey,617		),618619		/// The token property has been deleted.620		TokenPropertyDeleted(621			/// Identifier of the collection whose token has the property deleted.622			CollectionId,623			/// The token for which the property was deleted.624			TokenId,625			/// The property that was deleted.626			PropertyKey,627		),628629		/// The token property permission of a collection has been set.630		PropertyPermissionSet(631			/// ID of collection to which property permission has been set.632			CollectionId,633			/// The property permission that was set.634			PropertyKey,635		),636637		/// Address was added to the allow list.638		AllowListAddressAdded(639			/// ID of the affected collection.640			CollectionId,641			/// Address of the added account.642			T::CrossAccountId,643		),644645		/// Address was removed from the allow list.646		AllowListAddressRemoved(647			/// ID of the affected collection.648			CollectionId,649			/// Address of the removed account.650			T::CrossAccountId,651		),652653		/// Collection admin was added.654		CollectionAdminAdded(655			/// ID of the affected collection.656			CollectionId,657			/// Admin address.658			T::CrossAccountId,659		),660661		/// Collection admin was removed.662		CollectionAdminRemoved(663			/// ID of the affected collection.664			CollectionId,665			/// Removed admin address.666			T::CrossAccountId,667		),668669		/// Collection limits were set.670		CollectionLimitSet(671			/// ID of the affected collection.672			CollectionId,673		),674675		/// Collection owned was changed.676		CollectionOwnerChanged(677			/// ID of the affected collection.678			CollectionId,679			/// New owner address.680			T::AccountId,681		),682683		/// Collection permissions were set.684		CollectionPermissionSet(685			/// ID of the affected collection.686			CollectionId,687		),688689		/// Collection sponsor was set.690		CollectionSponsorSet(691			/// ID of the affected collection.692			CollectionId,693			/// New sponsor address.694			T::AccountId,695		),696697		/// New sponsor was confirm.698		SponsorshipConfirmed(699			/// ID of the affected collection.700			CollectionId,701			/// New sponsor address.702			T::AccountId,703		),704705		/// Collection sponsor was removed.706		CollectionSponsorRemoved(707			/// ID of the affected collection.708			CollectionId,709		),710	}711712	#[pallet::error]713	pub enum Error<T> {714		/// This collection does not exist.715		CollectionNotFound,716		/// Sender parameter and item owner must be equal.717		MustBeTokenOwner,718		/// No permission to perform action719		NoPermission,720		/// Destroying only empty collections is allowed721		CantDestroyNotEmptyCollection,722		/// Collection is not in mint mode.723		PublicMintingNotAllowed,724		/// Address is not in allow list.725		AddressNotInAllowlist,726727		/// Collection name can not be longer than 63 char.728		CollectionNameLimitExceeded,729		/// Collection description can not be longer than 255 char.730		CollectionDescriptionLimitExceeded,731		/// Token prefix can not be longer than 15 char.732		CollectionTokenPrefixLimitExceeded,733		/// Total collections bound exceeded.734		TotalCollectionsLimitExceeded,735		/// Exceeded max admin count736		CollectionAdminCountExceeded,737		/// Collection limit bounds per collection exceeded738		CollectionLimitBoundsExceeded,739		/// Tried to enable permissions which are only permitted to be disabled740		OwnerPermissionsCantBeReverted,741		/// Collection settings not allowing items transferring742		TransferNotAllowed,743		/// Account token limit exceeded per collection744		AccountTokenLimitExceeded,745		/// Collection token limit exceeded746		CollectionTokenLimitExceeded,747		/// Metadata flag frozen748		MetadataFlagFrozen,749750		/// Item does not exist751		TokenNotFound,752		/// Item is balance not enough753		TokenValueTooLow,754		/// Requested value is more than the approved755		ApprovedValueTooLow,756		/// Tried to approve more than owned757		CantApproveMoreThanOwned,758		/// Only spending from eth mirror could be approved759		AddressIsNotEthMirror,760761		/// Can't transfer tokens to ethereum zero address762		AddressIsZero,763764		/// The operation is not supported765		UnsupportedOperation,766767		/// Insufficient funds to perform an action768		NotSufficientFounds,769770		/// User does not satisfy the nesting rule771		UserIsNotAllowedToNest,772		/// Only tokens from specific collections may nest tokens under this one773		SourceCollectionIsNotAllowedToNest,774775		/// Tried to store more data than allowed in collection field776		CollectionFieldSizeExceeded,777778		/// Tried to store more property data than allowed779		NoSpaceForProperty,780781		/// Tried to store more property keys than allowed782		PropertyLimitReached,783784		/// Property key is too long785		PropertyKeyIsTooLong,786787		/// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed788		InvalidCharacterInPropertyKey,789790		/// Empty property keys are forbidden791		EmptyPropertyKey,792793		/// Tried to access an external collection with an internal API794		CollectionIsExternal,795796		/// Tried to access an internal collection with an external API797		CollectionIsInternal,798799		/// This address is not set as sponsor, use setCollectionSponsor first.800		ConfirmSponsorshipFail,801802		/// The user is not an administrator.803		UserIsNotCollectionAdmin,804	}805806	/// Storage of the count of created collections. Essentially contains the last collection ID.807	#[pallet::storage]808	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;809810	/// Storage of the count of deleted collections.811	#[pallet::storage]812	pub type DestroyedCollectionCount<T> =813		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;814815	/// Storage of collection info.816	#[pallet::storage]817	pub type CollectionById<T> = StorageMap<818		Hasher = Blake2_128Concat,819		Key = CollectionId,820		Value = Collection<<T as frame_system::Config>::AccountId>,821		QueryKind = OptionQuery,822	>;823824	/// Storage of collection properties.825	#[pallet::storage]826	#[pallet::getter(fn collection_properties)]827	pub type CollectionProperties<T> = StorageMap<828		Hasher = Blake2_128Concat,829		Key = CollectionId,830		Value = CollectionPropertiesT,831		QueryKind = ValueQuery,832	>;833834	/// Storage of token property permissions of a collection.835	#[pallet::storage]836	#[pallet::getter(fn property_permissions)]837	pub type CollectionPropertyPermissions<T> = StorageMap<838		Hasher = Blake2_128Concat,839		Key = CollectionId,840		Value = PropertiesPermissionMap,841		QueryKind = ValueQuery,842	>;843844	/// Storage of the amount of collection admins.845	#[pallet::storage]846	pub type AdminAmount<T> = StorageMap<847		Hasher = Blake2_128Concat,848		Key = CollectionId,849		Value = u32,850		QueryKind = ValueQuery,851	>;852853	/// List of collection admins.854	#[pallet::storage]855	pub type IsAdmin<T: Config> = StorageNMap<856		Key = (857			Key<Blake2_128Concat, CollectionId>,858			Key<Blake2_128Concat, T::CrossAccountId>,859		),860		Value = bool,861		QueryKind = ValueQuery,862	>;863864	/// Allowlisted collection users.865	#[pallet::storage]866	pub type Allowlist<T: Config> = StorageNMap<867		Key = (868			Key<Blake2_128Concat, CollectionId>,869			Key<Blake2_128Concat, T::CrossAccountId>,870		),871		Value = bool,872		QueryKind = ValueQuery,873	>;874875	/// Not used by code, exists only to provide some types to metadata.876	#[pallet::storage]877	pub type DummyStorageValue<T: Config> = StorageValue<878		Value = (879			CollectionStats,880			CollectionId,881			TokenId,882			TokenChild,883			PhantomType<(884				TokenData<T::CrossAccountId>,885				RpcCollection<T::AccountId>,886				// PoV Estimate Info887				PovInfo,888			)>,889		),890		QueryKind = OptionQuery,891	>;892}893894impl<T: Config> Pallet<T> {895	/// Enshure that receiver address is correct.896	///897	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.898	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {899		ensure!(900			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,901			<Error<T>>::AddressIsZero902		);903		Ok(())904	}905906	/// Get a vector of collection admins.907	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {908		<IsAdmin<T>>::iter_prefix((collection,))909			.map(|(a, _)| a)910			.collect()911	}912913	/// Get a vector of users allowed to mint tokens.914	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {915		<Allowlist<T>>::iter_prefix((collection,))916			.map(|(a, _)| a)917			.collect()918	}919920	/// Is `user` allowed to mint token in `collection`.921	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {922		<Allowlist<T>>::get((collection, user))923	}924925	/// Get statistics of collections.926	pub fn collection_stats() -> CollectionStats {927		let created = <CreatedCollectionCount<T>>::get();928		let destroyed = <DestroyedCollectionCount<T>>::get();929		CollectionStats {930			created: created.0,931			destroyed: destroyed.0,932			alive: created.0 - destroyed.0,933		}934	}935936	/// Get the effective limits for the collection.937	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {938		let collection = <CollectionById<T>>::get(collection)?;939		let limits = collection.limits;940		let effective_limits = CollectionLimits {941			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),942			sponsored_data_size: Some(limits.sponsored_data_size()),943			sponsored_data_rate_limit: Some(944				limits945					.sponsored_data_rate_limit946					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),947			),948			token_limit: Some(limits.token_limit()),949			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(950				match collection.mode {951					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,952					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,953					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,954				},955			)),956			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),957			owner_can_transfer: Some(limits.owner_can_transfer()),958			owner_can_destroy: Some(limits.owner_can_destroy()),959			transfers_enabled: Some(limits.transfers_enabled()),960		};961962		Some(effective_limits)963	}964965	/// Returns information about the `collection` adapted for rpc.966	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {967		let Collection {968			name,969			description,970			owner,971			mode,972			token_prefix,973			sponsorship,974			limits,975			permissions,976			flags,977		} = <CollectionById<T>>::get(collection)?;978979		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)980			.into_iter()981			.map(|(key, permission)| PropertyKeyPermission { key, permission })982			.collect();983984		let properties = <CollectionProperties<T>>::get(collection)985			.into_iter()986			.map(|(key, value)| Property { key, value })987			.collect();988989		let permissions = CollectionPermissions {990			access: Some(permissions.access()),991			mint_mode: Some(permissions.mint_mode()),992			nesting: Some(permissions.nesting().clone()),993		};994995		Some(RpcCollection {996			name: name.into_inner(),997			description: description.into_inner(),998			owner,999			mode,1000			token_prefix: token_prefix.into_inner(),1001			sponsorship,1002			limits,1003			permissions,1004			token_property_permissions,1005			properties,1006			read_only: flags.external,10071008			flags: RpcCollectionFlags {1009				foreign: flags.foreign,1010				erc721metadata: flags.erc721metadata,1011			},1012		})1013	}1014}10151016macro_rules! limit_default {1017	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1018		$(1019			if let Some($new) = $new.$field {1020				let $old = $old.$field($($arg)?);1021				let _ = $new;1022				let _ = $old;1023				$check1024			} else {1025				$new.$field = $old.$field1026			}1027		)*1028	}};1029}1030macro_rules! limit_default_clone {1031	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1032		$(1033			if let Some($new) = $new.$field.clone() {1034				let $old = $old.$field($($arg)?);1035				let _ = $new;1036				let _ = $old;1037				$check1038			} else {1039				$new.$field = $old.$field.clone()1040			}1041		)*1042	}};1043}10441045impl<T: Config> Pallet<T> {1046	/// Create new collection.1047	///1048	/// * `owner` - The owner of the collection.1049	/// * `data` - Description of the created collection.1050	/// * `flags` - Extra flags to store.1051	pub fn init_collection(1052		owner: T::CrossAccountId,1053		payer: T::CrossAccountId,1054		data: CreateCollectionData<T::AccountId>,1055		flags: CollectionFlags,1056	) -> Result<CollectionId, DispatchError> {1057		{1058			ensure!(1059				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1060				Error::<T>::CollectionTokenPrefixLimitExceeded1061			);1062		}10631064		let created_count = <CreatedCollectionCount<T>>::get()1065			.01066			.checked_add(1)1067			.ok_or(ArithmeticError::Overflow)?;1068		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1069		let id = CollectionId(created_count);10701071		// bound Total number of collections1072		ensure!(1073			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1074			<Error<T>>::TotalCollectionsLimitExceeded1075		);10761077		// =========10781079		let collection = Collection {1080			owner: owner.as_sub().clone(),1081			name: data.name,1082			mode: data.mode.clone(),1083			description: data.description,1084			token_prefix: data.token_prefix,1085			sponsorship: data1086				.pending_sponsor1087				.map(SponsorshipState::Unconfirmed)1088				.unwrap_or_default(),1089			limits: data1090				.limits1091				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1092				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,1093			permissions: data1094				.permissions1095				.map(|permissions| {1096					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1097				})1098				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1099			flags,1100		};11011102		let mut collection_properties = CollectionPropertiesT::new();1103		collection_properties1104			.try_set_from_iter(data.properties.into_iter())1105			.map_err(<Error<T>>::from)?;11061107		CollectionProperties::<T>::insert(id, collection_properties);11081109		let mut token_props_permissions = PropertiesPermissionMap::new();1110		token_props_permissions1111			.try_set_from_iter(data.token_property_permissions.into_iter())1112			.map_err(<Error<T>>::from)?;11131114		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11151116		// Take a (non-refundable) deposit of collection creation1117		{1118			let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1119			imbalance.subsume(<T as Config>::Currency::deposit(1120				&T::TreasuryAccountId::get(),1121				T::CollectionCreationPrice::get(),1122				Precision::Exact,1123			)?);1124			let credit =1125				<T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1126					.map_err(|_| Error::<T>::NotSufficientFounds)?;11271128			debug_assert!(credit.peek().is_zero())1129		}11301131		<CreatedCollectionCount<T>>::put(created_count);1132		<Pallet<T>>::deposit_event(Event::CollectionCreated(1133			id,1134			data.mode.id(),1135			owner.as_sub().clone(),1136		));1137		<PalletEvm<T>>::deposit_log(1138			erc::CollectionHelpersEvents::CollectionCreated {1139				owner: *owner.as_eth(),1140				collection_id: eth::collection_id_to_address(id),1141			}1142			.to_log(T::ContractAddress::get()),1143		);1144		<CollectionById<T>>::insert(id, collection);1145		Ok(id)1146	}11471148	/// Destroy collection.1149	///1150	/// * `collection` - Collection handler.1151	/// * `sender` - The owner or administrator of the collection.1152	pub fn destroy_collection(1153		collection: CollectionHandle<T>,1154		sender: &T::CrossAccountId,1155	) -> DispatchResult {1156		ensure!(1157			collection.limits.owner_can_destroy(),1158			<Error<T>>::NoPermission,1159		);1160		collection.check_is_owner(sender)?;11611162		let destroyed_collections = <DestroyedCollectionCount<T>>::get()1163			.01164			.checked_add(1)1165			.ok_or(ArithmeticError::Overflow)?;11661167		// =========11681169		<DestroyedCollectionCount<T>>::put(destroyed_collections);1170		<CollectionById<T>>::remove(collection.id);1171		<AdminAmount<T>>::remove(collection.id);1172		let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1173		let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1174		<CollectionProperties<T>>::remove(collection.id);11751176		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));11771178		<PalletEvm<T>>::deposit_log(1179			erc::CollectionHelpersEvents::CollectionDestroyed {1180				collection_id: eth::collection_id_to_address(collection.id),1181			}1182			.to_log(T::ContractAddress::get()),1183		);1184		Ok(())1185	}11861187	/// This function sets or removes a collection properties according to1188	/// `properties_updates` contents:1189	/// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1190	/// * removes a property under the <key> if the value is `None` `(<key>, None)`.1191	///1192	/// This function fires an event for each property change.1193	/// In case of an error, all the changes (including the events) will be reverted1194	/// since the function is transactional.1195	#[transactional]1196	fn modify_collection_properties(1197		collection: &CollectionHandle<T>,1198		sender: &T::CrossAccountId,1199		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1200	) -> DispatchResult {1201		collection.check_is_owner_or_admin(sender)?;12021203		let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12041205		for (key, value) in properties_updates {1206			match value {1207				Some(value) => {1208					stored_properties1209						.try_set(key.clone(), value)1210						.map_err(<Error<T>>::from)?;12111212					Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1213					<PalletEvm<T>>::deposit_log(1214						erc::CollectionHelpersEvents::CollectionChanged {1215							collection_id: eth::collection_id_to_address(collection.id),1216						}1217						.to_log(T::ContractAddress::get()),1218					);1219				}1220				None => {1221					stored_properties.remove(&key).map_err(<Error<T>>::from)?;12221223					Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1224					<PalletEvm<T>>::deposit_log(1225						erc::CollectionHelpersEvents::CollectionChanged {1226							collection_id: eth::collection_id_to_address(collection.id),1227						}1228						.to_log(T::ContractAddress::get()),1229					);1230				}1231			}1232		}12331234		<CollectionProperties<T>>::set(collection.id, stored_properties);12351236		Ok(())1237	}12381239	/// A batch operation to add, edit or remove properties for a token.1240	/// It sets or removes a token's properties according to1241	/// `properties_updates` contents:1242	/// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1243	/// * removes a property under the <key> if the value is `None` `(<key>, None)`.1244	///1245	/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.1246	/// - `is_token_create`: Indicates that method is called during token initialization.1247	///   Allows to bypass ownership check.1248	///1249	/// All affected properties should have `mutable` permission1250	/// to be **deleted** or to be **set more than once**,1251	/// and the sender should have permission to edit those properties.1252	///1253	/// This function fires an event for each property change.1254	/// In case of an error, all the changes (including the events) will be reverted1255	/// since the function is transactional.1256	pub fn modify_token_properties(1257		collection: &CollectionHandle<T>,1258		sender: &T::CrossAccountId,1259		token_id: TokenId,1260		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1261		is_token_create: bool,1262		mut stored_properties: TokenProperties,1263		is_token_owner: impl Fn() -> Result<bool, DispatchError>,1264		set_token_properties: impl FnOnce(TokenProperties),1265		log: evm_coder::ethereum::Log,1266	) -> DispatchResult {1267		let is_collection_admin = collection.is_owner_or_admin(sender);1268		let permissions = Self::property_permissions(collection.id);12691270		let mut token_owner_result = None;1271		let mut is_token_owner = || -> Result<bool, DispatchError> {1272			*token_owner_result.get_or_insert_with(&is_token_owner)1273		};12741275		for (key, value) in properties_updates {1276			let permission = permissions1277				.get(&key)1278				.cloned()1279				.unwrap_or_else(PropertyPermission::none);12801281			let is_property_exists = stored_properties.get(&key).is_some();12821283			match permission {1284				PropertyPermission { mutable: false, .. } if is_property_exists => {1285					return Err(<Error<T>>::NoPermission.into());1286				}12871288				PropertyPermission {1289					collection_admin,1290					token_owner,1291					..1292				} => {1293					//TODO: investigate threats during public minting.1294					let is_token_create =1295						is_token_create && (collection_admin || token_owner) && value.is_some();1296					if !(is_token_create1297						|| (collection_admin && is_collection_admin)1298						|| (token_owner && is_token_owner()?))1299					{1300						fail!(<Error<T>>::NoPermission);1301					}1302				}1303			}13041305			match value {1306				Some(value) => {1307					stored_properties1308						.try_set(key.clone(), value)1309						.map_err(<Error<T>>::from)?;13101311					Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1312				}1313				None => {1314					stored_properties.remove(&key).map_err(<Error<T>>::from)?;13151316					Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1317				}1318			}13191320			<PalletEvm<T>>::deposit_log(log.clone());1321		}13221323		set_token_properties(stored_properties);13241325		Ok(())1326	}13271328	/// Sets or unsets the approval of a given operator.1329	///1330	/// The `operator` is allowed to transfer all token pieces of the `owner` on their behalf.1331	/// - `owner`: Token owner1332	/// - `operator`: Operator1333	/// - `approve`: Should operator status be granted or revoked?1334	pub fn set_allowance_for_all(1335		collection: &CollectionHandle<T>,1336		owner: &T::CrossAccountId,1337		operator: &T::CrossAccountId,1338		approve: bool,1339		set_allowance: impl FnOnce(),1340		log: evm_coder::ethereum::Log,1341	) -> DispatchResult {1342		if collection.permissions.access() == AccessMode::AllowList {1343			collection.check_allowlist(owner)?;1344			collection.check_allowlist(operator)?;1345		}13461347		Self::ensure_correct_receiver(operator)?;13481349		set_allowance();13501351		<PalletEvm<T>>::deposit_log(log);1352		Self::deposit_event(Event::ApprovedForAll(1353			collection.id,1354			owner.clone(),1355			operator.clone(),1356			approve,1357		));1358		Ok(())1359	}13601361	/// Set collection property.1362	///1363	/// * `collection` - Collection handler.1364	/// * `sender` - The owner or administrator of the collection.1365	/// * `property` - The property to set.1366	pub fn set_collection_property(1367		collection: &CollectionHandle<T>,1368		sender: &T::CrossAccountId,1369		property: Property,1370	) -> DispatchResult {1371		Self::set_collection_properties(collection, sender, [property].into_iter())1372	}13731374	/// Set a scoped collection property, where the scope is a special prefix1375	/// prohibiting a user access to change the property directly.1376	///1377	/// * `collection_id` - ID of the collection for which the property is being set.1378	/// * `scope` - Property scope.1379	/// * `property` - The property to set.1380	pub fn set_scoped_collection_property(1381		collection_id: CollectionId,1382		scope: PropertyScope,1383		property: Property,1384	) -> DispatchResult {1385		CollectionProperties::<T>::try_mutate(collection_id, |properties| {1386			properties.try_scoped_set(scope, property.key, property.value)1387		})1388		.map_err(<Error<T>>::from)?;13891390		Ok(())1391	}13921393	/// Set scoped collection properties, where the scope is a special prefix1394	/// prohibiting a user access to change the properties directly.1395	///1396	/// * `collection_id` - ID of the collection for which the properties is being set.1397	/// * `scope` - Property scope.1398	/// * `properties` - The properties to set.1399	pub fn set_scoped_collection_properties(1400		collection_id: CollectionId,1401		scope: PropertyScope,1402		properties: impl Iterator<Item = Property>,1403	) -> DispatchResult {1404		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1405			stored_properties.try_scoped_set_from_iter(scope, properties)1406		})1407		.map_err(<Error<T>>::from)?;14081409		Ok(())1410	}14111412	/// Set collection properties.1413	///1414	/// * `collection` - Collection handler.1415	/// * `sender` - The owner or administrator of the collection.1416	/// * `properties` - The properties to set.1417	pub fn set_collection_properties(1418		collection: &CollectionHandle<T>,1419		sender: &T::CrossAccountId,1420		properties: impl Iterator<Item = Property>,1421	) -> DispatchResult {1422		Self::modify_collection_properties(1423			collection,1424			sender,1425			properties.map(|property| (property.key, Some(property.value))),1426		)1427	}14281429	/// Delete collection property.1430	///1431	/// * `collection` - Collection handler.1432	/// * `sender` - The owner or administrator of the collection.1433	/// * `property` - The property to delete.1434	pub fn delete_collection_property(1435		collection: &CollectionHandle<T>,1436		sender: &T::CrossAccountId,1437		property_key: PropertyKey,1438	) -> DispatchResult {1439		Self::delete_collection_properties(collection, sender, [property_key].into_iter())1440	}14411442	/// Delete collection properties.1443	///1444	/// * `collection` - Collection handler.1445	/// * `sender` - The owner or administrator of the collection.1446	/// * `properties` - The properties to delete.1447	pub fn delete_collection_properties(1448		collection: &CollectionHandle<T>,1449		sender: &T::CrossAccountId,1450		property_keys: impl Iterator<Item = PropertyKey>,1451	) -> DispatchResult {1452		Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1453	}14541455	/// Set collection propetry permission without any checks.1456	///1457	/// Used for migrations.1458	///1459	/// * `collection` - Collection handler.1460	/// * `property_permissions` - Property permissions.1461	pub fn set_property_permission_unchecked(1462		collection: CollectionId,1463		property_permission: PropertyKeyPermission,1464	) -> DispatchResult {1465		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1466			permissions.try_set(property_permission.key, property_permission.permission)1467		})1468		.map_err(<Error<T>>::from)?;1469		Ok(())1470	}14711472	/// Set collection property permission.1473	///1474	/// * `collection` - Collection handler.1475	/// * `sender` - The owner or administrator of the collection.1476	/// * `property_permission` - Property permission.1477	pub fn set_property_permission(1478		collection: &CollectionHandle<T>,1479		sender: &T::CrossAccountId,1480		property_permission: PropertyKeyPermission,1481	) -> DispatchResult {1482		Self::set_scoped_property_permission(1483			collection,1484			sender,1485			PropertyScope::None,1486			property_permission,1487		)1488	}14891490	/// Set collection property permission with scope.1491	///1492	/// * `collection` - Collection handler.1493	/// * `sender` - The owner or administrator of the collection.1494	/// * `scope` - Property scope.1495	/// * `property_permission` - Property permission.1496	pub fn set_scoped_property_permission(1497		collection: &CollectionHandle<T>,1498		sender: &T::CrossAccountId,1499		scope: PropertyScope,1500		property_permission: PropertyKeyPermission,1501	) -> DispatchResult {1502		collection.check_is_owner_or_admin(sender)?;15031504		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1505		let current_permission = all_permissions.get(&property_permission.key);1506		if matches![1507			current_permission,1508			Some(PropertyPermission { mutable: false, .. })1509		] {1510			return Err(<Error<T>>::NoPermission.into());1511		}15121513		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1514			let property_permission = property_permission.clone();1515			permissions.try_scoped_set(1516				scope,1517				property_permission.key,1518				property_permission.permission,1519			)1520		})1521		.map_err(<Error<T>>::from)?;15221523		Self::deposit_event(Event::PropertyPermissionSet(1524			collection.id,1525			property_permission.key,1526		));1527		<PalletEvm<T>>::deposit_log(1528			erc::CollectionHelpersEvents::CollectionChanged {1529				collection_id: eth::collection_id_to_address(collection.id),1530			}1531			.to_log(T::ContractAddress::get()),1532		);15331534		Ok(())1535	}15361537	/// Set token property permission.1538	///1539	/// * `collection` - Collection handler.1540	/// * `sender` - The owner or administrator of the collection.1541	/// * `property_permissions` - Property permissions.1542	#[transactional]1543	pub fn set_token_property_permissions(1544		collection: &CollectionHandle<T>,1545		sender: &T::CrossAccountId,1546		property_permissions: Vec<PropertyKeyPermission>,1547	) -> DispatchResult {1548		Self::set_scoped_token_property_permissions(1549			collection,1550			sender,1551			PropertyScope::None,1552			property_permissions,1553		)1554	}15551556	/// Set token property permission with scope.1557	///1558	/// * `collection` - Collection handler.1559	/// * `sender` - The owner or administrator of the collection.1560	/// * `scope` - Property scope.1561	/// * `property_permissions` - Property permissions.1562	#[transactional]1563	pub fn set_scoped_token_property_permissions(1564		collection: &CollectionHandle<T>,1565		sender: &T::CrossAccountId,1566		scope: PropertyScope,1567		property_permissions: Vec<PropertyKeyPermission>,1568	) -> DispatchResult {1569		for prop_pemission in property_permissions {1570			Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1571		}15721573		Ok(())1574	}15751576	/// Get collection property.1577	pub fn get_collection_property(1578		collection_id: CollectionId,1579		key: &PropertyKey,1580	) -> Option<PropertyValue> {1581		Self::collection_properties(collection_id).get(key).cloned()1582	}15831584	/// Convert byte vector to property key vector.1585	pub fn bytes_keys_to_property_keys(1586		keys: Vec<Vec<u8>>,1587	) -> Result<Vec<PropertyKey>, DispatchError> {1588		keys.into_iter()1589			.map(|key| -> Result<PropertyKey, DispatchError> {1590				key.try_into()1591					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1592			})1593			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1594	}15951596	/// Get properties according to given keys.1597	pub fn filter_collection_properties(1598		collection_id: CollectionId,1599		keys: Option<Vec<PropertyKey>>,1600	) -> Result<Vec<Property>, DispatchError> {1601		let properties = Self::collection_properties(collection_id);16021603		let properties = keys1604			.map(|keys| {1605				keys.into_iter()1606					.filter_map(|key| {1607						properties.get(&key).map(|value| Property {1608							key,1609							value: value.clone(),1610						})1611					})1612					.collect()1613			})1614			.unwrap_or_else(|| {1615				properties1616					.into_iter()1617					.map(|(key, value)| Property { key, value })1618					.collect()1619			});16201621		Ok(properties)1622	}16231624	/// Get property permissions according to given keys.1625	pub fn filter_property_permissions(1626		collection_id: CollectionId,1627		keys: Option<Vec<PropertyKey>>,1628	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1629		let permissions = Self::property_permissions(collection_id);16301631		let key_permissions = keys1632			.map(|keys| {1633				keys.into_iter()1634					.filter_map(|key| {1635						permissions1636							.get(&key)1637							.map(|permission| PropertyKeyPermission {1638								key,1639								permission: permission.clone(),1640							})1641					})1642					.collect()1643			})1644			.unwrap_or_else(|| {1645				permissions1646					.into_iter()1647					.map(|(key, permission)| PropertyKeyPermission { key, permission })1648					.collect()1649			});16501651		Ok(key_permissions)1652	}16531654	/// Toggle `user` participation in the `collection`'s allow list.1655	/// #### Store read/writes1656	/// 1 writes1657	pub fn toggle_allowlist(1658		collection: &CollectionHandle<T>,1659		sender: &T::CrossAccountId,1660		user: &T::CrossAccountId,1661		allowed: bool,1662	) -> DispatchResult {1663		collection.check_is_owner_or_admin(sender)?;16641665		// =========16661667		if allowed {1668			<Allowlist<T>>::insert((collection.id, user), true);1669			Self::deposit_event(Event::<T>::AllowListAddressAdded(1670				collection.id,1671				user.clone(),1672			));1673		} else {1674			<Allowlist<T>>::remove((collection.id, user));1675			Self::deposit_event(Event::<T>::AllowListAddressRemoved(1676				collection.id,1677				user.clone(),1678			));1679		}16801681		<PalletEvm<T>>::deposit_log(1682			erc::CollectionHelpersEvents::CollectionChanged {1683				collection_id: eth::collection_id_to_address(collection.id),1684			}1685			.to_log(T::ContractAddress::get()),1686		);16871688		Ok(())1689	}16901691	/// Toggle `user` participation in the `collection`'s admin list.1692	/// #### Store read/writes1693	/// 2 reads, 2 writes1694	pub fn toggle_admin(1695		collection: &CollectionHandle<T>,1696		sender: &T::CrossAccountId,1697		user: &T::CrossAccountId,1698		admin: bool,1699	) -> DispatchResult {1700		collection.check_is_internal()?;1701		collection.check_is_owner(sender)?;17021703		let is_admin = <IsAdmin<T>>::get((collection.id, user));1704		if is_admin == admin {1705			if admin {1706				return Ok(());1707			} else {1708				return Err(Error::<T>::UserIsNotCollectionAdmin.into());1709			}1710		}1711		let amount = <AdminAmount<T>>::get(collection.id);17121713		// =========17141715		if admin {1716			let amount = amount1717				.checked_add(1)1718				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1719			ensure!(1720				amount <= Self::collection_admins_limit(),1721				<Error<T>>::CollectionAdminCountExceeded,1722			);17231724			<AdminAmount<T>>::insert(collection.id, amount);1725			<IsAdmin<T>>::insert((collection.id, user), true);17261727			Self::deposit_event(Event::<T>::CollectionAdminAdded(1728				collection.id,1729				user.clone(),1730			));1731		} else {1732			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1733			<IsAdmin<T>>::remove((collection.id, user));17341735			Self::deposit_event(Event::<T>::CollectionAdminRemoved(1736				collection.id,1737				user.clone(),1738			));1739		}17401741		<PalletEvm<T>>::deposit_log(1742			erc::CollectionHelpersEvents::CollectionChanged {1743				collection_id: eth::collection_id_to_address(collection.id),1744			}1745			.to_log(T::ContractAddress::get()),1746		);17471748		Ok(())1749	}17501751	/// Update collection limits.1752	pub fn update_limits(1753		user: &T::CrossAccountId,1754		collection: &mut CollectionHandle<T>,1755		new_limit: CollectionLimits,1756	) -> DispatchResult {1757		collection.check_is_internal()?;1758		collection.check_is_owner_or_admin(user)?;17591760		collection.limits =1761			Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17621763		Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1764		<PalletEvm<T>>::deposit_log(1765			erc::CollectionHelpersEvents::CollectionChanged {1766				collection_id: eth::collection_id_to_address(collection.id),1767			}1768			.to_log(T::ContractAddress::get()),1769		);17701771		collection.save()1772	}17731774	/// Merge set fields from `new_limit` to `old_limit`.1775	fn clamp_limits(1776		mode: CollectionMode,1777		old_limit: &CollectionLimits,1778		mut new_limit: CollectionLimits,1779	) -> Result<CollectionLimits, DispatchError> {1780		let limits = old_limit;1781		limit_default!(old_limit, new_limit,1782			account_token_ownership_limit => ensure!(1783				new_limit <= MAX_TOKEN_OWNERSHIP,1784				<Error<T>>::CollectionLimitBoundsExceeded,1785			),1786			sponsored_data_size => ensure!(1787				new_limit <= CUSTOM_DATA_LIMIT,1788				<Error<T>>::CollectionLimitBoundsExceeded,1789			),17901791			sponsored_data_rate_limit => {},1792			token_limit => ensure!(1793				old_limit >= new_limit && new_limit > 0,1794				<Error<T>>::CollectionTokenLimitExceeded1795			),17961797			sponsor_transfer_timeout(match mode {1798				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1799				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1800				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1801			}) => ensure!(1802				new_limit <= MAX_SPONSOR_TIMEOUT,1803				<Error<T>>::CollectionLimitBoundsExceeded,1804			),1805			sponsor_approve_timeout => {},1806			owner_can_transfer => ensure!(1807				!limits.owner_can_transfer_instaled() ||1808				old_limit || !new_limit,1809				<Error<T>>::OwnerPermissionsCantBeReverted,1810			),1811			owner_can_destroy => ensure!(1812				old_limit || !new_limit,1813				<Error<T>>::OwnerPermissionsCantBeReverted,1814			),1815			transfers_enabled => {},1816		);1817		Ok(new_limit)1818	}18191820	/// Update collection permissions.1821	pub fn update_permissions(1822		user: &T::CrossAccountId,1823		collection: &mut CollectionHandle<T>,1824		new_permission: CollectionPermissions,1825	) -> DispatchResult {1826		collection.check_is_internal()?;1827		collection.check_is_owner_or_admin(user)?;1828		collection.permissions = Self::clamp_permissions(1829			collection.mode.clone(),1830			&collection.permissions,1831			new_permission,1832		)?;18331834		Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1835		<PalletEvm<T>>::deposit_log(1836			erc::CollectionHelpersEvents::CollectionChanged {1837				collection_id: eth::collection_id_to_address(collection.id),1838			}1839			.to_log(T::ContractAddress::get()),1840		);18411842		collection.save()1843	}18441845	/// Merge set fields from `new_permission` to `old_permission`.1846	fn clamp_permissions(1847		_mode: CollectionMode,1848		old_permission: &CollectionPermissions,1849		mut new_permission: CollectionPermissions,1850	) -> Result<CollectionPermissions, DispatchError> {1851		limit_default_clone!(old_permission, new_permission,1852			access => {},1853			mint_mode => {},1854			nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1855		);1856		Ok(new_permission)1857	}18581859	/// Repair possibly broken properties of a collection.1860	pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1861		CollectionProperties::<T>::mutate(collection_id, |properties| {1862			properties.recompute_consumed_space();1863		});18641865		Ok(())1866	}1867}18681869/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1870#[macro_export]1871macro_rules! unsupported {1872	($runtime:path) => {1873		Err($crate::Error::<$runtime>::UnsupportedOperation.into())1874	};1875}18761877/// Return weights for various worst-case operations.1878pub trait CommonWeightInfo<CrossAccountId> {1879	/// Weight of item creation.1880	fn create_item(data: &CreateItemData) -> Weight {1881		Self::create_multiple_items(from_ref(data))1882	}18831884	/// Weight of items creation.1885	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18861887	/// Weight of items creation.1888	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18891890	/// The weight of the burning item.1891	fn burn_item() -> Weight;18921893	/// Property setting weight.1894	///1895	/// * `amount`- The number of properties to set.1896	fn set_collection_properties(amount: u32) -> Weight;18971898	/// Collection property deletion weight.1899	///1900	/// * `amount`- The number of properties to set.1901	fn delete_collection_properties(amount: u32) -> Weight;19021903	/// Token property setting weight.1904	///1905	/// * `amount`- The number of properties to set.1906	fn set_token_properties(amount: u32) -> Weight;19071908	/// Token property deletion weight.1909	///1910	/// * `amount`- The number of properties to delete.1911	fn delete_token_properties(amount: u32) -> Weight;19121913	/// Token property permissions set weight.1914	///1915	/// * `amount`- The number of property permissions to set.1916	fn set_token_property_permissions(amount: u32) -> Weight;19171918	/// Transfer price of the token or its parts.1919	fn transfer() -> Weight;19201921	/// The price of setting the permission of the operation from another user.1922	fn approve() -> Weight;19231924	/// The price of setting the permission of the operation from another user for eth mirror.1925	fn approve_from() -> Weight;19261927	/// Transfer price from another user.1928	fn transfer_from() -> Weight;19291930	/// The price of burning a token from another user.1931	fn burn_from() -> Weight;19321933	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1934	/// whole users's balance.1935	///1936	/// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1937	fn burn_recursively_self_raw() -> Weight;19381939	/// Cost of iterating over `amount` children while burning, without counting child burning itself.1940	///1941	/// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1942	fn burn_recursively_breadth_raw(amount: u32) -> Weight;19431944	/// The price of recursive burning a token.1945	///1946	/// `max_selfs` - The maximum burning weight of the token itself.1947	/// `max_breadth` - The maximum number of nested tokens to burn.1948	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1949		Self::burn_recursively_self_raw()1950			.saturating_mul(max_selfs.max(1) as u64)1951			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1952	}19531954	/// The price of retrieving token owner1955	fn token_owner() -> Weight;19561957	/// The price of setting approval for all1958	fn set_allowance_for_all() -> Weight;19591960	/// The price of repairing an item.1961	fn force_repair_item() -> Weight;1962}19631964/// Weight info extension trait for refungible pallet.1965pub trait RefungibleExtensionsWeightInfo {1966	/// Weight of token repartition.1967	fn repartition() -> Weight;1968}19691970/// Common collection operations.1971///1972/// It wraps methods in Fungible, Nonfungible and Refungible pallets1973/// and adds weight info.1974pub trait CommonCollectionOperations<T: Config> {1975	/// Create token.1976	///1977	/// * `sender` - The user who mint the token and pays for the transaction.1978	/// * `to` - The user who will own the token.1979	/// * `data` - Token data.1980	/// * `nesting_budget` - A budget that can be spent on nesting tokens.1981	fn create_item(1982		&self,1983		sender: T::CrossAccountId,1984		to: T::CrossAccountId,1985		data: CreateItemData,1986		nesting_budget: &dyn Budget,1987	) -> DispatchResultWithPostInfo;19881989	/// Create multiple tokens.1990	///1991	/// * `sender` - The user who mint the token and pays for the transaction.1992	/// * `to` - The user who will own the token.1993	/// * `data` - Token data.1994	/// * `nesting_budget` - A budget that can be spent on nesting tokens.1995	fn create_multiple_items(1996		&self,1997		sender: T::CrossAccountId,1998		to: T::CrossAccountId,1999		data: Vec<CreateItemData>,2000		nesting_budget: &dyn Budget,2001	) -> DispatchResultWithPostInfo;20022003	/// Create multiple tokens.2004	///2005	/// * `sender` - The user who mint the token and pays for the transaction.2006	/// * `to` - The user who will own the token.2007	/// * `data` - Token data.2008	/// * `nesting_budget` - A budget that can be spent on nesting tokens.2009	fn create_multiple_items_ex(2010		&self,2011		sender: T::CrossAccountId,2012		data: CreateItemExData<T::CrossAccountId>,2013		nesting_budget: &dyn Budget,2014	) -> DispatchResultWithPostInfo;20152016	/// Burn token.2017	///2018	/// * `sender` - The user who owns the token.2019	/// * `token` - Token id that will burned.2020	/// * `amount` - The number of parts of the token that will be burned.2021	fn burn_item(2022		&self,2023		sender: T::CrossAccountId,2024		token: TokenId,2025		amount: u128,2026	) -> DispatchResultWithPostInfo;20272028	/// Burn token and all nested tokens recursievly.2029	///2030	/// * `sender` - The user who owns the token.2031	/// * `token` - Token id that will burned.2032	/// * `self_budget` - The budget that can be spent on burning tokens.2033	/// * `breadth_budget` - The budget that can be spent on burning nested tokens.2034	fn burn_item_recursively(2035		&self,2036		sender: T::CrossAccountId,2037		token: TokenId,2038		self_budget: &dyn Budget,2039		breadth_budget: &dyn Budget,2040	) -> DispatchResultWithPostInfo;20412042	/// Set collection properties.2043	///2044	/// * `sender` - Must be either the owner of the collection or its admin.2045	/// * `properties` - Properties to be set.2046	fn set_collection_properties(2047		&self,2048		sender: T::CrossAccountId,2049		properties: Vec<Property>,2050	) -> DispatchResultWithPostInfo;20512052	/// Delete collection properties.2053	///2054	/// * `sender` - Must be either the owner of the collection or its admin.2055	/// * `properties` - The properties to be removed.2056	fn delete_collection_properties(2057		&self,2058		sender: &T::CrossAccountId,2059		property_keys: Vec<PropertyKey>,2060	) -> DispatchResultWithPostInfo;20612062	/// Set token properties.2063	///2064	/// The appropriate [`PropertyPermission`] for the token property2065	/// must be set with [`Self::set_token_property_permissions`].2066	///2067	/// * `sender` - Must be either the owner of the token or its admin.2068	/// * `token_id` - The token for which the properties are being set.2069	/// * `properties` - Properties to be set.2070	/// * `budget` - Budget for setting properties.2071	fn set_token_properties(2072		&self,2073		sender: T::CrossAccountId,2074		token_id: TokenId,2075		properties: Vec<Property>,2076		budget: &dyn Budget,2077	) -> DispatchResultWithPostInfo;20782079	/// Remove token properties.2080	///2081	/// The appropriate [`PropertyPermission`] for the token property2082	/// must be set with [`Self::set_token_property_permissions`].2083	///2084	/// * `sender` - Must be either the owner of the token or its admin.2085	/// * `token_id` - The token for which the properties are being remove.2086	/// * `property_keys` - Keys to remove corresponding properties.2087	/// * `budget` - Budget for removing properties.2088	fn delete_token_properties(2089		&self,2090		sender: T::CrossAccountId,2091		token_id: TokenId,2092		property_keys: Vec<PropertyKey>,2093		budget: &dyn Budget,2094	) -> DispatchResultWithPostInfo;20952096	/// Set token property permissions.2097	///2098	/// * `sender` - Must be either the owner of the token or its admin.2099	/// * `token_id` - The token for which the properties are being set.2100	/// * `property_permissions` - Property permissions to be set.2101	/// * `budget` - Budget for setting properties.2102	fn set_token_property_permissions(2103		&self,2104		sender: &T::CrossAccountId,2105		property_permissions: Vec<PropertyKeyPermission>,2106	) -> DispatchResultWithPostInfo;21072108	/// Transfer amount of token pieces.2109	///2110	/// * `sender` - Donor user.2111	/// * `to` - Recepient user.2112	/// * `token` - The token of which parts are being sent.2113	/// * `amount` - The number of parts of the token that will be transferred.2114	/// * `budget` - The maximum budget that can be spent on the transfer.2115	fn transfer(2116		&self,2117		sender: T::CrossAccountId,2118		to: T::CrossAccountId,2119		token: TokenId,2120		amount: u128,2121		budget: &dyn Budget,2122	) -> DispatchResultWithPostInfo;21232124	/// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].2125	///2126	/// * `sender` - The user who grants access to the token.2127	/// * `spender` - The user to whom the rights are granted.2128	/// * `token` - The token to which access is granted.2129	/// * `amount` - The amount of pieces that another user can dispose of.2130	fn approve(2131		&self,2132		sender: T::CrossAccountId,2133		spender: T::CrossAccountId,2134		token: TokenId,2135		amount: u128,2136	) -> DispatchResultWithPostInfo;21372138	/// Grant access to another account to transfer parts of the token owned by the calling user's eth mirror via [Self::transfer_from].2139	///2140	/// * `sender` - The user who grants access to the token.2141	/// * `from` - Spender's eth mirror.2142	/// * `to` - The user to whom the rights are granted.2143	/// * `token` - The token to which access is granted.2144	/// * `amount` - The amount of pieces that another user can dispose of.2145	fn approve_from(2146		&self,2147		sender: T::CrossAccountId,2148		from: T::CrossAccountId,2149		to: T::CrossAccountId,2150		token: TokenId,2151		amount: u128,2152	) -> DispatchResultWithPostInfo;21532154	/// Send parts of a token owned by another user.2155	///2156	/// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2157	///2158	/// * `sender` - The user who must have access to the token (see [`Self::approve`]).2159	/// * `from` - The user who owns the token.2160	/// * `to` - Recepient user.2161	/// * `token` - The token of which parts are being sent.2162	/// * `amount` - The number of parts of the token that will be transferred.2163	/// * `budget` - The maximum budget that can be spent on the transfer.2164	fn transfer_from(2165		&self,2166		sender: T::CrossAccountId,2167		from: T::CrossAccountId,2168		to: T::CrossAccountId,2169		token: TokenId,2170		amount: u128,2171		budget: &dyn Budget,2172	) -> DispatchResultWithPostInfo;21732174	/// Burn parts of a token owned by another user.2175	///2176	/// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2177	///2178	/// * `sender` - The user who must have access to the token (see [`Self::approve`]).2179	/// * `from` - The user who owns the token.2180	/// * `token` - The token of which parts are being sent.2181	/// * `amount` - The number of parts of the token that will be transferred.2182	/// * `budget` - The maximum budget that can be spent on the burn.2183	fn burn_from(2184		&self,2185		sender: T::CrossAccountId,2186		from: T::CrossAccountId,2187		token: TokenId,2188		amount: u128,2189		budget: &dyn Budget,2190	) -> DispatchResultWithPostInfo;21912192	/// Check permission to nest token.2193	///2194	/// * `sender` - The user who initiated the check.2195	/// * `from` - The token that is checked for embedding.2196	/// * `under` - Token under which to check.2197	/// * `budget` - The maximum budget that can be spent on the check.2198	fn check_nesting(2199		&self,2200		sender: T::CrossAccountId,2201		from: (CollectionId, TokenId),2202		under: TokenId,2203		budget: &dyn Budget,2204	) -> DispatchResult;22052206	/// Nest one token into another.2207	///2208	/// * `under` - Token holder.2209	/// * `to_nest` - Nested token.2210	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22112212	/// Unnest token.2213	///2214	/// * `under` - Token holder.2215	/// * `to_nest` - Token to unnest.2216	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22172218	/// Get all user tokens.2219	///2220	/// * `account` - Account for which you need to get tokens.2221	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22222223	/// Get all the tokens in the collection.2224	fn collection_tokens(&self) -> Vec<TokenId>;22252226	/// Check if the token exists.2227	///2228	/// * `token` - Id token to check.2229	fn token_exists(&self, token: TokenId) -> bool;22302231	/// Get the id of the last minted token.2232	fn last_token_id(&self) -> TokenId;22332234	/// Get the owner of the token.2235	///2236	/// * `token` - The token for which you need to find out the owner.2237	fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22382239	/// Returns 10 tokens owners in no particular order.2240	///2241	/// * `token` - The token for which you need to find out the owners.2242	fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22432244	/// Get the value of the token property by key.2245	///2246	/// * `token` - Token with the property to get.2247	/// * `key` - Property name.2248	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22492250	/// Get a set of token properties by key vector.2251	///2252	/// * `token` - Token with the property to get.2253	/// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),2254	/// then all properties are returned.2255	fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22562257	/// Amount of unique collection tokens2258	fn total_supply(&self) -> u32;22592260	/// Amount of different tokens account has.2261	///2262	/// * `account` - The account for which need to get the balance.2263	fn account_balance(&self, account: T::CrossAccountId) -> u32;22642265	/// Amount of specific token account have.2266	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22672268	/// Amount of token pieces2269	fn total_pieces(&self, token: TokenId) -> Option<u128>;22702271	/// Get the number of parts of the token that a trusted user can manage.2272	///2273	/// * `sender` - Trusted user.2274	/// * `spender` - Owner of the token.2275	/// * `token` - The token for which to get the value.2276	fn allowance(2277		&self,2278		sender: T::CrossAccountId,2279		spender: T::CrossAccountId,2280		token: TokenId,2281	) -> u128;22822283	/// Get extension for RFT collection.2284	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22852286	/// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.2287	/// * `owner` - Token owner2288	/// * `operator` - Operator2289	/// * `approve` - Should operator status be granted or revoked?2290	fn set_allowance_for_all(2291		&self,2292		owner: T::CrossAccountId,2293		operator: T::CrossAccountId,2294		approve: bool,2295	) -> DispatchResultWithPostInfo;22962297	/// Tells whether the given `owner` approves the `operator`.2298	fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;22992300	/// Repairs a possibly broken item.2301	fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2302}23032304/// Extension for RFT collection.2305pub trait RefungibleExtensions<T>2306where2307	T: Config,2308{2309	/// Change the number of parts of the token.2310	///2311	/// When the value changes down, this function is equivalent to burning parts of the token.2312	///2313	/// * `sender` - The user calling the repartition operation. Must be the owner of the token.2314	/// * `token` - The token for which you want to change the number of parts.2315	/// * `amount` - The new value of the parts of the token.2316	fn repartition(2317		&self,2318		sender: &T::CrossAccountId,2319		token: TokenId,2320		amount: u128,2321	) -> DispatchResultWithPostInfo;2322}23232324/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].2325///2326/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.2327pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2328	let post_info = PostDispatchInfo {2329		actual_weight: Some(weight),2330		pays_fee: Pays::Yes,2331	};2332	match res {2333		Ok(()) => Ok(post_info),2334		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2335	}2336}23372338impl<T: Config> From<PropertiesError> for Error<T> {2339	fn from(error: PropertiesError) -> Self {2340		match error {2341			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2342			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2343			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2344			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2345			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2346		}2347	}2348}
modifiedpallets/configuration/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/configuration/src/benchmarking.rs
+++ b/pallets/configuration/src/benchmarking.rs
@@ -19,7 +19,7 @@
 use super::*;
 use frame_benchmarking::benchmarks;
 use frame_system::{EventRecord, RawOrigin};
-use frame_support::{assert_ok, traits::Currency};
+use frame_support::{assert_ok, traits::fungible::Inspect};
 
 fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
 	let events = frame_system::Pallet::<T>::events();
@@ -68,7 +68,7 @@
 	}
 
 	set_collator_selection_license_bond {
-		let bond_cost: Option<BalanceOf<T>> = Some(T::Currency::minimum_balance() * 10u32.into());
+		let bond_cost: Option<BalanceOf<T>> = Some(T::Balances::minimum_balance() * 10u32.into());
 	}: {
 		assert_ok!(
 			<Pallet<T>>::set_collator_selection_license_bond(RawOrigin::Root.into(), bond_cost.clone())
modifiedpallets/configuration/src/lib.rsdiffbeforeafterboth
--- a/pallets/configuration/src/lib.rs
+++ b/pallets/configuration/src/lib.rs
@@ -42,7 +42,7 @@
 mod pallet {
 	use super::*;
 	use frame_support::{
-		traits::{Get, ReservableCurrency, Currency},
+		traits::{fungible, Get, ReservableCurrency, Currency},
 		pallet_prelude::{StorageValue, ValueQuery, DispatchResult, IsType},
 		log,
 	};
@@ -50,15 +50,19 @@
 
 	pub use crate::weights::WeightInfo;
 	pub type BalanceOf<T> =
-		<<T as Config>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
+		<<T as Config>::Currency as fungible::Inspect<<T as SystemConfig>::AccountId>>::Balance;
 
 	#[pallet::config]
 	pub trait Config: frame_system::Config {
 		/// Overarching event type.
 		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
 
-		/// The currency mechanism.
-		type Currency: ReservableCurrency<Self::AccountId>;
+		type Currency: fungible::Inspect<Self::AccountId>
+			+ fungible::Mutate<Self::AccountId>
+			+ fungible::MutateFreeze<Self::AccountId>
+			+ fungible::InspectHold<Self::AccountId>
+			+ fungible::MutateHold<Self::AccountId>
+			+ fungible::BalancedHold<Self::AccountId>;
 
 		#[pallet::constant]
 		type DefaultWeightToFeeCoefficient: Get<u64>;