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

difftreelog

fix set prop for not existed token (#933)

bugrazoid2023-06-14parent: #807186d.patch.diff
in: master
* fix: set prop for not existed token

* optimize token checking

* remove comments

* test(token properties): on token non-existence

* fix PR comments

* rename value

* refactor(modify token properties): readability + grammar

* revert: unused import used for try-runtime

* fix prop permission check

* Add self_mint flag

* Add LazyValue

* fix tests

* fix unit tests

* fix docker

* fix mintCross sponsoring

* Generalize next_token_id

* fix: set sponsored properties

---------

20 files changed

modified.docker/Dockerfile-chain-dev-unitdiffbeforeafterboth
--- a/.docker/Dockerfile-chain-dev-unit
+++ b/.docker/Dockerfile-chain-dev-unit
@@ -17,4 +17,4 @@
 
 WORKDIR /dev_chain
 
-CMD cargo test --features=limit-testing --workspace
+CMD cargo test --features=limit-testing,tests --workspace
modifiedpallets/common/Cargo.tomldiffbeforeafterboth
--- a/pallets/common/Cargo.toml
+++ b/pallets/common/Cargo.toml
@@ -36,4 +36,5 @@
 	"up-pov-estimate-rpc/std",
 ]
 stubgen = ["evm-coder/stubgen"]
+tests = []
 try-runtime = ["frame-support/try-runtime"]
modifiedpallets/common/src/eth.rsdiffbeforeafterboth
--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -131,6 +131,18 @@
 	value: evm_coder::types::Bytes,
 }
 
+impl Property {
+	/// Property key.
+	pub fn key(&self) -> &str {
+		self.key.as_str()
+	}
+
+	/// Property value.
+	pub fn value(&self) -> &[u8] {
+		self.value.0.as_slice()
+	}
+}
+
 impl TryFrom<up_data_structs::Property> for Property {
 	type Error = pallet_evm_coder_substrate::execution::Error;
 
@@ -227,11 +239,9 @@
 			Some(value) => match value {
 				0 => Ok(Some(false)),
 				1 => Ok(Some(true)),
-				_ => {
-					return Err(Self::Error::Revert(format!(
-						"can't convert value to boolean \"{value}\""
-					)))
-				}
+				_ => Err(Self::Error::Revert(format!(
+					"can't convert value to boolean \"{value}\""
+				))),
 			},
 			None => Ok(None),
 		};
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::{68		Get,69		fungible::{Balanced, Debt, Inspect},70		tokens::{Imbalance, Precision, Preservation},71	},72	dispatch::Pays,73	transactional, fail,74};75use up_data_structs::{76	AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionFlags,77	RpcCollectionFlags, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,78	COLLECTION_ADMINS_LIMIT, TokenId, TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP,79	CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,80	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,81	CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit, budget::Budget,82	PhantomType, Property, CollectionProperties as CollectionPropertiesT, TokenProperties,83	PropertiesPermissionMap, PropertyKey, PropertyValue, PropertyPermission, PropertiesError,84	TokenOwnerError, PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope,85	CollectionPermissions,86};87use up_pov_estimate_rpc::PovInfo;8889pub use pallet::*;90use sp_core::H160;91use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, traits::Zero};9293#[cfg(feature = "runtime-benchmarks")]94pub mod benchmarking;95pub mod dispatch;96pub mod erc;97pub mod eth;98pub mod helpers;99#[allow(missing_docs)]100pub mod weights;101/// Weight info.102pub type SelfWeightOf<T> = <T as Config>::WeightInfo;103104/// Collection handle contains information about collection data and id.105/// Also provides functionality to count consumed gas.106///107/// CollectionHandle is used as a generic wrapper for collections of all types (except native fungible).108/// It allows to perform common operations and queries on any collection type,109/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].110#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]111pub struct CollectionHandle<T: Config> {112	/// Collection id113	pub id: CollectionId,114	collection: Collection<T::AccountId>,115	/// Substrate recorder for counting consumed gas116	pub recorder: SubstrateRecorder<T>,117}118119impl<T: Config> WithRecorder<T> for CollectionHandle<T> {120	fn recorder(&self) -> &SubstrateRecorder<T> {121		&self.recorder122	}123	fn into_recorder(self) -> SubstrateRecorder<T> {124		self.recorder125	}126}127128impl<T: Config> CollectionHandle<T> {129	/// Same as [CollectionHandle::new] but with an explicit gas limit.130	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {131		Self::new_with_recorder(id, SubstrateRecorder::new(gas_limit))132	}133134	/// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].135	pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {136		<CollectionById<T>>::get(id).map(|collection| Self {137			id,138			collection,139			recorder,140		})141	}142143	/// Retrives collection data from storage and creates collection handle with default parameters.144	/// If collection not found return `None`145	pub fn new(id: CollectionId) -> Option<Self> {146		Self::new_with_gas_limit(id, u64::MAX)147	}148149	/// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.150	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {151		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)152	}153154	/// Consume gas for reading.155	pub fn consume_store_reads(156		&self,157		reads: u64,158	) -> pallet_evm_coder_substrate::execution::Result<()> {159		self.recorder().consume_store_reads(reads)160	}161162	/// Consume gas for writing.163	pub fn consume_store_writes(164		&self,165		writes: u64,166	) -> pallet_evm_coder_substrate::execution::Result<()> {167		self.recorder().consume_store_writes(writes)168	}169170	/// Consume gas for reading and writing.171	pub fn consume_store_reads_and_writes(172		&self,173		reads: u64,174		writes: u64,175	) -> pallet_evm_coder_substrate::execution::Result<()> {176		self.recorder()177			.consume_store_reads_and_writes(reads, writes)178	}179180	/// Save collection to storage.181	pub fn save(&self) -> DispatchResult {182		<CollectionById<T>>::insert(self.id, &self.collection);183		Ok(())184	}185186	/// Set collection sponsor.187	///188	/// Unique collections allows sponsoring for certain actions.189	/// This method allows you to set the sponsor of the collection.190	/// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].191	pub fn set_sponsor(192		&mut self,193		sender: &T::CrossAccountId,194		sponsor: T::AccountId,195	) -> DispatchResult {196		self.check_is_internal()?;197		self.check_is_owner_or_admin(sender)?;198199		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());200201		<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));202		<PalletEvm<T>>::deposit_log(203			erc::CollectionHelpersEvents::CollectionChanged {204				collection_id: eth::collection_id_to_address(self.id),205			}206			.to_log(T::ContractAddress::get()),207		);208209		self.save()210	}211212	/// Force set `sponsor`.213	///214	/// Differs from [`set_sponsor`][`Self::set_sponsor`] in that confirmation215	/// from the `sponsor` is not required.216	///217	/// # Arguments218	///219	/// * `sender`: Caller's account.220	/// * `sponsor`: ID of the account of the sponsor-to-be.221	pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {222		self.check_is_internal()?;223224		self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());225226		<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));227		<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(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	/// Confirm sponsorship239	///240	/// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.241	/// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].242	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {243		self.check_is_internal()?;244		ensure!(245			self.collection.sponsorship.pending_sponsor() == Some(sender),246			Error::<T>::ConfirmSponsorshipFail247		);248249		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());250251		<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));252		<PalletEvm<T>>::deposit_log(253			erc::CollectionHelpersEvents::CollectionChanged {254				collection_id: eth::collection_id_to_address(self.id),255			}256			.to_log(T::ContractAddress::get()),257		);258259		self.save()260	}261262	/// Remove collection sponsor.263	pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {264		self.check_is_internal()?;265		self.check_is_owner_or_admin(sender)?;266267		self.collection.sponsorship = SponsorshipState::Disabled;268269		<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));270		<PalletEvm<T>>::deposit_log(271			erc::CollectionHelpersEvents::CollectionChanged {272				collection_id: eth::collection_id_to_address(self.id),273			}274			.to_log(T::ContractAddress::get()),275		);276		self.save()277	}278279	/// Force remove `sponsor`.280	///281	/// Differs from `remove_sponsor` in that282	/// it doesn't require consent from the `owner` of the collection.283	pub fn force_remove_sponsor(&mut self) -> DispatchResult {284		self.check_is_internal()?;285286		self.collection.sponsorship = SponsorshipState::Disabled;287288		<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));289		<PalletEvm<T>>::deposit_log(290			erc::CollectionHelpersEvents::CollectionChanged {291				collection_id: eth::collection_id_to_address(self.id),292			}293			.to_log(T::ContractAddress::get()),294		);295		self.save()296	}297298	/// Checks that the collection was created with, and must be operated upon through **Unique API**.299	/// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.300	pub fn check_is_internal(&self) -> DispatchResult {301		if self.flags.external {302			return Err(<Error<T>>::CollectionIsExternal)?;303		}304305		Ok(())306	}307308	/// Checks that the collection was created with, and must be operated upon through an **assimilated API**.309	/// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.310	pub fn check_is_external(&self) -> DispatchResult {311		if !self.flags.external {312			return Err(<Error<T>>::CollectionIsInternal)?;313		}314315		Ok(())316	}317}318319impl<T: Config> Deref for CollectionHandle<T> {320	type Target = Collection<T::AccountId>;321322	fn deref(&self) -> &Self::Target {323		&self.collection324	}325}326327impl<T: Config> DerefMut for CollectionHandle<T> {328	fn deref_mut(&mut self) -> &mut Self::Target {329		&mut self.collection330	}331}332333impl<T: Config> CollectionHandle<T> {334	/// Checks if the `user` is the owner of the collection.335	pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {336		ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);337		Ok(())338	}339340	/// Returns **true** if the `user` is the owner or administrator of the collection.341	pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {342		*user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))343	}344345	/// Checks if the `user` is the owner or administrator of the collection.346	pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {347		ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);348		Ok(())349	}350351	/// Returns **true** if352	/// * the `user`is a collection owner or admin353	/// * the collection limits allow the owner/admins to transfer/burn any collection token354	pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {355		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)356	}357358	/// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.359	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {360		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)361	}362363	/// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.364	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {365		ensure!(366			<Allowlist<T>>::get((self.id, user)),367			<Error<T>>::AddressNotInAllowlist368		);369		Ok(())370	}371372	/// Changes collection owner to another account373	/// #### Store read/writes374	/// 1 writes375	pub fn change_owner(376		&mut self,377		caller: T::CrossAccountId,378		new_owner: T::CrossAccountId,379	) -> DispatchResult {380		self.check_is_internal()?;381		self.check_is_owner(&caller)?;382		self.collection.owner = new_owner.as_sub().clone();383384		<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(385			self.id,386			new_owner.as_sub().clone(),387		));388		<PalletEvm<T>>::deposit_log(389			erc::CollectionHelpersEvents::CollectionChanged {390				collection_id: eth::collection_id_to_address(self.id),391			}392			.to_log(T::ContractAddress::get()),393		);394395		self.save()396	}397}398399#[frame_support::pallet]400pub mod pallet {401402	use super::*;403	use dispatch::CollectionDispatch;404	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};405	use up_data_structs::{TokenId, mapping::TokenAddressMapping};406	use scale_info::TypeInfo;407	use weights::WeightInfo;408409	#[pallet::config]410	pub trait Config:411		frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo412	{413		/// Weight information for functions of this pallet.414		type WeightInfo: WeightInfo;415416		/// Events compatible with [`frame_system::Config::Event`].417		type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;418419		/// Handler of accounts and payment.420		type Currency: Balanced<Self::AccountId> + Inspect<Self::AccountId>;421422		/// Set price to create a collection.423		#[pallet::constant]424		type CollectionCreationPrice: Get<425			<<Self as Config>::Currency as Inspect<Self::AccountId>>::Balance,426		>;427428		/// Dispatcher of operations on collections.429		type CollectionDispatch: CollectionDispatch<Self>;430431		/// Account which holds the chain's treasury.432		type TreasuryAccountId: Get<Self::AccountId>;433434		/// Address under which the CollectionHelper contract would be available.435		#[pallet::constant]436		type ContractAddress: Get<H160>;437438		/// Mapper for token addresses to Ethereum addresses.439		type EvmTokenAddressMapping: TokenAddressMapping<H160>;440441		/// Mapper for token addresses to [`CrossAccountId`].442		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;443	}444445	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);446	/// Collection id for native fungible collction.447	pub const NATIVE_FUNGIBLE_COLLECTION_ID: CollectionId = CollectionId(0);448449	#[pallet::pallet]450	#[pallet::storage_version(STORAGE_VERSION)]451	pub struct Pallet<T>(_);452453	#[pallet::extra_constants]454	impl<T: Config> Pallet<T> {455		/// Maximum admins per collection.456		pub fn collection_admins_limit() -> u32 {457			COLLECTION_ADMINS_LIMIT458		}459	}460461	#[pallet::genesis_config]462	pub struct GenesisConfig<T>(PhantomData<T>);463464	#[cfg(feature = "std")]465	impl<T: Config> Default for GenesisConfig<T> {466		fn default() -> Self {467			Self(Default::default())468		}469	}470471	#[pallet::genesis_build]472	impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {473		fn build(&self) {474			StorageVersion::new(1).put::<Pallet<T>>();475		}476	}477478	impl<T: Config> Pallet<T> {479		/// Helper function that handles deposit events480		pub fn deposit_event(event: Event<T>) {481			let event = <T as Config>::RuntimeEvent::from(event);482			let event = event.into();483			<frame_system::Pallet<T>>::deposit_event(event)484		}485	}486487	#[pallet::event]488	pub enum Event<T: Config> {489		/// New collection was created490		CollectionCreated(491			/// Globally unique identifier of newly created collection.492			CollectionId,493			/// [`CollectionMode`] converted into _u8_.494			u8,495			/// Collection owner.496			T::AccountId,497		),498499		/// New collection was destroyed500		CollectionDestroyed(501			/// Globally unique identifier of collection.502			CollectionId,503		),504505		/// New item was created.506		ItemCreated(507			/// Id of the collection where item was created.508			CollectionId,509			/// Id of an item. Unique within the collection.510			TokenId,511			/// Owner of newly created item512			T::CrossAccountId,513			/// Always 1 for NFT514			u128,515		),516517		/// Collection item was burned.518		ItemDestroyed(519			/// Id of the collection where item was destroyed.520			CollectionId,521			/// Identifier of burned NFT.522			TokenId,523			/// Which user has destroyed its tokens.524			T::CrossAccountId,525			/// Amount of token pieces destroed. Always 1 for NFT.526			u128,527		),528529		/// Item was transferred530		Transfer(531			/// Id of collection to which item is belong.532			CollectionId,533			/// Id of an item.534			TokenId,535			/// Original owner of item.536			T::CrossAccountId,537			/// New owner of item.538			T::CrossAccountId,539			/// Amount of token pieces transfered. Always 1 for NFT.540			u128,541		),542543		/// Amount pieces of token owned by `sender` was approved for `spender`.544		Approved(545			/// Id of collection to which item is belong.546			CollectionId,547			/// Id of an item.548			TokenId,549			/// Original owner of item.550			T::CrossAccountId,551			/// Id for which the approval was granted.552			T::CrossAccountId,553			/// Amount of token pieces transfered. Always 1 for NFT.554			u128,555		),556557		/// A `sender` approves operations on all owned tokens for `spender`.558		ApprovedForAll(559			/// Id of collection to which item is belong.560			CollectionId,561			/// Owner of a wallet.562			T::CrossAccountId,563			/// Id for which operator status was granted or rewoked.564			T::CrossAccountId,565			/// Is operator status granted or revoked?566			bool,567		),568569		/// The colletion property has been added or edited.570		CollectionPropertySet(571			/// Id of collection to which property has been set.572			CollectionId,573			/// The property that was set.574			PropertyKey,575		),576577		/// The property has been deleted.578		CollectionPropertyDeleted(579			/// Id of collection to which property has been deleted.580			CollectionId,581			/// The property that was deleted.582			PropertyKey,583		),584585		/// The token property has been added or edited.586		TokenPropertySet(587			/// Identifier of the collection whose token has the property set.588			CollectionId,589			/// The token for which the property was set.590			TokenId,591			/// The property that was set.592			PropertyKey,593		),594595		/// The token property has been deleted.596		TokenPropertyDeleted(597			/// Identifier of the collection whose token has the property deleted.598			CollectionId,599			/// The token for which the property was deleted.600			TokenId,601			/// The property that was deleted.602			PropertyKey,603		),604605		/// The token property permission of a collection has been set.606		PropertyPermissionSet(607			/// ID of collection to which property permission has been set.608			CollectionId,609			/// The property permission that was set.610			PropertyKey,611		),612613		/// Address was added to the allow list.614		AllowListAddressAdded(615			/// ID of the affected collection.616			CollectionId,617			/// Address of the added account.618			T::CrossAccountId,619		),620621		/// Address was removed from the allow list.622		AllowListAddressRemoved(623			/// ID of the affected collection.624			CollectionId,625			/// Address of the removed account.626			T::CrossAccountId,627		),628629		/// Collection admin was added.630		CollectionAdminAdded(631			/// ID of the affected collection.632			CollectionId,633			/// Admin address.634			T::CrossAccountId,635		),636637		/// Collection admin was removed.638		CollectionAdminRemoved(639			/// ID of the affected collection.640			CollectionId,641			/// Removed admin address.642			T::CrossAccountId,643		),644645		/// Collection limits were set.646		CollectionLimitSet(647			/// ID of the affected collection.648			CollectionId,649		),650651		/// Collection owned was changed.652		CollectionOwnerChanged(653			/// ID of the affected collection.654			CollectionId,655			/// New owner address.656			T::AccountId,657		),658659		/// Collection permissions were set.660		CollectionPermissionSet(661			/// ID of the affected collection.662			CollectionId,663		),664665		/// Collection sponsor was set.666		CollectionSponsorSet(667			/// ID of the affected collection.668			CollectionId,669			/// New sponsor address.670			T::AccountId,671		),672673		/// New sponsor was confirm.674		SponsorshipConfirmed(675			/// ID of the affected collection.676			CollectionId,677			/// New sponsor address.678			T::AccountId,679		),680681		/// Collection sponsor was removed.682		CollectionSponsorRemoved(683			/// ID of the affected collection.684			CollectionId,685		),686	}687688	#[pallet::error]689	pub enum Error<T> {690		/// This collection does not exist.691		CollectionNotFound,692		/// Sender parameter and item owner must be equal.693		MustBeTokenOwner,694		/// No permission to perform action695		NoPermission,696		/// Destroying only empty collections is allowed697		CantDestroyNotEmptyCollection,698		/// Collection is not in mint mode.699		PublicMintingNotAllowed,700		/// Address is not in allow list.701		AddressNotInAllowlist,702703		/// Collection name can not be longer than 63 char.704		CollectionNameLimitExceeded,705		/// Collection description can not be longer than 255 char.706		CollectionDescriptionLimitExceeded,707		/// Token prefix can not be longer than 15 char.708		CollectionTokenPrefixLimitExceeded,709		/// Total collections bound exceeded.710		TotalCollectionsLimitExceeded,711		/// Exceeded max admin count712		CollectionAdminCountExceeded,713		/// Collection limit bounds per collection exceeded714		CollectionLimitBoundsExceeded,715		/// Tried to enable permissions which are only permitted to be disabled716		OwnerPermissionsCantBeReverted,717		/// Collection settings not allowing items transferring718		TransferNotAllowed,719		/// Account token limit exceeded per collection720		AccountTokenLimitExceeded,721		/// Collection token limit exceeded722		CollectionTokenLimitExceeded,723		/// Metadata flag frozen724		MetadataFlagFrozen,725726		/// Item does not exist727		TokenNotFound,728		/// Item is balance not enough729		TokenValueTooLow,730		/// Requested value is more than the approved731		ApprovedValueTooLow,732		/// Tried to approve more than owned733		CantApproveMoreThanOwned,734		/// Only spending from eth mirror could be approved735		AddressIsNotEthMirror,736737		/// Can't transfer tokens to ethereum zero address738		AddressIsZero,739740		/// The operation is not supported741		UnsupportedOperation,742743		/// Insufficient funds to perform an action744		NotSufficientFounds,745746		/// User does not satisfy the nesting rule747		UserIsNotAllowedToNest,748		/// Only tokens from specific collections may nest tokens under this one749		SourceCollectionIsNotAllowedToNest,750751		/// Tried to store more data than allowed in collection field752		CollectionFieldSizeExceeded,753754		/// Tried to store more property data than allowed755		NoSpaceForProperty,756757		/// Tried to store more property keys than allowed758		PropertyLimitReached,759760		/// Property key is too long761		PropertyKeyIsTooLong,762763		/// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed764		InvalidCharacterInPropertyKey,765766		/// Empty property keys are forbidden767		EmptyPropertyKey,768769		/// Tried to access an external collection with an internal API770		CollectionIsExternal,771772		/// Tried to access an internal collection with an external API773		CollectionIsInternal,774775		/// This address is not set as sponsor, use setCollectionSponsor first.776		ConfirmSponsorshipFail,777778		/// The user is not an administrator.779		UserIsNotCollectionAdmin,780	}781782	/// Storage of the count of created collections. Essentially contains the last collection ID.783	#[pallet::storage]784	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;785786	/// Storage of the count of deleted collections.787	#[pallet::storage]788	pub type DestroyedCollectionCount<T> =789		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;790791	/// Storage of collection info.792	#[pallet::storage]793	pub type CollectionById<T> = StorageMap<794		Hasher = Blake2_128Concat,795		Key = CollectionId,796		Value = Collection<<T as frame_system::Config>::AccountId>,797		QueryKind = OptionQuery,798	>;799800	/// Storage of collection properties.801	#[pallet::storage]802	#[pallet::getter(fn collection_properties)]803	pub type CollectionProperties<T> = StorageMap<804		Hasher = Blake2_128Concat,805		Key = CollectionId,806		Value = CollectionPropertiesT,807		QueryKind = ValueQuery,808	>;809810	/// Storage of token property permissions of a collection.811	#[pallet::storage]812	#[pallet::getter(fn property_permissions)]813	pub type CollectionPropertyPermissions<T> = StorageMap<814		Hasher = Blake2_128Concat,815		Key = CollectionId,816		Value = PropertiesPermissionMap,817		QueryKind = ValueQuery,818	>;819820	/// Storage of the amount of collection admins.821	#[pallet::storage]822	pub type AdminAmount<T> = StorageMap<823		Hasher = Blake2_128Concat,824		Key = CollectionId,825		Value = u32,826		QueryKind = ValueQuery,827	>;828829	/// List of collection admins.830	#[pallet::storage]831	pub type IsAdmin<T: Config> = StorageNMap<832		Key = (833			Key<Blake2_128Concat, CollectionId>,834			Key<Blake2_128Concat, T::CrossAccountId>,835		),836		Value = bool,837		QueryKind = ValueQuery,838	>;839840	/// Allowlisted collection users.841	#[pallet::storage]842	pub type Allowlist<T: Config> = StorageNMap<843		Key = (844			Key<Blake2_128Concat, CollectionId>,845			Key<Blake2_128Concat, T::CrossAccountId>,846		),847		Value = bool,848		QueryKind = ValueQuery,849	>;850851	/// Not used by code, exists only to provide some types to metadata.852	#[pallet::storage]853	pub type DummyStorageValue<T: Config> = StorageValue<854		Value = (855			CollectionStats,856			CollectionId,857			TokenId,858			TokenChild,859			PhantomType<(860				TokenData<T::CrossAccountId>,861				RpcCollection<T::AccountId>,862				// PoV Estimate Info863				PovInfo,864			)>,865		),866		QueryKind = OptionQuery,867	>;868}869870impl<T: Config> Pallet<T> {871	/// Enshure that receiver address is correct.872	///873	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.874	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {875		ensure!(876			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,877			<Error<T>>::AddressIsZero878		);879		Ok(())880	}881882	/// Get a vector of collection admins.883	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {884		<IsAdmin<T>>::iter_prefix((collection,))885			.map(|(a, _)| a)886			.collect()887	}888889	/// Get a vector of users allowed to mint tokens.890	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {891		<Allowlist<T>>::iter_prefix((collection,))892			.map(|(a, _)| a)893			.collect()894	}895896	/// Is `user` allowed to mint token in `collection`.897	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {898		<Allowlist<T>>::get((collection, user))899	}900901	/// Get statistics of collections.902	pub fn collection_stats() -> CollectionStats {903		let created = <CreatedCollectionCount<T>>::get();904		let destroyed = <DestroyedCollectionCount<T>>::get();905		CollectionStats {906			created: created.0,907			destroyed: destroyed.0,908			alive: created.0 - destroyed.0,909		}910	}911912	/// Get the effective limits for the collection.913	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {914		let collection = <CollectionById<T>>::get(collection)?;915		let limits = collection.limits;916		let effective_limits = CollectionLimits {917			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),918			sponsored_data_size: Some(limits.sponsored_data_size()),919			sponsored_data_rate_limit: Some(920				limits921					.sponsored_data_rate_limit922					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),923			),924			token_limit: Some(limits.token_limit()),925			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(926				match collection.mode {927					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,928					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,929					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,930				},931			)),932			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),933			owner_can_transfer: Some(limits.owner_can_transfer()),934			owner_can_destroy: Some(limits.owner_can_destroy()),935			transfers_enabled: Some(limits.transfers_enabled()),936		};937938		Some(effective_limits)939	}940941	/// Returns information about the `collection` adapted for rpc.942	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {943		let Collection {944			name,945			description,946			owner,947			mode,948			token_prefix,949			sponsorship,950			limits,951			permissions,952			flags,953		} = <CollectionById<T>>::get(collection)?;954955		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)956			.into_iter()957			.map(|(key, permission)| PropertyKeyPermission { key, permission })958			.collect();959960		let properties = <CollectionProperties<T>>::get(collection)961			.into_iter()962			.map(|(key, value)| Property { key, value })963			.collect();964965		let permissions = CollectionPermissions {966			access: Some(permissions.access()),967			mint_mode: Some(permissions.mint_mode()),968			nesting: Some(permissions.nesting().clone()),969		};970971		Some(RpcCollection {972			name: name.into_inner(),973			description: description.into_inner(),974			owner,975			mode,976			token_prefix: token_prefix.into_inner(),977			sponsorship,978			limits,979			permissions,980			token_property_permissions,981			properties,982			read_only: flags.external,983984			flags: RpcCollectionFlags {985				foreign: flags.foreign,986				erc721metadata: flags.erc721metadata,987			},988		})989	}990}991992macro_rules! limit_default {993	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{994		$(995			if let Some($new) = $new.$field {996				let $old = $old.$field($($arg)?);997				let _ = $new;998				let _ = $old;999				$check1000			} else {1001				$new.$field = $old.$field1002			}1003		)*1004	}};1005}1006macro_rules! limit_default_clone {1007	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1008		$(1009			if let Some($new) = $new.$field.clone() {1010				let $old = $old.$field($($arg)?);1011				let _ = $new;1012				let _ = $old;1013				$check1014			} else {1015				$new.$field = $old.$field.clone()1016			}1017		)*1018	}};1019}10201021impl<T: Config> Pallet<T> {1022	/// Create new collection.1023	///1024	/// * `owner` - The owner of the collection.1025	/// * `data` - Description of the created collection.1026	/// * `flags` - Extra flags to store.1027	pub fn init_collection(1028		owner: T::CrossAccountId,1029		payer: T::CrossAccountId,1030		data: CreateCollectionData<T::AccountId>,1031		flags: CollectionFlags,1032	) -> Result<CollectionId, DispatchError> {1033		{1034			ensure!(1035				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1036				Error::<T>::CollectionTokenPrefixLimitExceeded1037			);1038		}10391040		let created_count = <CreatedCollectionCount<T>>::get()1041			.01042			.checked_add(1)1043			.ok_or(ArithmeticError::Overflow)?;1044		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1045		let id = CollectionId(created_count);10461047		// bound Total number of collections1048		ensure!(1049			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1050			<Error<T>>::TotalCollectionsLimitExceeded1051		);10521053		// =========10541055		let collection = Collection {1056			owner: owner.as_sub().clone(),1057			name: data.name,1058			mode: data.mode.clone(),1059			description: data.description,1060			token_prefix: data.token_prefix,1061			sponsorship: data1062				.pending_sponsor1063				.map(SponsorshipState::Unconfirmed)1064				.unwrap_or_default(),1065			limits: data1066				.limits1067				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1068				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,1069			permissions: data1070				.permissions1071				.map(|permissions| {1072					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1073				})1074				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1075			flags,1076		};10771078		let mut collection_properties = CollectionPropertiesT::new();1079		collection_properties1080			.try_set_from_iter(data.properties.into_iter())1081			.map_err(<Error<T>>::from)?;10821083		CollectionProperties::<T>::insert(id, collection_properties);10841085		let mut token_props_permissions = PropertiesPermissionMap::new();1086		token_props_permissions1087			.try_set_from_iter(data.token_property_permissions.into_iter())1088			.map_err(<Error<T>>::from)?;10891090		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);10911092		// Take a (non-refundable) deposit of collection creation1093		{1094			let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1095			imbalance.subsume(<T as Config>::Currency::deposit(1096				&T::TreasuryAccountId::get(),1097				T::CollectionCreationPrice::get(),1098				Precision::Exact,1099			)?);1100			let credit =1101				<T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1102					.map_err(|_| Error::<T>::NotSufficientFounds)?;11031104			debug_assert!(credit.peek().is_zero())1105		}11061107		<CreatedCollectionCount<T>>::put(created_count);1108		<Pallet<T>>::deposit_event(Event::CollectionCreated(1109			id,1110			data.mode.id(),1111			owner.as_sub().clone(),1112		));1113		<PalletEvm<T>>::deposit_log(1114			erc::CollectionHelpersEvents::CollectionCreated {1115				owner: *owner.as_eth(),1116				collection_id: eth::collection_id_to_address(id),1117			}1118			.to_log(T::ContractAddress::get()),1119		);1120		<CollectionById<T>>::insert(id, collection);1121		Ok(id)1122	}11231124	/// Destroy collection.1125	///1126	/// * `collection` - Collection handler.1127	/// * `sender` - The owner or administrator of the collection.1128	pub fn destroy_collection(1129		collection: CollectionHandle<T>,1130		sender: &T::CrossAccountId,1131	) -> DispatchResult {1132		ensure!(1133			collection.limits.owner_can_destroy(),1134			<Error<T>>::NoPermission,1135		);1136		collection.check_is_owner(sender)?;11371138		let destroyed_collections = <DestroyedCollectionCount<T>>::get()1139			.01140			.checked_add(1)1141			.ok_or(ArithmeticError::Overflow)?;11421143		// =========11441145		<DestroyedCollectionCount<T>>::put(destroyed_collections);1146		<CollectionById<T>>::remove(collection.id);1147		<AdminAmount<T>>::remove(collection.id);1148		let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1149		let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1150		<CollectionProperties<T>>::remove(collection.id);11511152		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));11531154		<PalletEvm<T>>::deposit_log(1155			erc::CollectionHelpersEvents::CollectionDestroyed {1156				collection_id: eth::collection_id_to_address(collection.id),1157			}1158			.to_log(T::ContractAddress::get()),1159		);1160		Ok(())1161	}11621163	/// This function sets or removes a collection properties according to1164	/// `properties_updates` contents:1165	/// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1166	/// * removes a property under the <key> if the value is `None` `(<key>, None)`.1167	///1168	/// This function fires an event for each property change.1169	/// In case of an error, all the changes (including the events) will be reverted1170	/// since the function is transactional.1171	#[transactional]1172	fn modify_collection_properties(1173		collection: &CollectionHandle<T>,1174		sender: &T::CrossAccountId,1175		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1176	) -> DispatchResult {1177		collection.check_is_owner_or_admin(sender)?;11781179		let mut stored_properties = <CollectionProperties<T>>::get(collection.id);11801181		for (key, value) in properties_updates {1182			match value {1183				Some(value) => {1184					stored_properties1185						.try_set(key.clone(), value)1186						.map_err(<Error<T>>::from)?;11871188					Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1189					<PalletEvm<T>>::deposit_log(1190						erc::CollectionHelpersEvents::CollectionChanged {1191							collection_id: eth::collection_id_to_address(collection.id),1192						}1193						.to_log(T::ContractAddress::get()),1194					);1195				}1196				None => {1197					stored_properties.remove(&key).map_err(<Error<T>>::from)?;11981199					Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1200					<PalletEvm<T>>::deposit_log(1201						erc::CollectionHelpersEvents::CollectionChanged {1202							collection_id: eth::collection_id_to_address(collection.id),1203						}1204						.to_log(T::ContractAddress::get()),1205					);1206				}1207			}1208		}12091210		<CollectionProperties<T>>::set(collection.id, stored_properties);12111212		Ok(())1213	}12141215	/// A batch operation to add, edit or remove properties for a token.1216	/// It sets or removes a token's properties according to1217	/// `properties_updates` contents:1218	/// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1219	/// * removes a property under the <key> if the value is `None` `(<key>, None)`.1220	///1221	/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.1222	/// - `is_token_create`: Indicates that method is called during token initialization.1223	///   Allows to bypass ownership check.1224	///1225	/// All affected properties should have `mutable` permission1226	/// to be **deleted** or to be **set more than once**,1227	/// and the sender should have permission to edit those properties.1228	///1229	/// This function fires an event for each property change.1230	/// In case of an error, all the changes (including the events) will be reverted1231	/// since the function is transactional.1232	pub fn modify_token_properties(1233		collection: &CollectionHandle<T>,1234		sender: &T::CrossAccountId,1235		token_id: TokenId,1236		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1237		is_token_create: bool,1238		mut stored_properties: TokenProperties,1239		is_token_owner: impl Fn() -> Result<bool, DispatchError>,1240		set_token_properties: impl FnOnce(TokenProperties),1241		log: evm_coder::ethereum::Log,1242	) -> DispatchResult {1243		let is_collection_admin = collection.is_owner_or_admin(sender);1244		let permissions = Self::property_permissions(collection.id);12451246		let mut token_owner_result = None;1247		let mut is_token_owner = || -> Result<bool, DispatchError> {1248			*token_owner_result.get_or_insert_with(&is_token_owner)1249		};12501251		for (key, value) in properties_updates {1252			let permission = permissions1253				.get(&key)1254				.cloned()1255				.unwrap_or_else(PropertyPermission::none);12561257			let is_property_exists = stored_properties.get(&key).is_some();12581259			match permission {1260				PropertyPermission { mutable: false, .. } if is_property_exists => {1261					return Err(<Error<T>>::NoPermission.into());1262				}12631264				PropertyPermission {1265					collection_admin,1266					token_owner,1267					..1268				} => {1269					//TODO: investigate threats during public minting.1270					let is_token_create =1271						is_token_create && (collection_admin || token_owner) && value.is_some();1272					if !(is_token_create1273						|| (collection_admin && is_collection_admin)1274						|| (token_owner && is_token_owner()?))1275					{1276						fail!(<Error<T>>::NoPermission);1277					}1278				}1279			}12801281			match value {1282				Some(value) => {1283					stored_properties1284						.try_set(key.clone(), value)1285						.map_err(<Error<T>>::from)?;12861287					Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1288				}1289				None => {1290					stored_properties.remove(&key).map_err(<Error<T>>::from)?;12911292					Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1293				}1294			}12951296			<PalletEvm<T>>::deposit_log(log.clone());1297		}12981299		set_token_properties(stored_properties);13001301		Ok(())1302	}13031304	/// Sets or unsets the approval of a given operator.1305	///1306	/// The `operator` is allowed to transfer all token pieces of the `owner` on their behalf.1307	/// - `owner`: Token owner1308	/// - `operator`: Operator1309	/// - `approve`: Should operator status be granted or revoked?1310	pub fn set_allowance_for_all(1311		collection: &CollectionHandle<T>,1312		owner: &T::CrossAccountId,1313		operator: &T::CrossAccountId,1314		approve: bool,1315		set_allowance: impl FnOnce(),1316		log: evm_coder::ethereum::Log,1317	) -> DispatchResult {1318		if collection.permissions.access() == AccessMode::AllowList {1319			collection.check_allowlist(owner)?;1320			collection.check_allowlist(operator)?;1321		}13221323		Self::ensure_correct_receiver(operator)?;13241325		set_allowance();13261327		<PalletEvm<T>>::deposit_log(log);1328		Self::deposit_event(Event::ApprovedForAll(1329			collection.id,1330			owner.clone(),1331			operator.clone(),1332			approve,1333		));1334		Ok(())1335	}13361337	/// Set collection property.1338	///1339	/// * `collection` - Collection handler.1340	/// * `sender` - The owner or administrator of the collection.1341	/// * `property` - The property to set.1342	pub fn set_collection_property(1343		collection: &CollectionHandle<T>,1344		sender: &T::CrossAccountId,1345		property: Property,1346	) -> DispatchResult {1347		Self::set_collection_properties(collection, sender, [property].into_iter())1348	}13491350	/// Set a scoped collection property, where the scope is a special prefix1351	/// prohibiting a user access to change the property directly.1352	///1353	/// * `collection_id` - ID of the collection for which the property is being set.1354	/// * `scope` - Property scope.1355	/// * `property` - The property to set.1356	pub fn set_scoped_collection_property(1357		collection_id: CollectionId,1358		scope: PropertyScope,1359		property: Property,1360	) -> DispatchResult {1361		CollectionProperties::<T>::try_mutate(collection_id, |properties| {1362			properties.try_scoped_set(scope, property.key, property.value)1363		})1364		.map_err(<Error<T>>::from)?;13651366		Ok(())1367	}13681369	/// Set scoped collection properties, where the scope is a special prefix1370	/// prohibiting a user access to change the properties directly.1371	///1372	/// * `collection_id` - ID of the collection for which the properties is being set.1373	/// * `scope` - Property scope.1374	/// * `properties` - The properties to set.1375	pub fn set_scoped_collection_properties(1376		collection_id: CollectionId,1377		scope: PropertyScope,1378		properties: impl Iterator<Item = Property>,1379	) -> DispatchResult {1380		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1381			stored_properties.try_scoped_set_from_iter(scope, properties)1382		})1383		.map_err(<Error<T>>::from)?;13841385		Ok(())1386	}13871388	/// Set collection properties.1389	///1390	/// * `collection` - Collection handler.1391	/// * `sender` - The owner or administrator of the collection.1392	/// * `properties` - The properties to set.1393	pub fn set_collection_properties(1394		collection: &CollectionHandle<T>,1395		sender: &T::CrossAccountId,1396		properties: impl Iterator<Item = Property>,1397	) -> DispatchResult {1398		Self::modify_collection_properties(1399			collection,1400			sender,1401			properties.map(|property| (property.key, Some(property.value))),1402		)1403	}14041405	/// Delete collection property.1406	///1407	/// * `collection` - Collection handler.1408	/// * `sender` - The owner or administrator of the collection.1409	/// * `property` - The property to delete.1410	pub fn delete_collection_property(1411		collection: &CollectionHandle<T>,1412		sender: &T::CrossAccountId,1413		property_key: PropertyKey,1414	) -> DispatchResult {1415		Self::delete_collection_properties(collection, sender, [property_key].into_iter())1416	}14171418	/// Delete collection properties.1419	///1420	/// * `collection` - Collection handler.1421	/// * `sender` - The owner or administrator of the collection.1422	/// * `properties` - The properties to delete.1423	pub fn delete_collection_properties(1424		collection: &CollectionHandle<T>,1425		sender: &T::CrossAccountId,1426		property_keys: impl Iterator<Item = PropertyKey>,1427	) -> DispatchResult {1428		Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1429	}14301431	/// Set collection propetry permission without any checks.1432	///1433	/// Used for migrations.1434	///1435	/// * `collection` - Collection handler.1436	/// * `property_permissions` - Property permissions.1437	pub fn set_property_permission_unchecked(1438		collection: CollectionId,1439		property_permission: PropertyKeyPermission,1440	) -> DispatchResult {1441		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1442			permissions.try_set(property_permission.key, property_permission.permission)1443		})1444		.map_err(<Error<T>>::from)?;1445		Ok(())1446	}14471448	/// Set collection property permission.1449	///1450	/// * `collection` - Collection handler.1451	/// * `sender` - The owner or administrator of the collection.1452	/// * `property_permission` - Property permission.1453	pub fn set_property_permission(1454		collection: &CollectionHandle<T>,1455		sender: &T::CrossAccountId,1456		property_permission: PropertyKeyPermission,1457	) -> DispatchResult {1458		Self::set_scoped_property_permission(1459			collection,1460			sender,1461			PropertyScope::None,1462			property_permission,1463		)1464	}14651466	/// Set collection property permission with scope.1467	///1468	/// * `collection` - Collection handler.1469	/// * `sender` - The owner or administrator of the collection.1470	/// * `scope` - Property scope.1471	/// * `property_permission` - Property permission.1472	pub fn set_scoped_property_permission(1473		collection: &CollectionHandle<T>,1474		sender: &T::CrossAccountId,1475		scope: PropertyScope,1476		property_permission: PropertyKeyPermission,1477	) -> DispatchResult {1478		collection.check_is_owner_or_admin(sender)?;14791480		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1481		let current_permission = all_permissions.get(&property_permission.key);1482		if matches![1483			current_permission,1484			Some(PropertyPermission { mutable: false, .. })1485		] {1486			return Err(<Error<T>>::NoPermission.into());1487		}14881489		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1490			let property_permission = property_permission.clone();1491			permissions.try_scoped_set(1492				scope,1493				property_permission.key,1494				property_permission.permission,1495			)1496		})1497		.map_err(<Error<T>>::from)?;14981499		Self::deposit_event(Event::PropertyPermissionSet(1500			collection.id,1501			property_permission.key,1502		));1503		<PalletEvm<T>>::deposit_log(1504			erc::CollectionHelpersEvents::CollectionChanged {1505				collection_id: eth::collection_id_to_address(collection.id),1506			}1507			.to_log(T::ContractAddress::get()),1508		);15091510		Ok(())1511	}15121513	/// Set token property permission.1514	///1515	/// * `collection` - Collection handler.1516	/// * `sender` - The owner or administrator of the collection.1517	/// * `property_permissions` - Property permissions.1518	#[transactional]1519	pub fn set_token_property_permissions(1520		collection: &CollectionHandle<T>,1521		sender: &T::CrossAccountId,1522		property_permissions: Vec<PropertyKeyPermission>,1523	) -> DispatchResult {1524		Self::set_scoped_token_property_permissions(1525			collection,1526			sender,1527			PropertyScope::None,1528			property_permissions,1529		)1530	}15311532	/// Set token property permission with scope.1533	///1534	/// * `collection` - Collection handler.1535	/// * `sender` - The owner or administrator of the collection.1536	/// * `scope` - Property scope.1537	/// * `property_permissions` - Property permissions.1538	#[transactional]1539	pub fn set_scoped_token_property_permissions(1540		collection: &CollectionHandle<T>,1541		sender: &T::CrossAccountId,1542		scope: PropertyScope,1543		property_permissions: Vec<PropertyKeyPermission>,1544	) -> DispatchResult {1545		for prop_pemission in property_permissions {1546			Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1547		}15481549		Ok(())1550	}15511552	/// Get collection property.1553	pub fn get_collection_property(1554		collection_id: CollectionId,1555		key: &PropertyKey,1556	) -> Option<PropertyValue> {1557		Self::collection_properties(collection_id).get(key).cloned()1558	}15591560	/// Convert byte vector to property key vector.1561	pub fn bytes_keys_to_property_keys(1562		keys: Vec<Vec<u8>>,1563	) -> Result<Vec<PropertyKey>, DispatchError> {1564		keys.into_iter()1565			.map(|key| -> Result<PropertyKey, DispatchError> {1566				key.try_into()1567					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1568			})1569			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1570	}15711572	/// Get properties according to given keys.1573	pub fn filter_collection_properties(1574		collection_id: CollectionId,1575		keys: Option<Vec<PropertyKey>>,1576	) -> Result<Vec<Property>, DispatchError> {1577		let properties = Self::collection_properties(collection_id);15781579		let properties = keys1580			.map(|keys| {1581				keys.into_iter()1582					.filter_map(|key| {1583						properties.get(&key).map(|value| Property {1584							key,1585							value: value.clone(),1586						})1587					})1588					.collect()1589			})1590			.unwrap_or_else(|| {1591				properties1592					.into_iter()1593					.map(|(key, value)| Property { key, value })1594					.collect()1595			});15961597		Ok(properties)1598	}15991600	/// Get property permissions according to given keys.1601	pub fn filter_property_permissions(1602		collection_id: CollectionId,1603		keys: Option<Vec<PropertyKey>>,1604	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1605		let permissions = Self::property_permissions(collection_id);16061607		let key_permissions = keys1608			.map(|keys| {1609				keys.into_iter()1610					.filter_map(|key| {1611						permissions1612							.get(&key)1613							.map(|permission| PropertyKeyPermission {1614								key,1615								permission: permission.clone(),1616							})1617					})1618					.collect()1619			})1620			.unwrap_or_else(|| {1621				permissions1622					.into_iter()1623					.map(|(key, permission)| PropertyKeyPermission { key, permission })1624					.collect()1625			});16261627		Ok(key_permissions)1628	}16291630	/// Toggle `user` participation in the `collection`'s allow list.1631	/// #### Store read/writes1632	/// 1 writes1633	pub fn toggle_allowlist(1634		collection: &CollectionHandle<T>,1635		sender: &T::CrossAccountId,1636		user: &T::CrossAccountId,1637		allowed: bool,1638	) -> DispatchResult {1639		collection.check_is_owner_or_admin(sender)?;16401641		// =========16421643		if allowed {1644			<Allowlist<T>>::insert((collection.id, user), true);1645			Self::deposit_event(Event::<T>::AllowListAddressAdded(1646				collection.id,1647				user.clone(),1648			));1649		} else {1650			<Allowlist<T>>::remove((collection.id, user));1651			Self::deposit_event(Event::<T>::AllowListAddressRemoved(1652				collection.id,1653				user.clone(),1654			));1655		}16561657		<PalletEvm<T>>::deposit_log(1658			erc::CollectionHelpersEvents::CollectionChanged {1659				collection_id: eth::collection_id_to_address(collection.id),1660			}1661			.to_log(T::ContractAddress::get()),1662		);16631664		Ok(())1665	}16661667	/// Toggle `user` participation in the `collection`'s admin list.1668	/// #### Store read/writes1669	/// 2 reads, 2 writes1670	pub fn toggle_admin(1671		collection: &CollectionHandle<T>,1672		sender: &T::CrossAccountId,1673		user: &T::CrossAccountId,1674		admin: bool,1675	) -> DispatchResult {1676		collection.check_is_internal()?;1677		collection.check_is_owner(sender)?;16781679		let is_admin = <IsAdmin<T>>::get((collection.id, user));1680		if is_admin == admin {1681			if admin {1682				return Ok(());1683			} else {1684				return Err(Error::<T>::UserIsNotCollectionAdmin.into());1685			}1686		}1687		let amount = <AdminAmount<T>>::get(collection.id);16881689		// =========16901691		if admin {1692			let amount = amount1693				.checked_add(1)1694				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1695			ensure!(1696				amount <= Self::collection_admins_limit(),1697				<Error<T>>::CollectionAdminCountExceeded,1698			);16991700			<AdminAmount<T>>::insert(collection.id, amount);1701			<IsAdmin<T>>::insert((collection.id, user), true);17021703			Self::deposit_event(Event::<T>::CollectionAdminAdded(1704				collection.id,1705				user.clone(),1706			));1707		} else {1708			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1709			<IsAdmin<T>>::remove((collection.id, user));17101711			Self::deposit_event(Event::<T>::CollectionAdminRemoved(1712				collection.id,1713				user.clone(),1714			));1715		}17161717		<PalletEvm<T>>::deposit_log(1718			erc::CollectionHelpersEvents::CollectionChanged {1719				collection_id: eth::collection_id_to_address(collection.id),1720			}1721			.to_log(T::ContractAddress::get()),1722		);17231724		Ok(())1725	}17261727	/// Update collection limits.1728	pub fn update_limits(1729		user: &T::CrossAccountId,1730		collection: &mut CollectionHandle<T>,1731		new_limit: CollectionLimits,1732	) -> DispatchResult {1733		collection.check_is_internal()?;1734		collection.check_is_owner_or_admin(user)?;17351736		collection.limits =1737			Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17381739		Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1740		<PalletEvm<T>>::deposit_log(1741			erc::CollectionHelpersEvents::CollectionChanged {1742				collection_id: eth::collection_id_to_address(collection.id),1743			}1744			.to_log(T::ContractAddress::get()),1745		);17461747		collection.save()1748	}17491750	/// Merge set fields from `new_limit` to `old_limit`.1751	fn clamp_limits(1752		mode: CollectionMode,1753		old_limit: &CollectionLimits,1754		mut new_limit: CollectionLimits,1755	) -> Result<CollectionLimits, DispatchError> {1756		let limits = old_limit;1757		limit_default!(old_limit, new_limit,1758			account_token_ownership_limit => ensure!(1759				new_limit <= MAX_TOKEN_OWNERSHIP,1760				<Error<T>>::CollectionLimitBoundsExceeded,1761			),1762			sponsored_data_size => ensure!(1763				new_limit <= CUSTOM_DATA_LIMIT,1764				<Error<T>>::CollectionLimitBoundsExceeded,1765			),17661767			sponsored_data_rate_limit => {},1768			token_limit => ensure!(1769				old_limit >= new_limit && new_limit > 0,1770				<Error<T>>::CollectionTokenLimitExceeded1771			),17721773			sponsor_transfer_timeout(match mode {1774				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1775				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1776				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1777			}) => ensure!(1778				new_limit <= MAX_SPONSOR_TIMEOUT,1779				<Error<T>>::CollectionLimitBoundsExceeded,1780			),1781			sponsor_approve_timeout => {},1782			owner_can_transfer => ensure!(1783				!limits.owner_can_transfer_instaled() ||1784				old_limit || !new_limit,1785				<Error<T>>::OwnerPermissionsCantBeReverted,1786			),1787			owner_can_destroy => ensure!(1788				old_limit || !new_limit,1789				<Error<T>>::OwnerPermissionsCantBeReverted,1790			),1791			transfers_enabled => {},1792		);1793		Ok(new_limit)1794	}17951796	/// Update collection permissions.1797	pub fn update_permissions(1798		user: &T::CrossAccountId,1799		collection: &mut CollectionHandle<T>,1800		new_permission: CollectionPermissions,1801	) -> DispatchResult {1802		collection.check_is_internal()?;1803		collection.check_is_owner_or_admin(user)?;1804		collection.permissions = Self::clamp_permissions(1805			collection.mode.clone(),1806			&collection.permissions,1807			new_permission,1808		)?;18091810		Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1811		<PalletEvm<T>>::deposit_log(1812			erc::CollectionHelpersEvents::CollectionChanged {1813				collection_id: eth::collection_id_to_address(collection.id),1814			}1815			.to_log(T::ContractAddress::get()),1816		);18171818		collection.save()1819	}18201821	/// Merge set fields from `new_permission` to `old_permission`.1822	fn clamp_permissions(1823		_mode: CollectionMode,1824		old_permission: &CollectionPermissions,1825		mut new_permission: CollectionPermissions,1826	) -> Result<CollectionPermissions, DispatchError> {1827		limit_default_clone!(old_permission, new_permission,1828			access => {},1829			mint_mode => {},1830			nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1831		);1832		Ok(new_permission)1833	}18341835	/// Repair possibly broken properties of a collection.1836	pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1837		CollectionProperties::<T>::mutate(collection_id, |properties| {1838			properties.recompute_consumed_space();1839		});18401841		Ok(())1842	}1843}18441845/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1846#[macro_export]1847macro_rules! unsupported {1848	($runtime:path) => {1849		Err($crate::Error::<$runtime>::UnsupportedOperation.into())1850	};1851}18521853/// Return weights for various worst-case operations.1854pub trait CommonWeightInfo<CrossAccountId> {1855	/// Weight of item creation.1856	fn create_item(data: &CreateItemData) -> Weight {1857		Self::create_multiple_items(from_ref(data))1858	}18591860	/// Weight of items creation.1861	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18621863	/// Weight of items creation.1864	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18651866	/// The weight of the burning item.1867	fn burn_item() -> Weight;18681869	/// Property setting weight.1870	///1871	/// * `amount`- The number of properties to set.1872	fn set_collection_properties(amount: u32) -> Weight;18731874	/// Collection property deletion weight.1875	///1876	/// * `amount`- The number of properties to set.1877	fn delete_collection_properties(amount: u32) -> Weight;18781879	/// Token property setting weight.1880	///1881	/// * `amount`- The number of properties to set.1882	fn set_token_properties(amount: u32) -> Weight;18831884	/// Token property deletion weight.1885	///1886	/// * `amount`- The number of properties to delete.1887	fn delete_token_properties(amount: u32) -> Weight;18881889	/// Token property permissions set weight.1890	///1891	/// * `amount`- The number of property permissions to set.1892	fn set_token_property_permissions(amount: u32) -> Weight;18931894	/// Transfer price of the token or its parts.1895	fn transfer() -> Weight;18961897	/// The price of setting the permission of the operation from another user.1898	fn approve() -> Weight;18991900	/// The price of setting the permission of the operation from another user for eth mirror.1901	fn approve_from() -> Weight;19021903	/// Transfer price from another user.1904	fn transfer_from() -> Weight;19051906	/// The price of burning a token from another user.1907	fn burn_from() -> Weight;19081909	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1910	/// whole users's balance.1911	///1912	/// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1913	fn burn_recursively_self_raw() -> Weight;19141915	/// Cost of iterating over `amount` children while burning, without counting child burning itself.1916	///1917	/// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1918	fn burn_recursively_breadth_raw(amount: u32) -> Weight;19191920	/// The price of recursive burning a token.1921	///1922	/// `max_selfs` - The maximum burning weight of the token itself.1923	/// `max_breadth` - The maximum number of nested tokens to burn.1924	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1925		Self::burn_recursively_self_raw()1926			.saturating_mul(max_selfs.max(1) as u64)1927			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1928	}19291930	/// The price of retrieving token owner1931	fn token_owner() -> Weight;19321933	/// The price of setting approval for all1934	fn set_allowance_for_all() -> Weight;19351936	/// The price of repairing an item.1937	fn force_repair_item() -> Weight;1938}19391940/// Weight info extension trait for refungible pallet.1941pub trait RefungibleExtensionsWeightInfo {1942	/// Weight of token repartition.1943	fn repartition() -> Weight;1944}19451946/// Common collection operations.1947///1948/// It wraps methods in Fungible, Nonfungible and Refungible pallets1949/// and adds weight info.1950pub trait CommonCollectionOperations<T: Config> {1951	/// Create token.1952	///1953	/// * `sender` - The user who mint the token and pays for the transaction.1954	/// * `to` - The user who will own the token.1955	/// * `data` - Token data.1956	/// * `nesting_budget` - A budget that can be spent on nesting tokens.1957	fn create_item(1958		&self,1959		sender: T::CrossAccountId,1960		to: T::CrossAccountId,1961		data: CreateItemData,1962		nesting_budget: &dyn Budget,1963	) -> DispatchResultWithPostInfo;19641965	/// Create multiple tokens.1966	///1967	/// * `sender` - The user who mint the token and pays for the transaction.1968	/// * `to` - The user who will own the token.1969	/// * `data` - Token data.1970	/// * `nesting_budget` - A budget that can be spent on nesting tokens.1971	fn create_multiple_items(1972		&self,1973		sender: T::CrossAccountId,1974		to: T::CrossAccountId,1975		data: Vec<CreateItemData>,1976		nesting_budget: &dyn Budget,1977	) -> DispatchResultWithPostInfo;19781979	/// Create multiple tokens.1980	///1981	/// * `sender` - The user who mint the token and pays for the transaction.1982	/// * `to` - The user who will own the token.1983	/// * `data` - Token data.1984	/// * `nesting_budget` - A budget that can be spent on nesting tokens.1985	fn create_multiple_items_ex(1986		&self,1987		sender: T::CrossAccountId,1988		data: CreateItemExData<T::CrossAccountId>,1989		nesting_budget: &dyn Budget,1990	) -> DispatchResultWithPostInfo;19911992	/// Burn token.1993	///1994	/// * `sender` - The user who owns the token.1995	/// * `token` - Token id that will burned.1996	/// * `amount` - The number of parts of the token that will be burned.1997	fn burn_item(1998		&self,1999		sender: T::CrossAccountId,2000		token: TokenId,2001		amount: u128,2002	) -> DispatchResultWithPostInfo;20032004	/// Burn token and all nested tokens recursievly.2005	///2006	/// * `sender` - The user who owns the token.2007	/// * `token` - Token id that will burned.2008	/// * `self_budget` - The budget that can be spent on burning tokens.2009	/// * `breadth_budget` - The budget that can be spent on burning nested tokens.2010	fn burn_item_recursively(2011		&self,2012		sender: T::CrossAccountId,2013		token: TokenId,2014		self_budget: &dyn Budget,2015		breadth_budget: &dyn Budget,2016	) -> DispatchResultWithPostInfo;20172018	/// Set collection properties.2019	///2020	/// * `sender` - Must be either the owner of the collection or its admin.2021	/// * `properties` - Properties to be set.2022	fn set_collection_properties(2023		&self,2024		sender: T::CrossAccountId,2025		properties: Vec<Property>,2026	) -> DispatchResultWithPostInfo;20272028	/// Delete collection properties.2029	///2030	/// * `sender` - Must be either the owner of the collection or its admin.2031	/// * `properties` - The properties to be removed.2032	fn delete_collection_properties(2033		&self,2034		sender: &T::CrossAccountId,2035		property_keys: Vec<PropertyKey>,2036	) -> DispatchResultWithPostInfo;20372038	/// Set token properties.2039	///2040	/// The appropriate [`PropertyPermission`] for the token property2041	/// must be set with [`Self::set_token_property_permissions`].2042	///2043	/// * `sender` - Must be either the owner of the token or its admin.2044	/// * `token_id` - The token for which the properties are being set.2045	/// * `properties` - Properties to be set.2046	/// * `budget` - Budget for setting properties.2047	fn set_token_properties(2048		&self,2049		sender: T::CrossAccountId,2050		token_id: TokenId,2051		properties: Vec<Property>,2052		budget: &dyn Budget,2053	) -> DispatchResultWithPostInfo;20542055	/// Remove token properties.2056	///2057	/// The appropriate [`PropertyPermission`] for the token property2058	/// must be set with [`Self::set_token_property_permissions`].2059	///2060	/// * `sender` - Must be either the owner of the token or its admin.2061	/// * `token_id` - The token for which the properties are being remove.2062	/// * `property_keys` - Keys to remove corresponding properties.2063	/// * `budget` - Budget for removing properties.2064	fn delete_token_properties(2065		&self,2066		sender: T::CrossAccountId,2067		token_id: TokenId,2068		property_keys: Vec<PropertyKey>,2069		budget: &dyn Budget,2070	) -> DispatchResultWithPostInfo;20712072	/// Set token property permissions.2073	///2074	/// * `sender` - Must be either the owner of the token or its admin.2075	/// * `token_id` - The token for which the properties are being set.2076	/// * `property_permissions` - Property permissions to be set.2077	/// * `budget` - Budget for setting properties.2078	fn set_token_property_permissions(2079		&self,2080		sender: &T::CrossAccountId,2081		property_permissions: Vec<PropertyKeyPermission>,2082	) -> DispatchResultWithPostInfo;20832084	/// Transfer amount of token pieces.2085	///2086	/// * `sender` - Donor user.2087	/// * `to` - Recepient user.2088	/// * `token` - The token of which parts are being sent.2089	/// * `amount` - The number of parts of the token that will be transferred.2090	/// * `budget` - The maximum budget that can be spent on the transfer.2091	fn transfer(2092		&self,2093		sender: T::CrossAccountId,2094		to: T::CrossAccountId,2095		token: TokenId,2096		amount: u128,2097		budget: &dyn Budget,2098	) -> DispatchResultWithPostInfo;20992100	/// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].2101	///2102	/// * `sender` - The user who grants access to the token.2103	/// * `spender` - The user to whom the rights are granted.2104	/// * `token` - The token to which access is granted.2105	/// * `amount` - The amount of pieces that another user can dispose of.2106	fn approve(2107		&self,2108		sender: T::CrossAccountId,2109		spender: T::CrossAccountId,2110		token: TokenId,2111		amount: u128,2112	) -> DispatchResultWithPostInfo;21132114	/// Grant access to another account to transfer parts of the token owned by the calling user's eth mirror via [Self::transfer_from].2115	///2116	/// * `sender` - The user who grants access to the token.2117	/// * `from` - Spender's eth mirror.2118	/// * `to` - The user to whom the rights are granted.2119	/// * `token` - The token to which access is granted.2120	/// * `amount` - The amount of pieces that another user can dispose of.2121	fn approve_from(2122		&self,2123		sender: T::CrossAccountId,2124		from: T::CrossAccountId,2125		to: T::CrossAccountId,2126		token: TokenId,2127		amount: u128,2128	) -> DispatchResultWithPostInfo;21292130	/// Send parts of a token owned by another user.2131	///2132	/// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2133	///2134	/// * `sender` - The user who must have access to the token (see [`Self::approve`]).2135	/// * `from` - The user who owns the token.2136	/// * `to` - Recepient user.2137	/// * `token` - The token of which parts are being sent.2138	/// * `amount` - The number of parts of the token that will be transferred.2139	/// * `budget` - The maximum budget that can be spent on the transfer.2140	fn transfer_from(2141		&self,2142		sender: T::CrossAccountId,2143		from: T::CrossAccountId,2144		to: T::CrossAccountId,2145		token: TokenId,2146		amount: u128,2147		budget: &dyn Budget,2148	) -> DispatchResultWithPostInfo;21492150	/// Burn parts of a token owned by another user.2151	///2152	/// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2153	///2154	/// * `sender` - The user who must have access to the token (see [`Self::approve`]).2155	/// * `from` - The user who owns the token.2156	/// * `token` - The token of which parts are being sent.2157	/// * `amount` - The number of parts of the token that will be transferred.2158	/// * `budget` - The maximum budget that can be spent on the burn.2159	fn burn_from(2160		&self,2161		sender: T::CrossAccountId,2162		from: T::CrossAccountId,2163		token: TokenId,2164		amount: u128,2165		budget: &dyn Budget,2166	) -> DispatchResultWithPostInfo;21672168	/// Check permission to nest token.2169	///2170	/// * `sender` - The user who initiated the check.2171	/// * `from` - The token that is checked for embedding.2172	/// * `under` - Token under which to check.2173	/// * `budget` - The maximum budget that can be spent on the check.2174	fn check_nesting(2175		&self,2176		sender: T::CrossAccountId,2177		from: (CollectionId, TokenId),2178		under: TokenId,2179		budget: &dyn Budget,2180	) -> DispatchResult;21812182	/// Nest one token into another.2183	///2184	/// * `under` - Token holder.2185	/// * `to_nest` - Nested token.2186	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));21872188	/// Unnest token.2189	///2190	/// * `under` - Token holder.2191	/// * `to_nest` - Token to unnest.2192	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));21932194	/// Get all user tokens.2195	///2196	/// * `account` - Account for which you need to get tokens.2197	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;21982199	/// Get all the tokens in the collection.2200	fn collection_tokens(&self) -> Vec<TokenId>;22012202	/// Check if the token exists.2203	///2204	/// * `token` - Id token to check.2205	fn token_exists(&self, token: TokenId) -> bool;22062207	/// Get the id of the last minted token.2208	fn last_token_id(&self) -> TokenId;22092210	/// Get the owner of the token.2211	///2212	/// * `token` - The token for which you need to find out the owner.2213	fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22142215	/// Returns 10 tokens owners in no particular order.2216	///2217	/// * `token` - The token for which you need to find out the owners.2218	fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22192220	/// Get the value of the token property by key.2221	///2222	/// * `token` - Token with the property to get.2223	/// * `key` - Property name.2224	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22252226	/// Get a set of token properties by key vector.2227	///2228	/// * `token` - Token with the property to get.2229	/// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),2230	/// then all properties are returned.2231	fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22322233	/// Amount of unique collection tokens2234	fn total_supply(&self) -> u32;22352236	/// Amount of different tokens account has.2237	///2238	/// * `account` - The account for which need to get the balance.2239	fn account_balance(&self, account: T::CrossAccountId) -> u32;22402241	/// Amount of specific token account have.2242	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22432244	/// Amount of token pieces2245	fn total_pieces(&self, token: TokenId) -> Option<u128>;22462247	/// Get the number of parts of the token that a trusted user can manage.2248	///2249	/// * `sender` - Trusted user.2250	/// * `spender` - Owner of the token.2251	/// * `token` - The token for which to get the value.2252	fn allowance(2253		&self,2254		sender: T::CrossAccountId,2255		spender: T::CrossAccountId,2256		token: TokenId,2257	) -> u128;22582259	/// Get extension for RFT collection.2260	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22612262	/// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.2263	/// * `owner` - Token owner2264	/// * `operator` - Operator2265	/// * `approve` - Should operator status be granted or revoked?2266	fn set_allowance_for_all(2267		&self,2268		owner: T::CrossAccountId,2269		operator: T::CrossAccountId,2270		approve: bool,2271	) -> DispatchResultWithPostInfo;22722273	/// Tells whether the given `owner` approves the `operator`.2274	fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;22752276	/// Repairs a possibly broken item.2277	fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2278}22792280/// Extension for RFT collection.2281pub trait RefungibleExtensions<T>2282where2283	T: Config,2284{2285	/// Change the number of parts of the token.2286	///2287	/// When the value changes down, this function is equivalent to burning parts of the token.2288	///2289	/// * `sender` - The user calling the repartition operation. Must be the owner of the token.2290	/// * `token` - The token for which you want to change the number of parts.2291	/// * `amount` - The new value of the parts of the token.2292	fn repartition(2293		&self,2294		sender: &T::CrossAccountId,2295		token: TokenId,2296		amount: u128,2297	) -> DispatchResultWithPostInfo;2298}22992300/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].2301///2302/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.2303pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2304	let post_info = PostDispatchInfo {2305		actual_weight: Some(weight),2306		pays_fee: Pays::Yes,2307	};2308	match res {2309		Ok(()) => Ok(post_info),2310		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2311	}2312}23132314impl<T: Config> From<PropertiesError> for Error<T> {2315	fn from(error: PropertiesError) -> Self {2316		match error {2317			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2318			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2319			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2320			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2321			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2322		}2323	}2324}
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 up_data_structs::{76	AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionFlags,77	RpcCollectionFlags, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,78	COLLECTION_ADMINS_LIMIT, TokenId, TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP,79	CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,80	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,81	CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit, budget::Budget,82	PhantomType, Property, CollectionProperties as CollectionPropertiesT, TokenProperties,83	PropertiesPermissionMap, PropertyKey, PropertyValue, PropertyPermission, PropertiesError,84	TokenOwnerError, PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope,85	CollectionPermissions,86};87use up_pov_estimate_rpc::PovInfo;8889pub use pallet::*;90use sp_core::H160;91use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, traits::Zero};9293#[cfg(feature = "runtime-benchmarks")]94pub mod benchmarking;95pub mod dispatch;96pub mod erc;97pub mod eth;98pub mod helpers;99#[allow(missing_docs)]100pub mod weights;101/// Weight info.102pub type SelfWeightOf<T> = <T as Config>::WeightInfo;103104/// Collection handle contains information about collection data and id.105/// Also provides functionality to count consumed gas.106///107/// CollectionHandle is used as a generic wrapper for collections of all types (except native fungible).108/// It allows to perform common operations and queries on any collection type,109/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].110#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]111pub struct CollectionHandle<T: Config> {112	/// Collection id113	pub id: CollectionId,114	collection: Collection<T::AccountId>,115	/// Substrate recorder for counting consumed gas116	pub recorder: SubstrateRecorder<T>,117}118119impl<T: Config> WithRecorder<T> for CollectionHandle<T> {120	fn recorder(&self) -> &SubstrateRecorder<T> {121		&self.recorder122	}123	fn into_recorder(self) -> SubstrateRecorder<T> {124		self.recorder125	}126}127128impl<T: Config> CollectionHandle<T> {129	/// Same as [CollectionHandle::new] but with an explicit gas limit.130	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {131		Self::new_with_recorder(id, SubstrateRecorder::new(gas_limit))132	}133134	/// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].135	pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {136		<CollectionById<T>>::get(id).map(|collection| Self {137			id,138			collection,139			recorder,140		})141	}142143	/// Retrives collection data from storage and creates collection handle with default parameters.144	/// If collection not found return `None`145	pub fn new(id: CollectionId) -> Option<Self> {146		Self::new_with_gas_limit(id, u64::MAX)147	}148149	/// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.150	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {151		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)152	}153154	/// Consume gas for reading.155	pub fn consume_store_reads(156		&self,157		reads: u64,158	) -> pallet_evm_coder_substrate::execution::Result<()> {159		self.recorder().consume_store_reads(reads)160	}161162	/// Consume gas for writing.163	pub fn consume_store_writes(164		&self,165		writes: u64,166	) -> pallet_evm_coder_substrate::execution::Result<()> {167		self.recorder().consume_store_writes(writes)168	}169170	/// Consume gas for reading and writing.171	pub fn consume_store_reads_and_writes(172		&self,173		reads: u64,174		writes: u64,175	) -> pallet_evm_coder_substrate::execution::Result<()> {176		self.recorder()177			.consume_store_reads_and_writes(reads, writes)178	}179180	/// Save collection to storage.181	pub fn save(&self) -> DispatchResult {182		<CollectionById<T>>::insert(self.id, &self.collection);183		Ok(())184	}185186	/// Set collection sponsor.187	///188	/// Unique collections allows sponsoring for certain actions.189	/// This method allows you to set the sponsor of the collection.190	/// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].191	pub fn set_sponsor(192		&mut self,193		sender: &T::CrossAccountId,194		sponsor: T::AccountId,195	) -> DispatchResult {196		self.check_is_internal()?;197		self.check_is_owner_or_admin(sender)?;198199		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());200201		<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));202		<PalletEvm<T>>::deposit_log(203			erc::CollectionHelpersEvents::CollectionChanged {204				collection_id: eth::collection_id_to_address(self.id),205			}206			.to_log(T::ContractAddress::get()),207		);208209		self.save()210	}211212	/// Force set `sponsor`.213	///214	/// Differs from [`set_sponsor`][`Self::set_sponsor`] in that confirmation215	/// from the `sponsor` is not required.216	///217	/// # Arguments218	///219	/// * `sponsor`: ID of the account of the sponsor-to-be.220	pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {221		self.check_is_internal()?;222223		self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());224225		<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));226		<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));227		<PalletEvm<T>>::deposit_log(228			erc::CollectionHelpersEvents::CollectionChanged {229				collection_id: eth::collection_id_to_address(self.id),230			}231			.to_log(T::ContractAddress::get()),232		);233234		self.save()235	}236237	/// Confirm sponsorship238	///239	/// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.240	/// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].241	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {242		self.check_is_internal()?;243		ensure!(244			self.collection.sponsorship.pending_sponsor() == Some(sender),245			Error::<T>::ConfirmSponsorshipFail246		);247248		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());249250		<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));251		<PalletEvm<T>>::deposit_log(252			erc::CollectionHelpersEvents::CollectionChanged {253				collection_id: eth::collection_id_to_address(self.id),254			}255			.to_log(T::ContractAddress::get()),256		);257258		self.save()259	}260261	/// Remove collection sponsor.262	pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {263		self.check_is_internal()?;264		self.check_is_owner_or_admin(sender)?;265266		self.collection.sponsorship = SponsorshipState::Disabled;267268		<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));269		<PalletEvm<T>>::deposit_log(270			erc::CollectionHelpersEvents::CollectionChanged {271				collection_id: eth::collection_id_to_address(self.id),272			}273			.to_log(T::ContractAddress::get()),274		);275		self.save()276	}277278	/// Force remove `sponsor`.279	///280	/// Differs from `remove_sponsor` in that281	/// it doesn't require consent from the `owner` of the collection.282	pub fn force_remove_sponsor(&mut self) -> DispatchResult {283		self.check_is_internal()?;284285		self.collection.sponsorship = SponsorshipState::Disabled;286287		<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));288		<PalletEvm<T>>::deposit_log(289			erc::CollectionHelpersEvents::CollectionChanged {290				collection_id: eth::collection_id_to_address(self.id),291			}292			.to_log(T::ContractAddress::get()),293		);294		self.save()295	}296297	/// Checks that the collection was created with, and must be operated upon through **Unique API**.298	/// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.299	pub fn check_is_internal(&self) -> DispatchResult {300		if self.flags.external {301			return Err(<Error<T>>::CollectionIsExternal)?;302		}303304		Ok(())305	}306307	/// Checks that the collection was created with, and must be operated upon through an **assimilated API**.308	/// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.309	pub fn check_is_external(&self) -> DispatchResult {310		if !self.flags.external {311			return Err(<Error<T>>::CollectionIsInternal)?;312		}313314		Ok(())315	}316}317318impl<T: Config> Deref for CollectionHandle<T> {319	type Target = Collection<T::AccountId>;320321	fn deref(&self) -> &Self::Target {322		&self.collection323	}324}325326impl<T: Config> DerefMut for CollectionHandle<T> {327	fn deref_mut(&mut self) -> &mut Self::Target {328		&mut self.collection329	}330}331332impl<T: Config> CollectionHandle<T> {333	/// Checks if the `user` is the owner of the collection.334	pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {335		ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);336		Ok(())337	}338339	/// Returns **true** if the `user` is the owner or administrator of the collection.340	pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {341		*user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))342	}343344	/// Checks if the `user` is the owner or administrator of the collection.345	pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {346		ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);347		Ok(())348	}349350	/// Returns **true** if351	/// * the `user`is a collection owner or admin352	/// * the collection limits allow the owner/admins to transfer/burn any collection token353	pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {354		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)355	}356357	/// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.358	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {359		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)360	}361362	/// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.363	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {364		ensure!(365			<Allowlist<T>>::get((self.id, user)),366			<Error<T>>::AddressNotInAllowlist367		);368		Ok(())369	}370371	/// Changes collection owner to another account372	/// #### Store read/writes373	/// 1 writes374	pub fn change_owner(375		&mut self,376		caller: T::CrossAccountId,377		new_owner: T::CrossAccountId,378	) -> DispatchResult {379		self.check_is_internal()?;380		self.check_is_owner(&caller)?;381		self.collection.owner = new_owner.as_sub().clone();382383		<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(384			self.id,385			new_owner.as_sub().clone(),386		));387		<PalletEvm<T>>::deposit_log(388			erc::CollectionHelpersEvents::CollectionChanged {389				collection_id: eth::collection_id_to_address(self.id),390			}391			.to_log(T::ContractAddress::get()),392		);393394		self.save()395	}396}397398#[frame_support::pallet]399pub mod pallet {400401	use super::*;402	use dispatch::CollectionDispatch;403	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};404	use up_data_structs::{TokenId, mapping::TokenAddressMapping};405	use scale_info::TypeInfo;406	use weights::WeightInfo;407408	#[pallet::config]409	pub trait Config:410		frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo411	{412		/// Weight information for functions of this pallet.413		type WeightInfo: WeightInfo;414415		/// Events compatible with [`frame_system::Config::Event`].416		type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;417418		/// Handler of accounts and payment.419		type Currency: Balanced<Self::AccountId> + Inspect<Self::AccountId>;420421		/// Set price to create a collection.422		#[pallet::constant]423		type CollectionCreationPrice: Get<424			<<Self as Config>::Currency as Inspect<Self::AccountId>>::Balance,425		>;426427		/// Dispatcher of operations on collections.428		type CollectionDispatch: CollectionDispatch<Self>;429430		/// Account which holds the chain's treasury.431		type TreasuryAccountId: Get<Self::AccountId>;432433		/// Address under which the CollectionHelper contract would be available.434		#[pallet::constant]435		type ContractAddress: Get<H160>;436437		/// Mapper for token addresses to Ethereum addresses.438		type EvmTokenAddressMapping: TokenAddressMapping<H160>;439440		/// Mapper for token addresses to [`CrossAccountId`].441		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;442	}443444	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);445	/// Collection id for native fungible collction.446	pub const NATIVE_FUNGIBLE_COLLECTION_ID: CollectionId = CollectionId(0);447448	#[pallet::pallet]449	#[pallet::storage_version(STORAGE_VERSION)]450	pub struct Pallet<T>(_);451452	#[pallet::extra_constants]453	impl<T: Config> Pallet<T> {454		/// Maximum admins per collection.455		pub fn collection_admins_limit() -> u32 {456			COLLECTION_ADMINS_LIMIT457		}458	}459460	#[pallet::genesis_config]461	pub struct GenesisConfig<T>(PhantomData<T>);462463	#[cfg(feature = "std")]464	impl<T: Config> Default for GenesisConfig<T> {465		fn default() -> Self {466			Self(Default::default())467		}468	}469470	#[pallet::genesis_build]471	impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {472		fn build(&self) {473			StorageVersion::new(1).put::<Pallet<T>>();474		}475	}476477	impl<T: Config> Pallet<T> {478		/// Helper function that handles deposit events479		pub fn deposit_event(event: Event<T>) {480			let event = <T as Config>::RuntimeEvent::from(event);481			let event = event.into();482			<frame_system::Pallet<T>>::deposit_event(event)483		}484	}485486	#[pallet::event]487	pub enum Event<T: Config> {488		/// New collection was created489		CollectionCreated(490			/// Globally unique identifier of newly created collection.491			CollectionId,492			/// [`CollectionMode`] converted into _u8_.493			u8,494			/// Collection owner.495			T::AccountId,496		),497498		/// New collection was destroyed499		CollectionDestroyed(500			/// Globally unique identifier of collection.501			CollectionId,502		),503504		/// New item was created.505		ItemCreated(506			/// Id of the collection where item was created.507			CollectionId,508			/// Id of an item. Unique within the collection.509			TokenId,510			/// Owner of newly created item511			T::CrossAccountId,512			/// Always 1 for NFT513			u128,514		),515516		/// Collection item was burned.517		ItemDestroyed(518			/// Id of the collection where item was destroyed.519			CollectionId,520			/// Identifier of burned NFT.521			TokenId,522			/// Which user has destroyed its tokens.523			T::CrossAccountId,524			/// Amount of token pieces destroed. Always 1 for NFT.525			u128,526		),527528		/// Item was transferred529		Transfer(530			/// Id of collection to which item is belong.531			CollectionId,532			/// Id of an item.533			TokenId,534			/// Original owner of item.535			T::CrossAccountId,536			/// New owner of item.537			T::CrossAccountId,538			/// Amount of token pieces transfered. Always 1 for NFT.539			u128,540		),541542		/// Amount pieces of token owned by `sender` was approved for `spender`.543		Approved(544			/// Id of collection to which item is belong.545			CollectionId,546			/// Id of an item.547			TokenId,548			/// Original owner of item.549			T::CrossAccountId,550			/// Id for which the approval was granted.551			T::CrossAccountId,552			/// Amount of token pieces transfered. Always 1 for NFT.553			u128,554		),555556		/// A `sender` approves operations on all owned tokens for `spender`.557		ApprovedForAll(558			/// Id of collection to which item is belong.559			CollectionId,560			/// Owner of a wallet.561			T::CrossAccountId,562			/// Id for which operator status was granted or rewoked.563			T::CrossAccountId,564			/// Is operator status granted or revoked?565			bool,566		),567568		/// The colletion property has been added or edited.569		CollectionPropertySet(570			/// Id of collection to which property has been set.571			CollectionId,572			/// The property that was set.573			PropertyKey,574		),575576		/// The property has been deleted.577		CollectionPropertyDeleted(578			/// Id of collection to which property has been deleted.579			CollectionId,580			/// The property that was deleted.581			PropertyKey,582		),583584		/// The token property has been added or edited.585		TokenPropertySet(586			/// Identifier of the collection whose token has the property set.587			CollectionId,588			/// The token for which the property was set.589			TokenId,590			/// The property that was set.591			PropertyKey,592		),593594		/// The token property has been deleted.595		TokenPropertyDeleted(596			/// Identifier of the collection whose token has the property deleted.597			CollectionId,598			/// The token for which the property was deleted.599			TokenId,600			/// The property that was deleted.601			PropertyKey,602		),603604		/// The token property permission of a collection has been set.605		PropertyPermissionSet(606			/// ID of collection to which property permission has been set.607			CollectionId,608			/// The property permission that was set.609			PropertyKey,610		),611612		/// Address was added to the allow list.613		AllowListAddressAdded(614			/// ID of the affected collection.615			CollectionId,616			/// Address of the added account.617			T::CrossAccountId,618		),619620		/// Address was removed from the allow list.621		AllowListAddressRemoved(622			/// ID of the affected collection.623			CollectionId,624			/// Address of the removed account.625			T::CrossAccountId,626		),627628		/// Collection admin was added.629		CollectionAdminAdded(630			/// ID of the affected collection.631			CollectionId,632			/// Admin address.633			T::CrossAccountId,634		),635636		/// Collection admin was removed.637		CollectionAdminRemoved(638			/// ID of the affected collection.639			CollectionId,640			/// Removed admin address.641			T::CrossAccountId,642		),643644		/// Collection limits were set.645		CollectionLimitSet(646			/// ID of the affected collection.647			CollectionId,648		),649650		/// Collection owned was changed.651		CollectionOwnerChanged(652			/// ID of the affected collection.653			CollectionId,654			/// New owner address.655			T::AccountId,656		),657658		/// Collection permissions were set.659		CollectionPermissionSet(660			/// ID of the affected collection.661			CollectionId,662		),663664		/// Collection sponsor was set.665		CollectionSponsorSet(666			/// ID of the affected collection.667			CollectionId,668			/// New sponsor address.669			T::AccountId,670		),671672		/// New sponsor was confirm.673		SponsorshipConfirmed(674			/// ID of the affected collection.675			CollectionId,676			/// New sponsor address.677			T::AccountId,678		),679680		/// Collection sponsor was removed.681		CollectionSponsorRemoved(682			/// ID of the affected collection.683			CollectionId,684		),685	}686687	#[pallet::error]688	pub enum Error<T> {689		/// This collection does not exist.690		CollectionNotFound,691		/// Sender parameter and item owner must be equal.692		MustBeTokenOwner,693		/// No permission to perform action694		NoPermission,695		/// Destroying only empty collections is allowed696		CantDestroyNotEmptyCollection,697		/// Collection is not in mint mode.698		PublicMintingNotAllowed,699		/// Address is not in allow list.700		AddressNotInAllowlist,701702		/// Collection name can not be longer than 63 char.703		CollectionNameLimitExceeded,704		/// Collection description can not be longer than 255 char.705		CollectionDescriptionLimitExceeded,706		/// Token prefix can not be longer than 15 char.707		CollectionTokenPrefixLimitExceeded,708		/// Total collections bound exceeded.709		TotalCollectionsLimitExceeded,710		/// Exceeded max admin count711		CollectionAdminCountExceeded,712		/// Collection limit bounds per collection exceeded713		CollectionLimitBoundsExceeded,714		/// Tried to enable permissions which are only permitted to be disabled715		OwnerPermissionsCantBeReverted,716		/// Collection settings not allowing items transferring717		TransferNotAllowed,718		/// Account token limit exceeded per collection719		AccountTokenLimitExceeded,720		/// Collection token limit exceeded721		CollectionTokenLimitExceeded,722		/// Metadata flag frozen723		MetadataFlagFrozen,724725		/// Item does not exist726		TokenNotFound,727		/// Item is balance not enough728		TokenValueTooLow,729		/// Requested value is more than the approved730		ApprovedValueTooLow,731		/// Tried to approve more than owned732		CantApproveMoreThanOwned,733		/// Only spending from eth mirror could be approved734		AddressIsNotEthMirror,735736		/// Can't transfer tokens to ethereum zero address737		AddressIsZero,738739		/// The operation is not supported740		UnsupportedOperation,741742		/// Insufficient funds to perform an action743		NotSufficientFounds,744745		/// User does not satisfy the nesting rule746		UserIsNotAllowedToNest,747		/// Only tokens from specific collections may nest tokens under this one748		SourceCollectionIsNotAllowedToNest,749750		/// Tried to store more data than allowed in collection field751		CollectionFieldSizeExceeded,752753		/// Tried to store more property data than allowed754		NoSpaceForProperty,755756		/// Tried to store more property keys than allowed757		PropertyLimitReached,758759		/// Property key is too long760		PropertyKeyIsTooLong,761762		/// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed763		InvalidCharacterInPropertyKey,764765		/// Empty property keys are forbidden766		EmptyPropertyKey,767768		/// Tried to access an external collection with an internal API769		CollectionIsExternal,770771		/// Tried to access an internal collection with an external API772		CollectionIsInternal,773774		/// This address is not set as sponsor, use setCollectionSponsor first.775		ConfirmSponsorshipFail,776777		/// The user is not an administrator.778		UserIsNotCollectionAdmin,779	}780781	/// Storage of the count of created collections. Essentially contains the last collection ID.782	#[pallet::storage]783	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;784785	/// Storage of the count of deleted collections.786	#[pallet::storage]787	pub type DestroyedCollectionCount<T> =788		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;789790	/// Storage of collection info.791	#[pallet::storage]792	pub type CollectionById<T> = StorageMap<793		Hasher = Blake2_128Concat,794		Key = CollectionId,795		Value = Collection<<T as frame_system::Config>::AccountId>,796		QueryKind = OptionQuery,797	>;798799	/// Storage of collection properties.800	#[pallet::storage]801	#[pallet::getter(fn collection_properties)]802	pub type CollectionProperties<T> = StorageMap<803		Hasher = Blake2_128Concat,804		Key = CollectionId,805		Value = CollectionPropertiesT,806		QueryKind = ValueQuery,807	>;808809	/// Storage of token property permissions of a collection.810	#[pallet::storage]811	#[pallet::getter(fn property_permissions)]812	pub type CollectionPropertyPermissions<T> = StorageMap<813		Hasher = Blake2_128Concat,814		Key = CollectionId,815		Value = PropertiesPermissionMap,816		QueryKind = ValueQuery,817	>;818819	/// Storage of the amount of collection admins.820	#[pallet::storage]821	pub type AdminAmount<T> = StorageMap<822		Hasher = Blake2_128Concat,823		Key = CollectionId,824		Value = u32,825		QueryKind = ValueQuery,826	>;827828	/// List of collection admins.829	#[pallet::storage]830	pub type IsAdmin<T: Config> = StorageNMap<831		Key = (832			Key<Blake2_128Concat, CollectionId>,833			Key<Blake2_128Concat, T::CrossAccountId>,834		),835		Value = bool,836		QueryKind = ValueQuery,837	>;838839	/// Allowlisted collection users.840	#[pallet::storage]841	pub type Allowlist<T: Config> = StorageNMap<842		Key = (843			Key<Blake2_128Concat, CollectionId>,844			Key<Blake2_128Concat, T::CrossAccountId>,845		),846		Value = bool,847		QueryKind = ValueQuery,848	>;849850	/// Not used by code, exists only to provide some types to metadata.851	#[pallet::storage]852	pub type DummyStorageValue<T: Config> = StorageValue<853		Value = (854			CollectionStats,855			CollectionId,856			TokenId,857			TokenChild,858			PhantomType<(859				TokenData<T::CrossAccountId>,860				RpcCollection<T::AccountId>,861				// PoV Estimate Info862				PovInfo,863			)>,864		),865		QueryKind = OptionQuery,866	>;867}868869/// Represents the change mode for the token property.870pub enum SetPropertyMode {871	/// The token already exists.872	ExistingToken,873874	/// New token.875	NewToken {876		/// The creator of the token is the recipient.877		mint_target_is_sender: bool,878	},879}880881/// Value representation with delayed initialization time.882pub struct LazyValue<T, F: FnOnce() -> T> {883	value: Option<T>,884	f: Option<F>,885}886887impl<T, F: FnOnce() -> T> LazyValue<T, F> {888	/// Create a new LazyValue.889	pub fn new(f: F) -> Self {890		Self {891			value: None,892			f: Some(f),893		}894	}895896	/// Get the value. If it call furst time the value will be initialized.897	pub fn value(&mut self) -> &T {898		if self.value.is_none() {899			self.value = Some(self.f.take().unwrap()())900		}901902		self.value.as_ref().unwrap()903	}904905	/// Is value initialized.906	pub fn has_value(&self) -> bool {907		self.value.is_some()908	}909}910911fn check_token_permissions<T, FCA, FTO, FTE>(912	collection_admin_permitted: bool,913	token_owner_permitted: bool,914	is_collection_admin: &mut LazyValue<bool, FCA>,915	is_token_owner: &mut LazyValue<Result<bool, DispatchError>, FTO>,916	is_token_exist: &mut LazyValue<bool, FTE>,917) -> DispatchResult918where919	T: Config,920	FCA: FnOnce() -> bool,921	FTO: FnOnce() -> Result<bool, DispatchError>,922	FTE: FnOnce() -> bool,923{924	if !(collection_admin_permitted && *is_collection_admin.value()925		|| token_owner_permitted && (*is_token_owner.value())?)926	{927		fail!(<Error<T>>::NoPermission);928	}929930	let token_certainly_exist = is_token_owner.has_value() && (*is_token_owner.value())?;931	if !token_certainly_exist && !is_token_exist.value() {932		fail!(<Error<T>>::TokenNotFound);933	}934	Ok(())935}936937impl<T: Config> Pallet<T> {938	/// Enshure that receiver address is correct.939	///940	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.941	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {942		ensure!(943			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,944			<Error<T>>::AddressIsZero945		);946		Ok(())947	}948949	/// Get a vector of collection admins.950	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {951		<IsAdmin<T>>::iter_prefix((collection,))952			.map(|(a, _)| a)953			.collect()954	}955956	/// Get a vector of users allowed to mint tokens.957	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {958		<Allowlist<T>>::iter_prefix((collection,))959			.map(|(a, _)| a)960			.collect()961	}962963	/// Is `user` allowed to mint token in `collection`.964	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {965		<Allowlist<T>>::get((collection, user))966	}967968	/// Get statistics of collections.969	pub fn collection_stats() -> CollectionStats {970		let created = <CreatedCollectionCount<T>>::get();971		let destroyed = <DestroyedCollectionCount<T>>::get();972		CollectionStats {973			created: created.0,974			destroyed: destroyed.0,975			alive: created.0 - destroyed.0,976		}977	}978979	/// Get the effective limits for the collection.980	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {981		let collection = <CollectionById<T>>::get(collection)?;982		let limits = collection.limits;983		let effective_limits = CollectionLimits {984			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),985			sponsored_data_size: Some(limits.sponsored_data_size()),986			sponsored_data_rate_limit: Some(987				limits988					.sponsored_data_rate_limit989					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),990			),991			token_limit: Some(limits.token_limit()),992			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(993				match collection.mode {994					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,995					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,996					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,997				},998			)),999			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),1000			owner_can_transfer: Some(limits.owner_can_transfer()),1001			owner_can_destroy: Some(limits.owner_can_destroy()),1002			transfers_enabled: Some(limits.transfers_enabled()),1003		};10041005		Some(effective_limits)1006	}10071008	/// Returns information about the `collection` adapted for rpc.1009	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {1010		let Collection {1011			name,1012			description,1013			owner,1014			mode,1015			token_prefix,1016			sponsorship,1017			limits,1018			permissions,1019			flags,1020		} = <CollectionById<T>>::get(collection)?;10211022		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1023			.into_iter()1024			.map(|(key, permission)| PropertyKeyPermission { key, permission })1025			.collect();10261027		let properties = <CollectionProperties<T>>::get(collection)1028			.into_iter()1029			.map(|(key, value)| Property { key, value })1030			.collect();10311032		let permissions = CollectionPermissions {1033			access: Some(permissions.access()),1034			mint_mode: Some(permissions.mint_mode()),1035			nesting: Some(permissions.nesting().clone()),1036		};10371038		Some(RpcCollection {1039			name: name.into_inner(),1040			description: description.into_inner(),1041			owner,1042			mode,1043			token_prefix: token_prefix.into_inner(),1044			sponsorship,1045			limits,1046			permissions,1047			token_property_permissions,1048			properties,1049			read_only: flags.external,10501051			flags: RpcCollectionFlags {1052				foreign: flags.foreign,1053				erc721metadata: flags.erc721metadata,1054			},1055		})1056	}1057}10581059macro_rules! limit_default {1060	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1061		$(1062			if let Some($new) = $new.$field {1063				let $old = $old.$field($($arg)?);1064				let _ = $new;1065				let _ = $old;1066				$check1067			} else {1068				$new.$field = $old.$field1069			}1070		)*1071	}};1072}1073macro_rules! limit_default_clone {1074	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1075		$(1076			if let Some($new) = $new.$field.clone() {1077				let $old = $old.$field($($arg)?);1078				let _ = $new;1079				let _ = $old;1080				$check1081			} else {1082				$new.$field = $old.$field.clone()1083			}1084		)*1085	}};1086}10871088impl<T: Config> Pallet<T> {1089	/// Create new collection.1090	///1091	/// * `owner` - The owner of the collection.1092	/// * `data` - Description of the created collection.1093	/// * `flags` - Extra flags to store.1094	pub fn init_collection(1095		owner: T::CrossAccountId,1096		payer: T::CrossAccountId,1097		data: CreateCollectionData<T::AccountId>,1098		flags: CollectionFlags,1099	) -> Result<CollectionId, DispatchError> {1100		{1101			ensure!(1102				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1103				Error::<T>::CollectionTokenPrefixLimitExceeded1104			);1105		}11061107		let created_count = <CreatedCollectionCount<T>>::get()1108			.01109			.checked_add(1)1110			.ok_or(ArithmeticError::Overflow)?;1111		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1112		let id = CollectionId(created_count);11131114		// bound Total number of collections1115		ensure!(1116			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1117			<Error<T>>::TotalCollectionsLimitExceeded1118		);11191120		// =========11211122		let collection = Collection {1123			owner: owner.as_sub().clone(),1124			name: data.name,1125			mode: data.mode.clone(),1126			description: data.description,1127			token_prefix: data.token_prefix,1128			sponsorship: data1129				.pending_sponsor1130				.map(SponsorshipState::Unconfirmed)1131				.unwrap_or_default(),1132			limits: data1133				.limits1134				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1135				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,1136			permissions: data1137				.permissions1138				.map(|permissions| {1139					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1140				})1141				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1142			flags,1143		};11441145		let mut collection_properties = CollectionPropertiesT::new();1146		collection_properties1147			.try_set_from_iter(data.properties.into_iter())1148			.map_err(<Error<T>>::from)?;11491150		CollectionProperties::<T>::insert(id, collection_properties);11511152		let mut token_props_permissions = PropertiesPermissionMap::new();1153		token_props_permissions1154			.try_set_from_iter(data.token_property_permissions.into_iter())1155			.map_err(<Error<T>>::from)?;11561157		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11581159		// Take a (non-refundable) deposit of collection creation1160		{1161			let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1162			imbalance.subsume(<T as Config>::Currency::deposit(1163				&T::TreasuryAccountId::get(),1164				T::CollectionCreationPrice::get(),1165				Precision::Exact,1166			)?);1167			let credit =1168				<T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1169					.map_err(|_| Error::<T>::NotSufficientFounds)?;11701171			debug_assert!(credit.peek().is_zero())1172		}11731174		<CreatedCollectionCount<T>>::put(created_count);1175		<Pallet<T>>::deposit_event(Event::CollectionCreated(1176			id,1177			data.mode.id(),1178			owner.as_sub().clone(),1179		));1180		<PalletEvm<T>>::deposit_log(1181			erc::CollectionHelpersEvents::CollectionCreated {1182				owner: *owner.as_eth(),1183				collection_id: eth::collection_id_to_address(id),1184			}1185			.to_log(T::ContractAddress::get()),1186		);1187		<CollectionById<T>>::insert(id, collection);1188		Ok(id)1189	}11901191	/// Destroy collection.1192	///1193	/// * `collection` - Collection handler.1194	/// * `sender` - The owner or administrator of the collection.1195	pub fn destroy_collection(1196		collection: CollectionHandle<T>,1197		sender: &T::CrossAccountId,1198	) -> DispatchResult {1199		ensure!(1200			collection.limits.owner_can_destroy(),1201			<Error<T>>::NoPermission,1202		);1203		collection.check_is_owner(sender)?;12041205		let destroyed_collections = <DestroyedCollectionCount<T>>::get()1206			.01207			.checked_add(1)1208			.ok_or(ArithmeticError::Overflow)?;12091210		// =========12111212		<DestroyedCollectionCount<T>>::put(destroyed_collections);1213		<CollectionById<T>>::remove(collection.id);1214		<AdminAmount<T>>::remove(collection.id);1215		let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1216		let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1217		<CollectionProperties<T>>::remove(collection.id);12181219		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12201221		<PalletEvm<T>>::deposit_log(1222			erc::CollectionHelpersEvents::CollectionDestroyed {1223				collection_id: eth::collection_id_to_address(collection.id),1224			}1225			.to_log(T::ContractAddress::get()),1226		);1227		Ok(())1228	}12291230	/// This function sets or removes a collection properties according to1231	/// `properties_updates` contents:1232	/// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1233	/// * removes a property under the <key> if the value is `None` `(<key>, None)`.1234	///1235	/// This function fires an event for each property change.1236	/// In case of an error, all the changes (including the events) will be reverted1237	/// since the function is transactional.1238	#[transactional]1239	fn modify_collection_properties(1240		collection: &CollectionHandle<T>,1241		sender: &T::CrossAccountId,1242		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1243	) -> DispatchResult {1244		collection.check_is_owner_or_admin(sender)?;12451246		let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12471248		for (key, value) in properties_updates {1249			match value {1250				Some(value) => {1251					stored_properties1252						.try_set(key.clone(), value)1253						.map_err(<Error<T>>::from)?;12541255					Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1256					<PalletEvm<T>>::deposit_log(1257						erc::CollectionHelpersEvents::CollectionChanged {1258							collection_id: eth::collection_id_to_address(collection.id),1259						}1260						.to_log(T::ContractAddress::get()),1261					);1262				}1263				None => {1264					stored_properties.remove(&key).map_err(<Error<T>>::from)?;12651266					Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1267					<PalletEvm<T>>::deposit_log(1268						erc::CollectionHelpersEvents::CollectionChanged {1269							collection_id: eth::collection_id_to_address(collection.id),1270						}1271						.to_log(T::ContractAddress::get()),1272					);1273				}1274			}1275		}12761277		<CollectionProperties<T>>::set(collection.id, stored_properties);12781279		Ok(())1280	}12811282	/// A batch operation to add, edit or remove properties for a token.1283	/// It sets or removes a token's properties according to1284	/// `properties_updates` contents:1285	/// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1286	/// * removes a property under the <key> if the value is `None` `(<key>, None)`.1287	///1288	/// All affected properties should have `mutable` permission1289	/// to be **deleted** or to be **set more than once**,1290	/// and the sender should have permission to edit those properties.1291	///1292	/// This function fires an event for each property change.1293	/// In case of an error, all the changes (including the events) will be reverted1294	/// since the function is transactional.1295	#[allow(clippy::too_many_arguments)]1296	pub fn modify_token_properties<FTO, FTE>(1297		collection: &CollectionHandle<T>,1298		sender: &T::CrossAccountId,1299		token_id: TokenId,1300		is_token_exist: &mut LazyValue<bool, FTE>,1301		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1302		mut stored_properties: TokenProperties,1303		is_token_owner: &mut LazyValue<Result<bool, DispatchError>, FTO>,1304		set_token_properties: impl FnOnce(TokenProperties),1305		log: evm_coder::ethereum::Log,1306	) -> DispatchResult1307	where1308		FTO: FnOnce() -> Result<bool, DispatchError>,1309		FTE: FnOnce() -> bool,1310	{1311		let mut is_collection_admin = LazyValue::new(|| collection.is_owner_or_admin(sender));1312		let permissions = Self::property_permissions(collection.id);13131314		let mut changed = false;1315		for (key, value) in properties_updates {1316			let permission = permissions1317				.get(&key)1318				.cloned()1319				.unwrap_or_else(PropertyPermission::none);13201321			let property_exists = stored_properties.get(&key).is_some();13221323			match permission {1324				PropertyPermission { mutable: false, .. } if property_exists => {1325					return Err(<Error<T>>::NoPermission.into());1326				}13271328				PropertyPermission {1329					collection_admin,1330					token_owner,1331					..1332				} => check_token_permissions::<T, _, FTO, FTE>(1333					collection_admin,1334					token_owner,1335					&mut is_collection_admin,1336					is_token_owner,1337					is_token_exist,1338				)?,1339			}13401341			match value {1342				Some(value) => {1343					stored_properties1344						.try_set(key.clone(), value)1345						.map_err(<Error<T>>::from)?;13461347					Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1348				}1349				None => {1350					stored_properties.remove(&key).map_err(<Error<T>>::from)?;13511352					Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1353				}1354			}13551356			changed = true;1357		}13581359		if changed {1360			<PalletEvm<T>>::deposit_log(log);1361		}13621363		set_token_properties(stored_properties);13641365		Ok(())1366	}13671368	/// Sets or unsets the approval of a given operator.1369	///1370	/// The `operator` is allowed to transfer all token pieces of the `owner` on their behalf.1371	/// - `owner`: Token owner1372	/// - `operator`: Operator1373	/// - `approve`: Should operator status be granted or revoked?1374	pub fn set_allowance_for_all(1375		collection: &CollectionHandle<T>,1376		owner: &T::CrossAccountId,1377		operator: &T::CrossAccountId,1378		approve: bool,1379		set_allowance: impl FnOnce(),1380		log: evm_coder::ethereum::Log,1381	) -> DispatchResult {1382		if collection.permissions.access() == AccessMode::AllowList {1383			collection.check_allowlist(owner)?;1384			collection.check_allowlist(operator)?;1385		}13861387		Self::ensure_correct_receiver(operator)?;13881389		set_allowance();13901391		<PalletEvm<T>>::deposit_log(log);1392		Self::deposit_event(Event::ApprovedForAll(1393			collection.id,1394			owner.clone(),1395			operator.clone(),1396			approve,1397		));1398		Ok(())1399	}14001401	/// Set collection property.1402	///1403	/// * `collection` - Collection handler.1404	/// * `sender` - The owner or administrator of the collection.1405	/// * `property` - The property to set.1406	pub fn set_collection_property(1407		collection: &CollectionHandle<T>,1408		sender: &T::CrossAccountId,1409		property: Property,1410	) -> DispatchResult {1411		Self::set_collection_properties(collection, sender, [property].into_iter())1412	}14131414	/// Set a scoped collection property, where the scope is a special prefix1415	/// prohibiting a user access to change the property directly.1416	///1417	/// * `collection_id` - ID of the collection for which the property is being set.1418	/// * `scope` - Property scope.1419	/// * `property` - The property to set.1420	pub fn set_scoped_collection_property(1421		collection_id: CollectionId,1422		scope: PropertyScope,1423		property: Property,1424	) -> DispatchResult {1425		CollectionProperties::<T>::try_mutate(collection_id, |properties| {1426			properties.try_scoped_set(scope, property.key, property.value)1427		})1428		.map_err(<Error<T>>::from)?;14291430		Ok(())1431	}14321433	/// Set scoped collection properties, where the scope is a special prefix1434	/// prohibiting a user access to change the properties directly.1435	///1436	/// * `collection_id` - ID of the collection for which the properties is being set.1437	/// * `scope` - Property scope.1438	/// * `properties` - The properties to set.1439	pub fn set_scoped_collection_properties(1440		collection_id: CollectionId,1441		scope: PropertyScope,1442		properties: impl Iterator<Item = Property>,1443	) -> DispatchResult {1444		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1445			stored_properties.try_scoped_set_from_iter(scope, properties)1446		})1447		.map_err(<Error<T>>::from)?;14481449		Ok(())1450	}14511452	/// Set collection properties.1453	///1454	/// * `collection` - Collection handler.1455	/// * `sender` - The owner or administrator of the collection.1456	/// * `properties` - The properties to set.1457	pub fn set_collection_properties(1458		collection: &CollectionHandle<T>,1459		sender: &T::CrossAccountId,1460		properties: impl Iterator<Item = Property>,1461	) -> DispatchResult {1462		Self::modify_collection_properties(1463			collection,1464			sender,1465			properties.map(|property| (property.key, Some(property.value))),1466		)1467	}14681469	/// Delete collection property.1470	///1471	/// * `collection` - Collection handler.1472	/// * `sender` - The owner or administrator of the collection.1473	/// * `property` - The property to delete.1474	pub fn delete_collection_property(1475		collection: &CollectionHandle<T>,1476		sender: &T::CrossAccountId,1477		property_key: PropertyKey,1478	) -> DispatchResult {1479		Self::delete_collection_properties(collection, sender, [property_key].into_iter())1480	}14811482	/// Delete collection properties.1483	///1484	/// * `collection` - Collection handler.1485	/// * `sender` - The owner or administrator of the collection.1486	/// * `properties` - The properties to delete.1487	pub fn delete_collection_properties(1488		collection: &CollectionHandle<T>,1489		sender: &T::CrossAccountId,1490		property_keys: impl Iterator<Item = PropertyKey>,1491	) -> DispatchResult {1492		Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1493	}14941495	/// Set collection propetry permission without any checks.1496	///1497	/// Used for migrations.1498	///1499	/// * `collection` - Collection handler.1500	/// * `property_permissions` - Property permissions.1501	pub fn set_property_permission_unchecked(1502		collection: CollectionId,1503		property_permission: PropertyKeyPermission,1504	) -> DispatchResult {1505		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1506			permissions.try_set(property_permission.key, property_permission.permission)1507		})1508		.map_err(<Error<T>>::from)?;1509		Ok(())1510	}15111512	/// Set collection property permission.1513	///1514	/// * `collection` - Collection handler.1515	/// * `sender` - The owner or administrator of the collection.1516	/// * `property_permission` - Property permission.1517	pub fn set_property_permission(1518		collection: &CollectionHandle<T>,1519		sender: &T::CrossAccountId,1520		property_permission: PropertyKeyPermission,1521	) -> DispatchResult {1522		Self::set_scoped_property_permission(1523			collection,1524			sender,1525			PropertyScope::None,1526			property_permission,1527		)1528	}15291530	/// Set collection property permission with scope.1531	///1532	/// * `collection` - Collection handler.1533	/// * `sender` - The owner or administrator of the collection.1534	/// * `scope` - Property scope.1535	/// * `property_permission` - Property permission.1536	pub fn set_scoped_property_permission(1537		collection: &CollectionHandle<T>,1538		sender: &T::CrossAccountId,1539		scope: PropertyScope,1540		property_permission: PropertyKeyPermission,1541	) -> DispatchResult {1542		collection.check_is_owner_or_admin(sender)?;15431544		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1545		let current_permission = all_permissions.get(&property_permission.key);1546		if matches![1547			current_permission,1548			Some(PropertyPermission { mutable: false, .. })1549		] {1550			return Err(<Error<T>>::NoPermission.into());1551		}15521553		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1554			let property_permission = property_permission.clone();1555			permissions.try_scoped_set(1556				scope,1557				property_permission.key,1558				property_permission.permission,1559			)1560		})1561		.map_err(<Error<T>>::from)?;15621563		Self::deposit_event(Event::PropertyPermissionSet(1564			collection.id,1565			property_permission.key,1566		));1567		<PalletEvm<T>>::deposit_log(1568			erc::CollectionHelpersEvents::CollectionChanged {1569				collection_id: eth::collection_id_to_address(collection.id),1570			}1571			.to_log(T::ContractAddress::get()),1572		);15731574		Ok(())1575	}15761577	/// Set token property permission.1578	///1579	/// * `collection` - Collection handler.1580	/// * `sender` - The owner or administrator of the collection.1581	/// * `property_permissions` - Property permissions.1582	#[transactional]1583	pub fn set_token_property_permissions(1584		collection: &CollectionHandle<T>,1585		sender: &T::CrossAccountId,1586		property_permissions: Vec<PropertyKeyPermission>,1587	) -> DispatchResult {1588		Self::set_scoped_token_property_permissions(1589			collection,1590			sender,1591			PropertyScope::None,1592			property_permissions,1593		)1594	}15951596	/// Set token property permission with scope.1597	///1598	/// * `collection` - Collection handler.1599	/// * `sender` - The owner or administrator of the collection.1600	/// * `scope` - Property scope.1601	/// * `property_permissions` - Property permissions.1602	#[transactional]1603	pub fn set_scoped_token_property_permissions(1604		collection: &CollectionHandle<T>,1605		sender: &T::CrossAccountId,1606		scope: PropertyScope,1607		property_permissions: Vec<PropertyKeyPermission>,1608	) -> DispatchResult {1609		for prop_pemission in property_permissions {1610			Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1611		}16121613		Ok(())1614	}16151616	/// Get collection property.1617	pub fn get_collection_property(1618		collection_id: CollectionId,1619		key: &PropertyKey,1620	) -> Option<PropertyValue> {1621		Self::collection_properties(collection_id).get(key).cloned()1622	}16231624	/// Convert byte vector to property key vector.1625	pub fn bytes_keys_to_property_keys(1626		keys: Vec<Vec<u8>>,1627	) -> Result<Vec<PropertyKey>, DispatchError> {1628		keys.into_iter()1629			.map(|key| -> Result<PropertyKey, DispatchError> {1630				key.try_into()1631					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1632			})1633			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1634	}16351636	/// Get properties according to given keys.1637	pub fn filter_collection_properties(1638		collection_id: CollectionId,1639		keys: Option<Vec<PropertyKey>>,1640	) -> Result<Vec<Property>, DispatchError> {1641		let properties = Self::collection_properties(collection_id);16421643		let properties = keys1644			.map(|keys| {1645				keys.into_iter()1646					.filter_map(|key| {1647						properties.get(&key).map(|value| Property {1648							key,1649							value: value.clone(),1650						})1651					})1652					.collect()1653			})1654			.unwrap_or_else(|| {1655				properties1656					.into_iter()1657					.map(|(key, value)| Property { key, value })1658					.collect()1659			});16601661		Ok(properties)1662	}16631664	/// Get property permissions according to given keys.1665	pub fn filter_property_permissions(1666		collection_id: CollectionId,1667		keys: Option<Vec<PropertyKey>>,1668	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1669		let permissions = Self::property_permissions(collection_id);16701671		let key_permissions = keys1672			.map(|keys| {1673				keys.into_iter()1674					.filter_map(|key| {1675						permissions1676							.get(&key)1677							.map(|permission| PropertyKeyPermission {1678								key,1679								permission: permission.clone(),1680							})1681					})1682					.collect()1683			})1684			.unwrap_or_else(|| {1685				permissions1686					.into_iter()1687					.map(|(key, permission)| PropertyKeyPermission { key, permission })1688					.collect()1689			});16901691		Ok(key_permissions)1692	}16931694	/// Toggle `user` participation in the `collection`'s allow list.1695	/// #### Store read/writes1696	/// 1 writes1697	pub fn toggle_allowlist(1698		collection: &CollectionHandle<T>,1699		sender: &T::CrossAccountId,1700		user: &T::CrossAccountId,1701		allowed: bool,1702	) -> DispatchResult {1703		collection.check_is_owner_or_admin(sender)?;17041705		// =========17061707		if allowed {1708			<Allowlist<T>>::insert((collection.id, user), true);1709			Self::deposit_event(Event::<T>::AllowListAddressAdded(1710				collection.id,1711				user.clone(),1712			));1713		} else {1714			<Allowlist<T>>::remove((collection.id, user));1715			Self::deposit_event(Event::<T>::AllowListAddressRemoved(1716				collection.id,1717				user.clone(),1718			));1719		}17201721		<PalletEvm<T>>::deposit_log(1722			erc::CollectionHelpersEvents::CollectionChanged {1723				collection_id: eth::collection_id_to_address(collection.id),1724			}1725			.to_log(T::ContractAddress::get()),1726		);17271728		Ok(())1729	}17301731	/// Toggle `user` participation in the `collection`'s admin list.1732	/// #### Store read/writes1733	/// 2 reads, 2 writes1734	pub fn toggle_admin(1735		collection: &CollectionHandle<T>,1736		sender: &T::CrossAccountId,1737		user: &T::CrossAccountId,1738		admin: bool,1739	) -> DispatchResult {1740		collection.check_is_internal()?;1741		collection.check_is_owner(sender)?;17421743		let is_admin = <IsAdmin<T>>::get((collection.id, user));1744		if is_admin == admin {1745			if admin {1746				return Ok(());1747			} else {1748				return Err(Error::<T>::UserIsNotCollectionAdmin.into());1749			}1750		}1751		let amount = <AdminAmount<T>>::get(collection.id);17521753		// =========17541755		if admin {1756			let amount = amount1757				.checked_add(1)1758				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1759			ensure!(1760				amount <= Self::collection_admins_limit(),1761				<Error<T>>::CollectionAdminCountExceeded,1762			);17631764			<AdminAmount<T>>::insert(collection.id, amount);1765			<IsAdmin<T>>::insert((collection.id, user), true);17661767			Self::deposit_event(Event::<T>::CollectionAdminAdded(1768				collection.id,1769				user.clone(),1770			));1771		} else {1772			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1773			<IsAdmin<T>>::remove((collection.id, user));17741775			Self::deposit_event(Event::<T>::CollectionAdminRemoved(1776				collection.id,1777				user.clone(),1778			));1779		}17801781		<PalletEvm<T>>::deposit_log(1782			erc::CollectionHelpersEvents::CollectionChanged {1783				collection_id: eth::collection_id_to_address(collection.id),1784			}1785			.to_log(T::ContractAddress::get()),1786		);17871788		Ok(())1789	}17901791	/// Update collection limits.1792	pub fn update_limits(1793		user: &T::CrossAccountId,1794		collection: &mut CollectionHandle<T>,1795		new_limit: CollectionLimits,1796	) -> DispatchResult {1797		collection.check_is_internal()?;1798		collection.check_is_owner_or_admin(user)?;17991800		collection.limits =1801			Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;18021803		Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1804		<PalletEvm<T>>::deposit_log(1805			erc::CollectionHelpersEvents::CollectionChanged {1806				collection_id: eth::collection_id_to_address(collection.id),1807			}1808			.to_log(T::ContractAddress::get()),1809		);18101811		collection.save()1812	}18131814	/// Merge set fields from `new_limit` to `old_limit`.1815	fn clamp_limits(1816		mode: CollectionMode,1817		old_limit: &CollectionLimits,1818		mut new_limit: CollectionLimits,1819	) -> Result<CollectionLimits, DispatchError> {1820		let limits = old_limit;1821		limit_default!(old_limit, new_limit,1822			account_token_ownership_limit => ensure!(1823				new_limit <= MAX_TOKEN_OWNERSHIP,1824				<Error<T>>::CollectionLimitBoundsExceeded,1825			),1826			sponsored_data_size => ensure!(1827				new_limit <= CUSTOM_DATA_LIMIT,1828				<Error<T>>::CollectionLimitBoundsExceeded,1829			),18301831			sponsored_data_rate_limit => {},1832			token_limit => ensure!(1833				old_limit >= new_limit && new_limit > 0,1834				<Error<T>>::CollectionTokenLimitExceeded1835			),18361837			sponsor_transfer_timeout(match mode {1838				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1839				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1840				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1841			}) => ensure!(1842				new_limit <= MAX_SPONSOR_TIMEOUT,1843				<Error<T>>::CollectionLimitBoundsExceeded,1844			),1845			sponsor_approve_timeout => {},1846			owner_can_transfer => ensure!(1847				!limits.owner_can_transfer_instaled() ||1848				old_limit || !new_limit,1849				<Error<T>>::OwnerPermissionsCantBeReverted,1850			),1851			owner_can_destroy => ensure!(1852				old_limit || !new_limit,1853				<Error<T>>::OwnerPermissionsCantBeReverted,1854			),1855			transfers_enabled => {},1856		);1857		Ok(new_limit)1858	}18591860	/// Update collection permissions.1861	pub fn update_permissions(1862		user: &T::CrossAccountId,1863		collection: &mut CollectionHandle<T>,1864		new_permission: CollectionPermissions,1865	) -> DispatchResult {1866		collection.check_is_internal()?;1867		collection.check_is_owner_or_admin(user)?;1868		collection.permissions = Self::clamp_permissions(1869			collection.mode.clone(),1870			&collection.permissions,1871			new_permission,1872		)?;18731874		Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1875		<PalletEvm<T>>::deposit_log(1876			erc::CollectionHelpersEvents::CollectionChanged {1877				collection_id: eth::collection_id_to_address(collection.id),1878			}1879			.to_log(T::ContractAddress::get()),1880		);18811882		collection.save()1883	}18841885	/// Merge set fields from `new_permission` to `old_permission`.1886	fn clamp_permissions(1887		_mode: CollectionMode,1888		old_permission: &CollectionPermissions,1889		mut new_permission: CollectionPermissions,1890	) -> Result<CollectionPermissions, DispatchError> {1891		limit_default_clone!(old_permission, new_permission,1892			access => {},1893			mint_mode => {},1894			nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1895		);1896		Ok(new_permission)1897	}18981899	/// Repair possibly broken properties of a collection.1900	pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1901		CollectionProperties::<T>::mutate(collection_id, |properties| {1902			properties.recompute_consumed_space();1903		});19041905		Ok(())1906	}1907}19081909/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1910#[macro_export]1911macro_rules! unsupported {1912	($runtime:path) => {1913		Err($crate::Error::<$runtime>::UnsupportedOperation.into())1914	};1915}19161917/// Return weights for various worst-case operations.1918pub trait CommonWeightInfo<CrossAccountId> {1919	/// Weight of item creation.1920	fn create_item(data: &CreateItemData) -> Weight {1921		Self::create_multiple_items(from_ref(data))1922	}19231924	/// Weight of items creation.1925	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;19261927	/// Weight of items creation.1928	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;19291930	/// The weight of the burning item.1931	fn burn_item() -> Weight;19321933	/// Property setting weight.1934	///1935	/// * `amount`- The number of properties to set.1936	fn set_collection_properties(amount: u32) -> Weight;19371938	/// Collection property deletion weight.1939	///1940	/// * `amount`- The number of properties to set.1941	fn delete_collection_properties(amount: u32) -> Weight;19421943	/// Token property setting weight.1944	///1945	/// * `amount`- The number of properties to set.1946	fn set_token_properties(amount: u32) -> Weight;19471948	/// Token property deletion weight.1949	///1950	/// * `amount`- The number of properties to delete.1951	fn delete_token_properties(amount: u32) -> Weight;19521953	/// Token property permissions set weight.1954	///1955	/// * `amount`- The number of property permissions to set.1956	fn set_token_property_permissions(amount: u32) -> Weight;19571958	/// Transfer price of the token or its parts.1959	fn transfer() -> Weight;19601961	/// The price of setting the permission of the operation from another user.1962	fn approve() -> Weight;19631964	/// The price of setting the permission of the operation from another user for eth mirror.1965	fn approve_from() -> Weight;19661967	/// Transfer price from another user.1968	fn transfer_from() -> Weight;19691970	/// The price of burning a token from another user.1971	fn burn_from() -> Weight;19721973	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1974	/// whole users's balance.1975	///1976	/// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1977	fn burn_recursively_self_raw() -> Weight;19781979	/// Cost of iterating over `amount` children while burning, without counting child burning itself.1980	///1981	/// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1982	fn burn_recursively_breadth_raw(amount: u32) -> Weight;19831984	/// The price of recursive burning a token.1985	///1986	/// `max_selfs` - The maximum burning weight of the token itself.1987	/// `max_breadth` - The maximum number of nested tokens to burn.1988	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1989		Self::burn_recursively_self_raw()1990			.saturating_mul(max_selfs.max(1) as u64)1991			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1992	}19931994	/// The price of retrieving token owner1995	fn token_owner() -> Weight;19961997	/// The price of setting approval for all1998	fn set_allowance_for_all() -> Weight;19992000	/// The price of repairing an item.2001	fn force_repair_item() -> Weight;2002}20032004/// Weight info extension trait for refungible pallet.2005pub trait RefungibleExtensionsWeightInfo {2006	/// Weight of token repartition.2007	fn repartition() -> Weight;2008}20092010/// Common collection operations.2011///2012/// It wraps methods in Fungible, Nonfungible and Refungible pallets2013/// and adds weight info.2014pub trait CommonCollectionOperations<T: Config> {2015	/// Create token.2016	///2017	/// * `sender` - The user who mint the token and pays for the transaction.2018	/// * `to` - The user who will own the token.2019	/// * `data` - Token data.2020	/// * `nesting_budget` - A budget that can be spent on nesting tokens.2021	fn create_item(2022		&self,2023		sender: T::CrossAccountId,2024		to: T::CrossAccountId,2025		data: CreateItemData,2026		nesting_budget: &dyn Budget,2027	) -> DispatchResultWithPostInfo;20282029	/// Create multiple tokens.2030	///2031	/// * `sender` - The user who mint the token and pays for the transaction.2032	/// * `to` - The user who will own the token.2033	/// * `data` - Token data.2034	/// * `nesting_budget` - A budget that can be spent on nesting tokens.2035	fn create_multiple_items(2036		&self,2037		sender: T::CrossAccountId,2038		to: T::CrossAccountId,2039		data: Vec<CreateItemData>,2040		nesting_budget: &dyn Budget,2041	) -> DispatchResultWithPostInfo;20422043	/// Create multiple tokens.2044	///2045	/// * `sender` - The user who mint the token and pays for the transaction.2046	/// * `to` - The user who will own the token.2047	/// * `data` - Token data.2048	/// * `nesting_budget` - A budget that can be spent on nesting tokens.2049	fn create_multiple_items_ex(2050		&self,2051		sender: T::CrossAccountId,2052		data: CreateItemExData<T::CrossAccountId>,2053		nesting_budget: &dyn Budget,2054	) -> DispatchResultWithPostInfo;20552056	/// Burn token.2057	///2058	/// * `sender` - The user who owns the token.2059	/// * `token` - Token id that will burned.2060	/// * `amount` - The number of parts of the token that will be burned.2061	fn burn_item(2062		&self,2063		sender: T::CrossAccountId,2064		token: TokenId,2065		amount: u128,2066	) -> DispatchResultWithPostInfo;20672068	/// Burn token and all nested tokens recursievly.2069	///2070	/// * `sender` - The user who owns the token.2071	/// * `token` - Token id that will burned.2072	/// * `self_budget` - The budget that can be spent on burning tokens.2073	/// * `breadth_budget` - The budget that can be spent on burning nested tokens.2074	fn burn_item_recursively(2075		&self,2076		sender: T::CrossAccountId,2077		token: TokenId,2078		self_budget: &dyn Budget,2079		breadth_budget: &dyn Budget,2080	) -> DispatchResultWithPostInfo;20812082	/// Set collection properties.2083	///2084	/// * `sender` - Must be either the owner of the collection or its admin.2085	/// * `properties` - Properties to be set.2086	fn set_collection_properties(2087		&self,2088		sender: T::CrossAccountId,2089		properties: Vec<Property>,2090	) -> DispatchResultWithPostInfo;20912092	/// Delete collection properties.2093	///2094	/// * `sender` - Must be either the owner of the collection or its admin.2095	/// * `properties` - The properties to be removed.2096	fn delete_collection_properties(2097		&self,2098		sender: &T::CrossAccountId,2099		property_keys: Vec<PropertyKey>,2100	) -> DispatchResultWithPostInfo;21012102	/// Set token properties.2103	///2104	/// The appropriate [`PropertyPermission`] for the token property2105	/// must be set with [`Self::set_token_property_permissions`].2106	///2107	/// * `sender` - Must be either the owner of the token or its admin.2108	/// * `token_id` - The token for which the properties are being set.2109	/// * `properties` - Properties to be set.2110	/// * `budget` - Budget for setting properties.2111	fn set_token_properties(2112		&self,2113		sender: T::CrossAccountId,2114		token_id: TokenId,2115		properties: Vec<Property>,2116		budget: &dyn Budget,2117	) -> DispatchResultWithPostInfo;21182119	/// Remove token properties.2120	///2121	/// The appropriate [`PropertyPermission`] for the token property2122	/// must be set with [`Self::set_token_property_permissions`].2123	///2124	/// * `sender` - Must be either the owner of the token or its admin.2125	/// * `token_id` - The token for which the properties are being remove.2126	/// * `property_keys` - Keys to remove corresponding properties.2127	/// * `budget` - Budget for removing properties.2128	fn delete_token_properties(2129		&self,2130		sender: T::CrossAccountId,2131		token_id: TokenId,2132		property_keys: Vec<PropertyKey>,2133		budget: &dyn Budget,2134	) -> DispatchResultWithPostInfo;21352136	/// Set token property permissions.2137	///2138	/// * `sender` - Must be either the owner of the token or its admin.2139	/// * `token_id` - The token for which the properties are being set.2140	/// * `property_permissions` - Property permissions to be set.2141	/// * `budget` - Budget for setting properties.2142	fn set_token_property_permissions(2143		&self,2144		sender: &T::CrossAccountId,2145		property_permissions: Vec<PropertyKeyPermission>,2146	) -> DispatchResultWithPostInfo;21472148	/// Transfer amount of token pieces.2149	///2150	/// * `sender` - Donor user.2151	/// * `to` - Recepient user.2152	/// * `token` - The token of which parts are being sent.2153	/// * `amount` - The number of parts of the token that will be transferred.2154	/// * `budget` - The maximum budget that can be spent on the transfer.2155	fn transfer(2156		&self,2157		sender: T::CrossAccountId,2158		to: T::CrossAccountId,2159		token: TokenId,2160		amount: u128,2161		budget: &dyn Budget,2162	) -> DispatchResultWithPostInfo;21632164	/// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].2165	///2166	/// * `sender` - The user who grants access to the token.2167	/// * `spender` - The user to whom the rights are granted.2168	/// * `token` - The token to which access is granted.2169	/// * `amount` - The amount of pieces that another user can dispose of.2170	fn approve(2171		&self,2172		sender: T::CrossAccountId,2173		spender: T::CrossAccountId,2174		token: TokenId,2175		amount: u128,2176	) -> DispatchResultWithPostInfo;21772178	/// Grant access to another account to transfer parts of the token owned by the calling user's eth mirror via [Self::transfer_from].2179	///2180	/// * `sender` - The user who grants access to the token.2181	/// * `from` - Spender's eth mirror.2182	/// * `to` - The user to whom the rights are granted.2183	/// * `token` - The token to which access is granted.2184	/// * `amount` - The amount of pieces that another user can dispose of.2185	fn approve_from(2186		&self,2187		sender: T::CrossAccountId,2188		from: T::CrossAccountId,2189		to: T::CrossAccountId,2190		token: TokenId,2191		amount: u128,2192	) -> DispatchResultWithPostInfo;21932194	/// Send parts of a token owned by another user.2195	///2196	/// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2197	///2198	/// * `sender` - The user who must have access to the token (see [`Self::approve`]).2199	/// * `from` - The user who owns the token.2200	/// * `to` - Recepient user.2201	/// * `token` - The token of which parts are being sent.2202	/// * `amount` - The number of parts of the token that will be transferred.2203	/// * `budget` - The maximum budget that can be spent on the transfer.2204	fn transfer_from(2205		&self,2206		sender: T::CrossAccountId,2207		from: T::CrossAccountId,2208		to: T::CrossAccountId,2209		token: TokenId,2210		amount: u128,2211		budget: &dyn Budget,2212	) -> DispatchResultWithPostInfo;22132214	/// Burn parts of a token owned by another user.2215	///2216	/// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2217	///2218	/// * `sender` - The user who must have access to the token (see [`Self::approve`]).2219	/// * `from` - The user who owns the token.2220	/// * `token` - The token of which parts are being sent.2221	/// * `amount` - The number of parts of the token that will be transferred.2222	/// * `budget` - The maximum budget that can be spent on the burn.2223	fn burn_from(2224		&self,2225		sender: T::CrossAccountId,2226		from: T::CrossAccountId,2227		token: TokenId,2228		amount: u128,2229		budget: &dyn Budget,2230	) -> DispatchResultWithPostInfo;22312232	/// Check permission to nest token.2233	///2234	/// * `sender` - The user who initiated the check.2235	/// * `from` - The token that is checked for embedding.2236	/// * `under` - Token under which to check.2237	/// * `budget` - The maximum budget that can be spent on the check.2238	fn check_nesting(2239		&self,2240		sender: T::CrossAccountId,2241		from: (CollectionId, TokenId),2242		under: TokenId,2243		budget: &dyn Budget,2244	) -> DispatchResult;22452246	/// Nest one token into another.2247	///2248	/// * `under` - Token holder.2249	/// * `to_nest` - Nested token.2250	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22512252	/// Unnest token.2253	///2254	/// * `under` - Token holder.2255	/// * `to_nest` - Token to unnest.2256	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22572258	/// Get all user tokens.2259	///2260	/// * `account` - Account for which you need to get tokens.2261	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22622263	/// Get all the tokens in the collection.2264	fn collection_tokens(&self) -> Vec<TokenId>;22652266	/// Check if the token exists.2267	///2268	/// * `token` - Id token to check.2269	fn token_exists(&self, token: TokenId) -> bool;22702271	/// Get the id of the last minted token.2272	fn last_token_id(&self) -> TokenId;22732274	/// Get the owner of the token.2275	///2276	/// * `token` - The token for which you need to find out the owner.2277	fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22782279	/// Returns 10 tokens owners in no particular order.2280	///2281	/// * `token` - The token for which you need to find out the owners.2282	fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22832284	/// Get the value of the token property by key.2285	///2286	/// * `token` - Token with the property to get.2287	/// * `key` - Property name.2288	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22892290	/// Get a set of token properties by key vector.2291	///2292	/// * `token` - Token with the property to get.2293	/// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),2294	/// then all properties are returned.2295	fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22962297	/// Amount of unique collection tokens2298	fn total_supply(&self) -> u32;22992300	/// Amount of different tokens account has.2301	///2302	/// * `account` - The account for which need to get the balance.2303	fn account_balance(&self, account: T::CrossAccountId) -> u32;23042305	/// Amount of specific token account have.2306	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;23072308	/// Amount of token pieces2309	fn total_pieces(&self, token: TokenId) -> Option<u128>;23102311	/// Get the number of parts of the token that a trusted user can manage.2312	///2313	/// * `sender` - Trusted user.2314	/// * `spender` - Owner of the token.2315	/// * `token` - The token for which to get the value.2316	fn allowance(2317		&self,2318		sender: T::CrossAccountId,2319		spender: T::CrossAccountId,2320		token: TokenId,2321	) -> u128;23222323	/// Get extension for RFT collection.2324	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;23252326	/// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.2327	/// * `owner` - Token owner2328	/// * `operator` - Operator2329	/// * `approve` - Should operator status be granted or revoked?2330	fn set_allowance_for_all(2331		&self,2332		owner: T::CrossAccountId,2333		operator: T::CrossAccountId,2334		approve: bool,2335	) -> DispatchResultWithPostInfo;23362337	/// Tells whether the given `owner` approves the `operator`.2338	fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23392340	/// Repairs a possibly broken item.2341	fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2342}23432344/// Extension for RFT collection.2345pub trait RefungibleExtensions<T>2346where2347	T: Config,2348{2349	/// Change the number of parts of the token.2350	///2351	/// When the value changes down, this function is equivalent to burning parts of the token.2352	///2353	/// * `sender` - The user calling the repartition operation. Must be the owner of the token.2354	/// * `token` - The token for which you want to change the number of parts.2355	/// * `amount` - The new value of the parts of the token.2356	fn repartition(2357		&self,2358		sender: &T::CrossAccountId,2359		token: TokenId,2360		amount: u128,2361	) -> DispatchResultWithPostInfo;2362}23632364/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].2365///2366/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.2367pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2368	let post_info = PostDispatchInfo {2369		actual_weight: Some(weight),2370		pays_fee: Pays::Yes,2371	};2372	match res {2373		Ok(()) => Ok(post_info),2374		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2375	}2376}23772378impl<T: Config> From<PropertiesError> for Error<T> {2379	fn from(error: PropertiesError) -> Self {2380		match error {2381			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2382			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2383			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2384			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2385			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2386		}2387	}2388}23892390#[cfg(feature = "tests")]2391pub mod tests {2392	use crate::{DispatchResult, DispatchError, LazyValue, Config};23932394	const fn to_bool(u: u8) -> bool {2395		u != 02396	}23972398	#[derive(Debug)]2399	pub struct TestCase {2400		pub collection_admin: bool,2401		pub is_collection_admin: bool,2402		pub token_owner: bool,2403		pub is_token_owner: bool,2404		pub no_permission: bool,2405	}24062407	impl TestCase {2408		const fn new(2409			collection_admin: u8,2410			is_collection_admin: u8,2411			token_owner: u8,2412			is_token_owner: u8,2413			no_permission: u8,2414		) -> Self {2415			Self {2416				collection_admin: to_bool(collection_admin),2417				is_collection_admin: to_bool(is_collection_admin),2418				token_owner: to_bool(token_owner),2419				is_token_owner: to_bool(is_token_owner),2420				no_permission: to_bool(no_permission),2421			}2422		}2423	}24242425	#[rustfmt::skip]2426	pub const table: [TestCase; 16] = [2427		//                    ┌╴collection_admin2428		//                    │  ┌╴is_collection_admin2429		//                    │  │   ┌╴token_owner2430		//                    │  │   │  ┌╴is_token_ownership2431		//                    │  │   │  │   ┌╴no_permission2432		/*  0*/ TestCase::new(0, 0,  0, 0,  1),2433		/*  1*/ TestCase::new(0, 0,  0, 1,  1),2434		/*  2*/ TestCase::new(0, 0,  1, 0,  1),2435		/*  3*/ TestCase::new(0, 0,  1, 1,  0),2436		/*  4*/ TestCase::new(0, 1,  0, 0,  1),2437		/*  5*/ TestCase::new(0, 1,  0, 1,  1),2438		/*  6*/ TestCase::new(0, 1,  1, 0,  1),2439		/*  7*/ TestCase::new(0, 1,  1, 1,  0),2440		/*  8*/ TestCase::new(1, 0,  0, 0,  1),2441		/*  9*/ TestCase::new(1, 0,  0, 1,  1),2442		/* 10*/ TestCase::new(1, 0,  1, 0,  1),2443		/* 11*/ TestCase::new(1, 0,  1, 1,  0),2444		/* 12*/ TestCase::new(1, 1,  0, 0,  0),2445		/* 13*/ TestCase::new(1, 1,  0, 1,  0),2446		/* 14*/ TestCase::new(1, 1,  1, 0,  0),2447		/* 15*/ TestCase::new(1, 1,  1, 1,  0),2448	];24492450	pub fn check_token_permissions<T, FCA, FTO, FTE>(2451		collection_admin_permitted: bool,2452		token_owner_permitted: bool,2453		is_collection_admin: &mut LazyValue<bool, FCA>,2454		check_token_ownership: &mut LazyValue<Result<bool, DispatchError>, FTO>,2455		check_token_existence: &mut LazyValue<bool, FTE>,2456	) -> DispatchResult2457	where2458		T: Config,2459		FCA: FnOnce() -> bool,2460		FTO: FnOnce() -> Result<bool, DispatchError>,2461		FTE: FnOnce() -> bool,2462	{2463		crate::check_token_permissions::<T, FCA, FTO, FTE>(2464			collection_admin_permitted,2465			token_owner_permitted,2466			is_collection_admin,2467			check_token_ownership,2468			check_token_existence,2469		)2470	}2471}
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -245,7 +245,7 @@
 				&sender,
 				token_id,
 				properties.into_iter(),
-				false,
+				pallet_common::SetPropertyMode::ExistingToken,
 				nesting_budget,
 			),
 			weight,
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -194,7 +194,7 @@
 			&caller,
 			TokenId(token_id),
 			properties.into_iter(),
-			false,
+			pallet_common::SetPropertyMode::ExistingToken,
 			&nesting_budget,
 		)
 		.map_err(dispatch_to_evm::<T>)
@@ -939,9 +939,8 @@
 	/// @notice Returns next free NFT ID.
 	fn next_token_id(&self) -> Result<U256> {
 		self.consume_store_reads(1)?;
-		Ok(<TokensMinted<T>>::get(self.id)
-			.checked_add(1)
-			.ok_or("item id overflow")?
+		Ok(<Pallet<T>>::next_token_id(self)
+			.map_err(dispatch_to_evm::<T>)?
 			.into())
 	}
 
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -109,7 +109,7 @@
 use pallet_common::{
 	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,
 	eth::collection_id_to_address, SelfWeightOf as PalletCommonWeightOf,
-	weights::WeightInfo as CommonWeightInfo, helpers::add_weight_to_post_info,
+	weights::WeightInfo as CommonWeightInfo, helpers::add_weight_to_post_info, SetPropertyMode,
 };
 use pallet_structure::{Pallet as PalletStructure, Error as StructureError};
 use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
@@ -585,8 +585,6 @@
 	/// A batch operation to add, edit or remove properties for a token.
 	///
 	/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
-	/// - `is_token_create`: Indicates that method is called during token initialization.
-	///   Allows to bypass ownership check.
 	///
 	/// All affected properties should have `mutable` permission
 	/// to be **deleted** or to be **set more than once**,
@@ -601,10 +599,17 @@
 		sender: &T::CrossAccountId,
 		token_id: TokenId,
 		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,
-		is_token_create: bool,
+		mode: SetPropertyMode,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
-		let is_token_owner = || {
+		let mut is_token_owner = pallet_common::LazyValue::new(|| {
+			if let SetPropertyMode::NewToken {
+				mint_target_is_sender,
+			} = mode
+			{
+				return Ok(mint_target_is_sender);
+			}
+
 			let is_owned = <PalletStructure<T>>::check_indirectly_owned(
 				sender.clone(),
 				collection.id,
@@ -614,18 +619,21 @@
 			)?;
 
 			Ok(is_owned)
-		};
+		});
 
+		let mut is_token_exist =
+			pallet_common::LazyValue::new(|| Self::token_exists(collection, token_id));
+
 		let stored_properties = <TokenProperties<T>>::get((collection.id, token_id));
 
 		<PalletCommon<T>>::modify_token_properties(
 			collection,
 			sender,
 			token_id,
+			&mut is_token_exist,
 			properties_updates,
-			is_token_create,
 			stored_properties,
-			is_token_owner,
+			&mut is_token_owner,
 			|properties| <TokenProperties<T>>::set((collection.id, token_id), properties),
 			erc::ERC721TokenEvent::TokenChanged {
 				token_id: token_id.into(),
@@ -634,6 +642,19 @@
 		)
 	}
 
+	pub fn next_token_id(collection: &NonfungibleHandle<T>) -> Result<TokenId, DispatchError> {
+		let next_token_id = <TokensMinted<T>>::get(collection.id)
+			.checked_add(1)
+			.ok_or(<CommonError<T>>::CollectionTokenLimitExceeded)?;
+
+		ensure!(
+			collection.limits.token_limit() >= next_token_id,
+			<CommonError<T>>::CollectionTokenLimitExceeded
+		);
+
+		Ok(TokenId(next_token_id))
+	}
+
 	/// Batch operation to add or edit properties for the token
 	///
 	/// Same as [`modify_token_properties`] but doesn't allow to remove properties
@@ -644,7 +665,7 @@
 		sender: &T::CrossAccountId,
 		token_id: TokenId,
 		properties: impl Iterator<Item = Property>,
-		is_token_create: bool,
+		mode: SetPropertyMode,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
 		Self::modify_token_properties(
@@ -652,7 +673,7 @@
 			sender,
 			token_id,
 			properties.map(|p| (p.key, Some(p.value))),
-			is_token_create,
+			mode,
 			nesting_budget,
 		)
 	}
@@ -669,14 +690,12 @@
 		property: Property,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
-		let is_token_create = false;
-
 		Self::set_token_properties(
 			collection,
 			sender,
 			token_id,
 			[property].into_iter(),
-			is_token_create,
+			SetPropertyMode::ExistingToken,
 			nesting_budget,
 		)
 	}
@@ -693,14 +712,12 @@
 		property_keys: impl Iterator<Item = PropertyKey>,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
-		let is_token_create = false;
-
 		Self::modify_token_properties(
 			collection,
 			sender,
 			token_id,
 			property_keys.into_iter().map(|key| (key, None)),
-			is_token_create,
+			SetPropertyMode::ExistingToken,
 			nesting_budget,
 		)
 	}
@@ -985,7 +1002,9 @@
 					sender,
 					TokenId(token),
 					data.properties.clone().into_iter(),
-					true,
+					SetPropertyMode::NewToken {
+						mint_target_is_sender: sender.conv_eq(&data.owner),
+					},
 					nesting_budget,
 				) {
 					return TransactionOutcome::Rollback(Err(e));
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -399,7 +399,7 @@
 				&sender,
 				token_id,
 				properties.into_iter(),
-				false,
+				pallet_common::SetPropertyMode::ExistingToken,
 				nesting_budget,
 			),
 			weight,
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -196,7 +196,7 @@
 			&caller,
 			TokenId(token_id),
 			properties.into_iter(),
-			false,
+			pallet_common::SetPropertyMode::ExistingToken,
 			&nesting_budget,
 		)
 		.map_err(dispatch_to_evm::<T>)
@@ -973,9 +973,8 @@
 	/// @notice Returns next free RFT ID.
 	fn next_token_id(&self) -> Result<U256> {
 		self.consume_store_reads(1)?;
-		Ok(<TokensMinted<T>>::get(self.id)
-			.checked_add(1)
-			.ok_or("item id overflow")?
+		Ok(<Pallet<T>>::next_token_id(self)
+			.map_err(dispatch_to_evm::<T>)?
 			.into())
 	}
 
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -97,7 +97,7 @@
 use pallet_evm_coder_substrate::WithRecorder;
 use pallet_common::{
 	CommonCollectionOperations, Error as CommonError, eth::collection_id_to_address,
-	Event as CommonEvent, Pallet as PalletCommon,
+	Event as CommonEvent, Pallet as PalletCommon, SetPropertyMode,
 };
 use pallet_structure::Pallet as PalletStructure;
 use sp_core::{Get, H160};
@@ -521,8 +521,6 @@
 	/// * removes a property under the <key> if the value is `None` `(<key>, None)`.
 	///
 	/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
-	/// - `is_token_create`: Indicates that method is called during token initialization.
-	///   Allows to bypass ownership check.
 	///
 	/// All affected properties should have `mutable` permission
 	/// to be **deleted** or to be **set more than once**,
@@ -537,27 +535,38 @@
 		sender: &T::CrossAccountId,
 		token_id: TokenId,
 		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,
-		is_token_create: bool,
+		mode: SetPropertyMode,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
-		let is_token_owner = || -> Result<bool, DispatchError> {
-			let balance = collection.balance(sender.clone(), token_id);
-			let total_pieces: u128 =
-				Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);
-			if balance != total_pieces {
-				return Ok(false);
-			}
+		let mut is_token_owner =
+			pallet_common::LazyValue::new(|| -> Result<bool, DispatchError> {
+				if let SetPropertyMode::NewToken {
+					mint_target_is_sender,
+				} = mode
+				{
+					return Ok(mint_target_is_sender);
+				}
 
-			let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(
-				sender.clone(),
-				collection.id,
-				token_id,
-				None,
-				nesting_budget,
-			)?;
+				let balance = collection.balance(sender.clone(), token_id);
+				let total_pieces: u128 =
+					Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);
+				if balance != total_pieces {
+					return Ok(false);
+				}
+
+				let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(
+					sender.clone(),
+					collection.id,
+					token_id,
+					None,
+					nesting_budget,
+				)?;
+
+				Ok(is_bundle_owner)
+			});
 
-			Ok(is_bundle_owner)
-		};
+		let mut is_token_exist =
+			pallet_common::LazyValue::new(|| Self::token_exists(collection, token_id));
 
 		let stored_properties = <TokenProperties<T>>::get((collection.id, token_id));
 
@@ -565,10 +574,10 @@
 			collection,
 			sender,
 			token_id,
+			&mut is_token_exist,
 			properties_updates,
-			is_token_create,
 			stored_properties,
-			is_token_owner,
+			&mut is_token_owner,
 			|properties| <TokenProperties<T>>::set((collection.id, token_id), properties),
 			erc::ERC721TokenEvent::TokenChanged {
 				token_id: token_id.into(),
@@ -577,12 +586,25 @@
 		)
 	}
 
+	pub fn next_token_id(collection: &RefungibleHandle<T>) -> Result<TokenId, DispatchError> {
+		let next_token_id = <TokensMinted<T>>::get(collection.id)
+			.checked_add(1)
+			.ok_or(<CommonError<T>>::CollectionTokenLimitExceeded)?;
+
+		ensure!(
+			collection.limits.token_limit() >= next_token_id,
+			<CommonError<T>>::CollectionTokenLimitExceeded
+		);
+
+		Ok(TokenId(next_token_id))
+	}
+
 	pub fn set_token_properties(
 		collection: &RefungibleHandle<T>,
 		sender: &T::CrossAccountId,
 		token_id: TokenId,
 		properties: impl Iterator<Item = Property>,
-		is_token_create: bool,
+		mode: SetPropertyMode,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
 		Self::modify_token_properties(
@@ -590,7 +612,7 @@
 			sender,
 			token_id,
 			properties.map(|p| (p.key, Some(p.value))),
-			is_token_create,
+			mode,
 			nesting_budget,
 		)
 	}
@@ -602,14 +624,12 @@
 		property: Property,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
-		let is_token_create = false;
-
 		Self::set_token_properties(
 			collection,
 			sender,
 			token_id,
 			[property].into_iter(),
-			is_token_create,
+			SetPropertyMode::ExistingToken,
 			nesting_budget,
 		)
 	}
@@ -621,14 +641,12 @@
 		property_keys: impl Iterator<Item = PropertyKey>,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
-		let is_token_create = false;
-
 		Self::modify_token_properties(
 			collection,
 			sender,
 			token_id,
 			property_keys.into_iter().map(|key| (key, None)),
-			is_token_create,
+			SetPropertyMode::ExistingToken,
 			nesting_budget,
 		)
 	}
@@ -914,10 +932,14 @@
 				let token_id = first_token_id + i as u32 + 1;
 				<TotalSupply<T>>::insert((collection.id, token_id), totals[i]);
 
+				let mut mint_target_is_sender = true;
 				for (user, amount) in data.users.iter() {
 					if *amount == 0 {
 						continue;
 					}
+
+					mint_target_is_sender = mint_target_is_sender && sender.conv_eq(user);
+
 					<Balance<T>>::insert((collection.id, token_id, &user), amount);
 					<Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);
 					<PalletStructure<T>>::nest_if_sent_to_token_unchecked(
@@ -932,7 +954,9 @@
 					sender,
 					TokenId(token_id),
 					data.properties.clone().into_iter(),
-					true,
+					SetPropertyMode::NewToken {
+						mint_target_is_sender,
+					},
 					nesting_budget,
 				) {
 					return TransactionOutcome::Rollback(Err(e));
modifiedruntime/common/ethereum/sponsoring.rsdiffbeforeafterboth
--- a/runtime/common/ethereum/sponsoring.rs
+++ b/runtime/common/ethereum/sponsoring.rs
@@ -22,7 +22,7 @@
 use pallet_evm::account::CrossAccountId;
 use pallet_evm_transaction_payment::CallContext;
 use pallet_nonfungible::{
-	Config as NonfungibleConfig,
+	Config as NonfungibleConfig, Pallet as NonfungiblePallet, NonfungibleHandle,
 	erc::{
 		UniqueNFTCall, ERC721UniqueExtensionsCall, ERC721UniqueMintableCall, ERC721Call,
 		TokenPropertiesCall,
@@ -56,6 +56,8 @@
 pub struct UniqueEthSponsorshipHandler<T: UniqueConfig>(PhantomData<*const T>);
 impl<T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig>
 	SponsorshipHandler<T::CrossAccountId, CallContext> for UniqueEthSponsorshipHandler<T>
+where
+	T::AccountId: From<[u8; 32]>,
 {
 	fn get_sponsor(
 		who: &T::CrossAccountId,
@@ -67,29 +69,71 @@
 			let (method_id, mut reader) = AbiReader::new_call(&call_context.input).ok()?;
 			Some(T::CrossAccountId::from_sub(match &collection.mode {
 				CollectionMode::NFT => {
+					let collection = NonfungibleHandle::cast(collection);
 					let call = <UniqueNFTCall<T>>::parse(method_id, &mut reader).ok()??;
 					match call {
-						UniqueNFTCall::TokenProperties(TokenPropertiesCall::SetProperty {
-							token_id,
-							key,
-							value,
-							..
-						}) => {
-							let token_id: TokenId = token_id.try_into().ok()?;
-							withdraw_set_token_property::<T>(
-								&collection,
-								who,
-								&token_id,
-								key.len() + value.len(),
-							)
-							.map(|()| sponsor)
-						}
-						UniqueNFTCall::ERC721UniqueExtensions(
-							ERC721UniqueExtensionsCall::Transfer { token_id, .. },
-						) => {
-							let token_id: TokenId = token_id.try_into().ok()?;
-							withdraw_transfer::<T>(&collection, who, &token_id).map(|()| sponsor)
-						}
+						UniqueNFTCall::TokenProperties(call) => match call {
+							TokenPropertiesCall::SetProperty {
+								token_id,
+								key,
+								value,
+								..
+							} => {
+								let token_id: TokenId = token_id.try_into().ok()?;
+								withdraw_set_existing_token_property::<T>(
+									&collection,
+									who,
+									&token_id,
+									key.len() + value.len(),
+								)
+								.map(|()| sponsor)
+							}
+							TokenPropertiesCall::SetProperties {
+								token_id,
+								properties,
+								..
+							} => {
+								let token_id: TokenId = token_id.try_into().ok()?;
+								let data_size = properties
+									.into_iter()
+									.map(|p| p.key().len() + p.value().len())
+									.sum();
+
+								withdraw_set_existing_token_property::<T>(
+									&collection,
+									who,
+									&token_id,
+									data_size,
+								)
+								.map(|()| sponsor)
+							}
+							_ => None,
+						},
+						UniqueNFTCall::ERC721UniqueExtensions(call) => match call {
+							ERC721UniqueExtensionsCall::Transfer { token_id, .. } => {
+								let token_id: TokenId = token_id.try_into().ok()?;
+								withdraw_transfer::<T>(&collection, who, &token_id)
+									.map(|()| sponsor)
+							}
+							ERC721UniqueExtensionsCall::MintCross { properties, .. } => {
+								withdraw_create_item::<T>(
+									&collection,
+									who,
+									&CreateItemData::NFT(CreateNftData::default()),
+								)?;
+
+								let token_id =
+									<NonfungiblePallet<T>>::next_token_id(&collection).ok()?;
+								let data_size: usize = properties
+									.into_iter()
+									.map(|p| p.key().len() + p.value().len())
+									.sum();
+
+								withdraw_set_token_property::<T>(&collection, &token_id, data_size)
+									.map(|()| sponsor)
+							}
+							_ => None,
+						},
 						UniqueNFTCall::ERC721UniqueMintable(
 							ERC721UniqueMintableCall::Mint { .. }
 							| ERC721UniqueMintableCall::MintCheckId { .. }
modifiedruntime/common/ethereum/sponsoring/refungible.rsdiffbeforeafterboth
--- a/runtime/common/ethereum/sponsoring/refungible.rs
+++ b/runtime/common/ethereum/sponsoring/refungible.rs
@@ -94,7 +94,12 @@
 			..
 		} => {
 			let token_id = TokenId::try_from(token_id).ok()?;
-			withdraw_set_token_property::<T>(&collection, who, &token_id, key.len() + value.len())
+			withdraw_set_existing_token_property::<T>(
+				&collection,
+				who,
+				&token_id,
+				key.len() + value.len(),
+			)
 		}
 	}
 }
modifiedruntime/common/sponsoring.rsdiffbeforeafterboth
--- a/runtime/common/sponsoring.rs
+++ b/runtime/common/sponsoring.rs
@@ -39,7 +39,7 @@
 impl<T> Config for T where T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {}
 
 // TODO: permission check?
-pub fn withdraw_set_token_property<T: Config>(
+pub fn withdraw_set_existing_token_property<T: Config>(
 	collection: &CollectionHandle<T>,
 	who: &T::CrossAccountId,
 	item_id: &TokenId,
@@ -64,6 +64,17 @@
 		}
 	}
 
+	withdraw_set_token_property(collection, item_id, data_size)
+}
+
+pub fn withdraw_set_token_property<T: Config>(
+	collection: &CollectionHandle<T>,
+	item_id: &TokenId,
+	data_size: usize,
+) -> Option<()> {
+	if data_size == 0 {
+		return Some(());
+	}
 	if data_size > collection.limits.sponsored_data_size() as usize {
 		return None;
 	}
@@ -173,7 +184,6 @@
 			return None;
 		}
 	}
-
 	CreateItemBasket::<T>::insert((collection.id, who.as_sub()), block_number);
 
 	Some(())
@@ -237,7 +247,7 @@
 				..
 			} => {
 				let (sponsor, collection) = load::<T>(*collection_id)?;
-				withdraw_set_token_property(
+				withdraw_set_existing_token_property(
 					&collection,
 					&T::CrossAccountId::from_sub(who.clone()),
 					token_id,
modifiedruntime/tests/Cargo.tomldiffbeforeafterboth
--- a/runtime/tests/Cargo.toml
+++ b/runtime/tests/Cargo.toml
@@ -5,6 +5,7 @@
 
 [features]
 default = ['refungible']
+tests = ['pallet-common/tests']
 
 refungible = []
 
modifiedruntime/tests/src/tests.rsdiffbeforeafterboth
--- a/runtime/tests/src/tests.rs
+++ b/runtime/tests/src/tests.rs
@@ -1737,6 +1737,11 @@
 		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));
 
 		let origin1 = RuntimeOrigin::signed(1);
+		assert_ok!(Unique::add_collection_admin(
+			origin1.clone(),
+			collection_id,
+			account(1)
+		));
 
 		let data = default_nft_data();
 		create_test_item(collection_id, &data.into());
@@ -2610,3 +2615,67 @@
 		));
 	});
 }
+
+mod check_token_permissions {
+	use super::*;
+	use frame_support::once_cell::sync::Lazy;
+	use pallet_common::LazyValue;
+	use sp_runtime::DispatchError;
+
+	fn test<FTE: FnOnce() -> bool>(
+		i: usize,
+		test_case: &pallet_common::tests::TestCase,
+		check_token_existence: &mut LazyValue<bool, FTE>,
+	) {
+		let collection_admin = test_case.collection_admin;
+		let mut is_collection_admin = LazyValue::new(|| test_case.is_collection_admin);
+		let token_owner = test_case.token_owner;
+		let mut is_token_owner = LazyValue::new(|| Ok(test_case.is_token_owner));
+		let is_no_permission = test_case.no_permission;
+
+		let result = pallet_common::tests::check_token_permissions::<Test, _, _, FTE>(
+			collection_admin,
+			token_owner,
+			&mut is_collection_admin,
+			&mut is_token_owner,
+			check_token_existence,
+		);
+
+		if is_no_permission {
+			assert!(
+				result.is_err(),
+				"{i}: {test_case:?}, token_exist: {}",
+				check_token_existence.value()
+			);
+			assert_err!(result, pallet_common::Error::<Test>::NoPermission,);
+		} else if check_token_existence.has_value() && !check_token_existence.value() {
+			assert!(
+				result.is_err(),
+				"{i}: {test_case:?}, token_exist: {}",
+				check_token_existence.value()
+			);
+			assert_err!(result, pallet_common::Error::<Test>::TokenNotFound,);
+		}
+	}
+
+	#[test]
+	fn no_permission_only() {
+		new_test_ext().execute_with(|| {
+			let mut check_token_existence = LazyValue::new(|| true);
+			for (i, row) in pallet_common::tests::table.iter().enumerate() {
+				test(i, row, &mut check_token_existence);
+			}
+		});
+	}
+
+	#[test]
+	fn no_permission_and_token_not_found() {
+		new_test_ext().execute_with(|| {
+			for (i, row) in pallet_common::tests::table.iter().enumerate() {
+				// This is inside the loop to keep track of whether the lambda was called
+				let mut check_token_existence = LazyValue::new(|| false);
+				test(i, row, &mut check_token_existence);
+			}
+		});
+	}
+}
modifiedtests/src/createMultipleItemsEx.test.tsdiffbeforeafterboth
--- a/tests/src/createMultipleItemsEx.test.ts
+++ b/tests/src/createMultipleItemsEx.test.ts
@@ -195,7 +195,7 @@
       description: 'descr',
       tokenPrefix: 'COL',
       tokenPropertyPermissions: [
-        {key: 'k', permission: {tokenOwner: true, mutable: false, collectionAdmin: false}},
+        {key: 'k', permission: {tokenOwner: false, mutable: false, collectionAdmin: true}},
       ],
     });
 
modifiedtests/src/eth/collectionSponsoring.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collectionSponsoring.test.ts
+++ b/tests/src/eth/collectionSponsoring.test.ts
@@ -17,6 +17,7 @@
 import {IKeyringPair} from '@polkadot/types/types';
 import {Pallets, requirePalletsOrSkip, usingPlaygrounds} from '../util/index';
 import {itEth, expect} from './util';
+import {CollectionLimitField, TokenPermissionField} from './util/playgrounds/types';
 
 describe('evm nft collection sponsoring', () => {
   let donor: IKeyringPair;
@@ -138,8 +139,7 @@
       expect(sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true));
 
       // Create user with no balance:
-      const user = helper.eth.createAccount();
-      const userCross = helper.ethCrossAccount.fromAddress(user);
+      const user = helper.ethCrossAccount.createAccount();
       const nextTokenId = await collectionEvm.methods.nextTokenId().call();
       expect(nextTokenId).to.be.equal('1');
 
@@ -149,20 +149,29 @@
       expect(oldPermissions.access).to.be.equal('Normal');
 
       await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});
-      await collectionEvm.methods.addToCollectionAllowListCross(userCross).send({from: owner});
+      await collectionEvm.methods.addToCollectionAllowListCross(user).send({from: owner});
       await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});
+      await collectionEvm.methods.setCollectionLimit({field: CollectionLimitField.SponsoredDataRateLimit, value: {status: true, value: 30}}).send();
 
       const newPermissions = (await collectionSub.getData())!.raw.permissions;
       expect(newPermissions.mintMode).to.be.true;
       expect(newPermissions.access).to.be.equal('AllowList');
 
+      // Set token permissions
+      await collectionEvm.methods.setTokenPropertyPermissions([
+        ['key', [
+          [TokenPermissionField.TokenOwner, true],
+        ],
+        ],
+      ]).send({from: owner});
+
       const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
       const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));
-      const userBalanceBefore =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));
+      const userBalanceBefore =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));
 
       // User can mint token without balance:
       {
-        const result = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});
+        const result = await collectionEvm.methods.mintCross(user, [{key: 'key', value: Buffer.from('Value')}]).send({from: user.eth});
         const event = helper.eth.normalizeEvents(result.events)
           .find(event => event.event === 'Transfer');
 
@@ -171,22 +180,102 @@
           event: 'Transfer',
           args: {
             from: '0x0000000000000000000000000000000000000000',
-            to: user,
+            to: user.eth,
             tokenId: '1',
           },
         });
 
+        // await collectionEvm.methods.setProperties(1, [{key: 'key', value: Buffer.from('Value1')}]).send({from: user.eth});
+
         const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
         const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));
-        const userBalanceAfter =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));
+        const userBalanceAfter =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));
 
-        expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+        expect(await collectionEvm.methods.properties(nextTokenId, []).call())
+          .to.be.like([
+            [
+              'key',
+              '0x' + Buffer.from('Value').toString('hex'),
+            ],
+          ]);
         expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);
         expect(userBalanceAfter).to.be.eq(userBalanceBefore);
         expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;
       }
     }));
 
+  itEth('Can sponsor [set token properties] via access list', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const sponsorEth = await helper.eth.createAccountWithBalance(donor);
+    const sponsorCrossEth = helper.ethCrossAccount.fromAddress(sponsorEth);
+
+    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Sponsor collection', '1', '1', '');
+    const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, false);
+
+    // Set collection sponsor:
+    await collectionEvm.methods.setCollectionSponsorCross(sponsorCrossEth).send({from: owner});
+
+    // Sponsor can confirm sponsorship:
+    await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth});
+
+    // Create user with no balance:
+    const user = helper.ethCrossAccount.createAccount();
+    const nextTokenId = await collectionEvm.methods.nextTokenId().call();
+    expect(nextTokenId).to.be.equal('1');
+
+    // Set collection permissions:
+    await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});
+    await collectionEvm.methods.addToCollectionAllowListCross(user).send({from: owner});
+    await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});
+    await collectionEvm.methods.setCollectionLimit({field: CollectionLimitField.SponsoredDataRateLimit, value: {status: true, value: 30}}).send();
+
+    // Set token permissions
+    await collectionEvm.methods.setTokenPropertyPermissions([
+      ['key', [
+        [TokenPermissionField.TokenOwner, true],
+      ],
+      ],
+    ]).send({from: owner});
+
+    const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
+    const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));
+    const userBalanceBefore =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));
+
+    // User can mint token without balance:
+    {
+      const result = await collectionEvm.methods.mintCross(user, []).send({from: user.eth});
+      const event = helper.eth.normalizeEvents(result.events)
+        .find(event => event.event === 'Transfer');
+
+      expect(event).to.be.deep.equal({
+        address: collectionAddress,
+        event: 'Transfer',
+        args: {
+          from: '0x0000000000000000000000000000000000000000',
+          to: user.eth,
+          tokenId: '1',
+        },
+      });
+
+      await collectionEvm.methods.setProperties(1, [{key: 'key', value: Buffer.from('Value')}]).send({from: user.eth});
+
+      const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
+      const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));
+      const userBalanceAfter =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));
+
+      expect(await collectionEvm.methods.properties(nextTokenId, []).call())
+        .to.be.like([
+          [
+            'key',
+            '0x' + Buffer.from('Value').toString('hex'),
+          ],
+        ]);
+      expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);
+      expect(userBalanceAfter).to.be.eq(userBalanceBefore);
+      expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;
+    }
+  });
+
   // TODO: Temprorary off. Need refactor
   // itWeb3('Sponsoring collection from substrate address via access list', async ({api, web3, privateKeyWrapper}) => {
   //   const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
@@ -456,6 +545,15 @@
       expect(newPermissions.mintMode).to.be.true;
       expect(newPermissions.access).to.be.equal('AllowList');
 
+      // Set token permissions
+      await collectionEvm.methods.setTokenPropertyPermissions([
+        ['URI', [
+          [TokenPermissionField.TokenOwner, true],
+          [TokenPermissionField.CollectionAdmin, true],
+        ],
+        ],
+      ]).send({from: owner});
+
       const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
       const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));
       const userBalanceBefore =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));
@@ -623,6 +721,15 @@
     await collectionEvm.methods.addToCollectionAllowListCross(userCross).send({from: owner});
     await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});
 
+    // Set token permissions
+    await collectionEvm.methods.setTokenPropertyPermissions([
+      ['URI', [
+        [TokenPermissionField.TokenOwner, true],
+        [TokenPermissionField.CollectionAdmin, true],
+      ],
+      ],
+    ]).send({from: owner});
+
     const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
     const sponsorBalanceBefore = await helper.balance.getSubstrate(sponsor.address);
     const userBalanceBefore =  await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));
modifiedtests/src/eth/tokenProperties.test.tsdiffbeforeafterboth
--- a/tests/src/eth/tokenProperties.test.ts
+++ b/tests/src/eth/tokenProperties.test.ts
@@ -553,6 +553,63 @@
         ]).call({from: owner})).to.be.rejectedWith('NoPermission');
       }
     }));
+
+  [
+    {mode: 'nft' as const, requiredPallets: []},
+    {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+  ].map(testCase =>
+    itEth.ifWithPallets(`[${testCase.mode}] Can't be multiple set/read for non-existent token`, testCase.requiredPallets, async({helper}) => {
+      const caller = await helper.eth.createAccountWithBalance(donor);
+
+      const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });
+      const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.key, permission: {tokenOwner: true,
+        collectionAdmin: true,
+        mutable: true}}; });
+
+      const collection = await helper[testCase.mode].mintCollection(alice, {
+        tokenPrefix: 'ethp',
+        tokenPropertyPermissions: permissions,
+      }) as UniqueNFTCollection | UniqueRFTCollection;
+
+      await collection.addAdmin(alice, {Ethereum: caller});
+
+      const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+      const contract = await helper.ethNativeContract.collection(address, testCase.mode, caller);
+
+      await expect(contract.methods.setProperties(1, properties).call({from: caller})).to.be.rejectedWith('TokenNotFound');
+    }));
+
+  [
+    {mode: 'nft' as const, requiredPallets: []},
+    {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+  ].map(testCase =>
+    itEth.ifWithPallets(`[${testCase.mode}] Can't be deleted for non-existent token`, testCase.requiredPallets, async({helper}) => {
+      const caller = await helper.eth.createAccountWithBalance(donor);
+      const collection = await helper[testCase.mode].mintCollection(alice, {
+        tokenPropertyPermissions: [{
+          key: 'testKey',
+          permission: {
+            mutable: true,
+            collectionAdmin: true,
+          },
+        },
+        {
+          key: 'testKey_1',
+          permission: {
+            mutable: true,
+            collectionAdmin: true,
+          },
+        }],
+      });
+
+
+      await collection.addAdmin(alice, {Ethereum: caller});
+
+      const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+      const contract = await helper.ethNativeContract.collection(address, testCase.mode, caller);
+
+      await expect(contract.methods.deleteProperties(1, ['testKey', 'testKey_1']).call({from: caller})).to.be.rejectedWith('TokenNotFound');
+    }));
 });
 
 
modifiedtests/src/getPropertiesRpc.test.tsdiffbeforeafterboth
--- a/tests/src/getPropertiesRpc.test.ts
+++ b/tests/src/getPropertiesRpc.test.ts
@@ -120,3 +120,31 @@
     expect(propPermissions).to.be.deep.equal(tokenPropPermissions);
   });
 });
+
+[
+  {mode: 'nft' as const},
+  {mode: 'rft' as const},
+].map(testCase =>
+  describe('negative properties', () => {
+    let alice: IKeyringPair;
+
+    before(async () => {
+      await usingPlaygrounds(async (_, privateKey) => {
+        alice = await privateKey({url: import.meta.url});
+      });
+    });
+
+    itSub(`[${testCase.mode}] set token property for non-existent token`, async ({helper}) => {
+      const collection = await helper[testCase.mode].mintCollection(alice);
+      await collection.setTokenPropertyPermissions(alice, [{key: 'key', permission: {mutable: true, tokenOwner: true, collectionAdmin: true}}]);
+      await expect(collection.setTokenProperties(alice, 1, [{key: 'key', value: 'value'}])).to.be.rejectedWith('common.TokenNotFound');
+      expect(await collection.getTokenProperties(1, ['key'])).to.be.empty;
+    });
+
+    itSub(`[${testCase.mode}] delete token property for non-existent token`, async ({helper}) => {
+      const collection = await helper[testCase.mode].mintCollection(alice);
+      await collection.setTokenPropertyPermissions(alice, [{key: 'key', permission: {mutable: true, tokenOwner: true, collectionAdmin: true}}]);
+      await expect(collection.deleteTokenProperties(alice, 1, ['key'])).to.be.rejectedWith('common.TokenNotFound');
+      expect(await collection.getTokenProperties(1, ['key'])).to.be.empty;
+    });
+  }));
\ No newline at end of file
modifiedtests/src/nesting/tokenProperties.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/tokenProperties.test.ts
+++ b/tests/src/nesting/tokenProperties.test.ts
@@ -448,6 +448,29 @@
       expectedConsumedSpaceDiff = sizeOfProperty(biggerProp) - sizeOfProperty(smallerProp);
       expect(consumedSpace).to.be.equal(sizeOfProperty(biggerProp) - expectedConsumedSpaceDiff);
     }));
+
+  itSub('Set sponsored properties', async({helper}) => {
+    const collection = await helper.nft.mintCollection(alice, {tokenPropertyPermissions: [{key: 'k', permission: {tokenOwner: true}}]});
+
+    await collection.setSponsor(alice, alice.address);
+    await collection.confirmSponsorship(alice);
+    await collection.setPermissions(alice, {access: 'AllowList', mintMode: true});
+    await collection.addToAllowList(alice, {Substrate: bob.address});
+    await collection.setLimits(alice, {sponsoredDataRateLimit: {blocks: 30}});
+
+    const token = await collection.mintToken(alice, {Substrate: bob.address});
+
+    const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address);
+    const bobBalanceBefore = await helper.balance.getSubstrate(bob.address);
+
+    await token.setProperties(bob, [{key: 'k', value: 'val'}]);
+
+    const aliceBalanceAfter = await helper.balance.getSubstrate(alice.address);
+    const bobBalanceAfter = await helper.balance.getSubstrate(bob.address);
+
+    expect(bobBalanceAfter).to.be.equal(bobBalanceBefore);
+    expect(aliceBalanceBefore > aliceBalanceAfter).to.be.true;
+  });
 });
 
 describe('Negative Integration Test: Token Properties', () => {
@@ -475,6 +498,27 @@
     });
   });
 
+  [
+    {mode: 'nft' as const, requiredPallets: [Pallets.NFT]},
+    {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+  ].map(testCase =>
+    itSub.ifWithPallets(`Forbids adding/deleting properties of a token if token doesn't exist (${testCase.mode.toLocaleUpperCase})`, testCase.requiredPallets, async({helper}) => {
+      const collection = await helper[testCase.mode].mintCollection(alice, {
+        tokenPropertyPermissions: constitution.slice(0, 1).map(({permission}) => ({key: '1', permission})),
+      });
+      const nonExistentToken = collection.getTokenObject(1);
+
+      await expect(
+        nonExistentToken.setProperties(alice, [{key: '1', value: 'Serotonin increase'}]),
+        'on expecting failure whilst adding a property by alice',
+      ).to.be.rejectedWith(/common\.TokenNotFound/);
+
+      await expect(
+        nonExistentToken.deleteProperties(alice, ['1']),
+        'on expecting failure whilst deleting a property by alice',
+      ).to.be.rejectedWith(/common\.TokenNotFound/);
+    }));
+
   async function mintCollectionWithAllPermissionsAndToken(helper: UniqueHelper, mode: 'NFT' | 'RFT'): Promise<[UniqueNFToken | UniqueRFToken, bigint]> {
     const collection = await (mode == 'NFT' ? helper.nft : helper.rft).mintCollection(alice, {
       tokenPropertyPermissions: constitution.map(({permission}, i) => ({key: `${i+1}`, permission})),