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

difftreelog

CORE-386 Add methodt to evm

Trubnikov Sergey2022-05-31parent: #6afa4d8.patch.diff
in: master

11 files changed

modifiedpallets/common/src/erc.rsdiffbeforeafterboth
--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -21,8 +21,9 @@
 };
 pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};
 use pallet_evm_coder_substrate::dispatch_to_evm;
+use sp_core::{H160, U256, H256};
 use sp_std::vec::Vec;
-use up_data_structs::{Property, SponsoringRateLimit};
+use up_data_structs::{Property, SponsoringRateLimit, NestingRule, OwnerRestrictedSet};
 use alloc::format;
 
 use crate::{Pallet, CollectionHandle, Config, CollectionProperties};
@@ -46,7 +47,10 @@
 }
 
 #[solidity_interface(name = "Collection")]
-impl<T: Config> CollectionHandle<T> {
+impl<T: Config> CollectionHandle<T> 
+// where 
+// 	T::AccountId: From<H256>
+{
 	fn set_collection_property(&mut self, caller: caller, key: string, value: bytes) -> Result<()> {
 		let caller = T::CrossAccountId::from_eth(caller);
 		let key = <Vec<u8>>::from(key)
@@ -165,6 +169,89 @@
 	fn contract_address(&self, _caller: caller) -> Result<address> {
 		Ok(crate::eth::collection_id_to_address(self.id))
 	}
+
+	// fn add_admin_substrate(&self, caller: caller, new_admin: uint256) -> Result<void> {
+	// 	let mut new_admin_h256 = H256::default();
+	// 	new_admin.to_little_endian(&mut new_admin_h256.0);
+	// 	let account_id = T::AccountId::from(new_admin_h256);
+	// 	let caller = T::CrossAccountId::from_eth(caller);
+	// 	let new_admin = T::CrossAccountId::from_sub(account_id);
+	// 	<Pallet<T>>::toggle_admin(&self, &caller, &new_admin, true)
+	// 		.map_err(dispatch_to_evm::<T>)?;
+	// 	Ok(())
+	// }
+
+	// fn remove_admin_substrate(&self, caller: caller, new_admin: uint256) -> Result<void> {
+	// 	let mut new_admin_h256 = H256::default();
+	// 	new_admin.to_little_endian(&mut new_admin_h256.0);
+	// 	let account_id = T::AccountId::from(new_admin_h256);
+	// 	let caller = T::CrossAccountId::from_eth(caller);
+	// 	let new_admin = T::CrossAccountId::from_sub(account_id);
+	// 	<Pallet<T>>::toggle_admin(&self, &caller, &new_admin, false)
+	// 		.map_err(dispatch_to_evm::<T>)?;
+	// 	Ok(())
+	// }
+
+	fn add_admin(&self, caller: caller, new_admin: address) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		self.check_is_owner_or_admin(&caller)
+			.map_err(dispatch_to_evm::<T>)?;
+		let new_admin = T::CrossAccountId::from_eth(new_admin);
+		<Pallet<T>>::toggle_admin(&self, &caller, &new_admin, true)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
+	fn remove_admin(&self, caller: caller, admin: address) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		self.check_is_owner_or_admin(&caller)
+			.map_err(dispatch_to_evm::<T>)?;
+		let admin = T::CrossAccountId::from_eth(admin);
+		<Pallet<T>>::toggle_admin(&self, &caller, &admin, false)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
+	#[solidity(rename_selector = "setNesting")]
+	fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		self.check_is_owner_or_admin(&caller)
+			.map_err(dispatch_to_evm::<T>)?;
+		self.collection.permissions.nesting = Some(match enable {
+			false => NestingRule::Disabled,
+			true => NestingRule::Owner,
+		});
+		save(self);
+		Ok(())
+	}
+
+	#[solidity(rename_selector = "setNesting")]
+	fn set_nesting(&mut self, caller: caller, enable: bool, collections: Vec<address>) -> Result<void> {
+		if collections.is_empty() {
+			return Err("No addresses provided".into());
+		}
+		if collections.len() >= OwnerRestrictedSet::bound() {
+			return Err(Error::Revert(format!("Out of bound: {} >= {}", collections.len(), OwnerRestrictedSet::bound())));
+		}
+		let caller = T::CrossAccountId::from_eth(caller);
+		self.check_is_owner_or_admin(&caller)
+			.map_err(dispatch_to_evm::<T>)?;
+		self.collection.permissions.nesting = Some(match enable {
+			false => NestingRule::Disabled,
+			true => {
+				let mut bv = OwnerRestrictedSet::new();
+				for i in collections {
+					bv.try_insert(
+						crate::eth::map_eth_to_id(&i)
+							.ok_or(Error::Revert("Can't convert address into collection id".into()))?
+					).map_err(|e| Error::Revert(format!("{:?}", e)))?;
+				}
+				NestingRule::OwnerRestricted (bv)
+			}
+		});
+		save(self);
+		Ok(())
+	}
 }
 
 fn check_is_owner<T: Config>(caller: caller, collection: &CollectionHandle<T>) -> Result<()> {
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#![cfg_attr(not(feature = "std"), no_std)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};25use evm_coder::ToLog;26use frame_support::{27	dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},28	ensure,29	traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},30	weights::Pays,31	transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35	COLLECTION_NUMBER_LIMIT,36	Collection,37	RpcCollection,38	CollectionId,39	CreateItemData,40	MAX_TOKEN_PREFIX_LENGTH,41	COLLECTION_ADMINS_LIMIT,42	TokenId,43	TokenChild,44	CollectionStats,45	MAX_TOKEN_OWNERSHIP,46	CollectionMode,47	NFT_SPONSOR_TRANSFER_TIMEOUT,48	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,50	MAX_SPONSOR_TIMEOUT,51	CUSTOM_DATA_LIMIT,52	CollectionLimits,53	CreateCollectionData,54	SponsorshipState,55	CreateItemExData,56	SponsoringRateLimit,57	budget::Budget,58	PhantomType,59	Property,60	Properties,61	PropertiesPermissionMap,62	PropertyKey,63	PropertyValue,64	PropertyPermission,65	PropertiesError,66	PropertyKeyPermission,67	TokenData,68	TrySetProperty,69	PropertyScope,70	// RMRK71	RmrkCollectionInfo,72	RmrkInstanceInfo,73	RmrkResourceInfo,74	RmrkPropertyInfo,75	RmrkBaseInfo,76	RmrkPartType,77	RmrkTheme,78	RmrkNftChild,79	CollectionPermissions,80	SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97	pub id: CollectionId,98	collection: Collection<T::AccountId>,99	pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102	fn recorder(&self) -> &SubstrateRecorder<T> {103		&self.recorder104	}105	fn into_recorder(self) -> SubstrateRecorder<T> {106		self.recorder107	}108}109impl<T: Config> CollectionHandle<T> {110	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111		<CollectionById<T>>::get(id).map(|collection| Self {112			id,113			collection,114			recorder: SubstrateRecorder::new(gas_limit),115		})116	}117118	pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {119		<CollectionById<T>>::get(id).map(|collection| Self {120			id,121			collection,122			recorder,123		})124	}125126	pub fn new(id: CollectionId) -> Option<Self> {127		Self::new_with_gas_limit(id, u64::MAX)128	}129	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {130		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)131	}132	pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {133		self.recorder134			.consume_gas(T::GasWeightMapping::weight_to_gas(135				<T as frame_system::Config>::DbWeight::get()136					.read137					.saturating_mul(reads),138			))139	}140	pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {141		self.recorder142			.consume_gas(T::GasWeightMapping::weight_to_gas(143				<T as frame_system::Config>::DbWeight::get()144					.write145					.saturating_mul(writes),146			))147	}148	pub fn save(self) -> DispatchResult {149		<CollectionById<T>>::insert(self.id, self.collection);150		Ok(())151	}152153	pub fn set_sponsor(&mut self, sponsor: T::AccountId) {154		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);155	}156157	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> bool {158		if self.collection.sponsorship.pending_sponsor() != Some(sender) {159			return false;160		};161162		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());163		true164	}165}166impl<T: Config> Deref for CollectionHandle<T> {167	type Target = Collection<T::AccountId>;168169	fn deref(&self) -> &Self::Target {170		&self.collection171	}172}173174impl<T: Config> DerefMut for CollectionHandle<T> {175	fn deref_mut(&mut self) -> &mut Self::Target {176		&mut self.collection177	}178}179180impl<T: Config> CollectionHandle<T> {181	pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {182		ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);183		Ok(())184	}185	pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {186		*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))187	}188	pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {189		ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);190		Ok(())191	}192	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {193		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)194	}195	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {196		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)197	}198	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {199		ensure!(200			<Allowlist<T>>::get((self.id, user)),201			<Error<T>>::AddressNotInAllowlist202		);203		Ok(())204	}205}206207#[frame_support::pallet]208pub mod pallet {209	use super::*;210	use pallet_evm::account;211	use dispatch::CollectionDispatch;212	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};213	use frame_system::pallet_prelude::*;214	use frame_support::traits::Currency;215	use up_data_structs::{TokenId, mapping::TokenAddressMapping};216	use scale_info::TypeInfo;217	use weights::WeightInfo;218219	#[pallet::config]220	pub trait Config:221		frame_system::Config222		+ pallet_evm_coder_substrate::Config223		+ pallet_evm::Config224		+ TypeInfo225		+ account::Config226	{227		type WeightInfo: WeightInfo;228		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;229230		type Currency: Currency<Self::AccountId>;231232		#[pallet::constant]233		type CollectionCreationPrice: Get<234			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,235		>;236		type CollectionDispatch: CollectionDispatch<Self>;237238		type TreasuryAccountId: Get<Self::AccountId>;239		type ContractAddress: Get<H160>;240241		type EvmTokenAddressMapping: TokenAddressMapping<H160>;242		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;243	}244245	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);246247	#[pallet::pallet]248	#[pallet::storage_version(STORAGE_VERSION)]249	#[pallet::generate_store(pub(super) trait Store)]250	pub struct Pallet<T>(_);251252	#[pallet::extra_constants]253	impl<T: Config> Pallet<T> {254		pub fn collection_admins_limit() -> u32 {255			COLLECTION_ADMINS_LIMIT256		}257	}258259	#[pallet::event]260	#[pallet::generate_deposit(pub fn deposit_event)]261	pub enum Event<T: Config> {262		/// New collection was created263		///264		/// # Arguments265		///266		/// * collection_id: Globally unique identifier of newly created collection.267		///268		/// * mode: [CollectionMode] converted into u8.269		///270		/// * account_id: Collection owner.271		CollectionCreated(CollectionId, u8, T::AccountId),272273		/// New collection was destroyed274		///275		/// # Arguments276		///277		/// * collection_id: Globally unique identifier of collection.278		CollectionDestroyed(CollectionId),279280		/// New item was created.281		///282		/// # Arguments283		///284		/// * collection_id: Id of the collection where item was created.285		///286		/// * item_id: Id of an item. Unique within the collection.287		///288		/// * recipient: Owner of newly created item289		///290		/// * amount: Always 1 for NFT291		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),292293		/// Collection item was burned.294		///295		/// # Arguments296		///297		/// * collection_id.298		///299		/// * item_id: Identifier of burned NFT.300		///301		/// * owner: which user has destroyed its tokens302		///303		/// * amount: Always 1 for NFT304		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),305306		/// Item was transferred307		///308		/// * collection_id: Id of collection to which item is belong309		///310		/// * item_id: Id of an item311		///312		/// * sender: Original owner of item313		///314		/// * recipient: New owner of item315		///316		/// * amount: Always 1 for NFT317		Transfer(318			CollectionId,319			TokenId,320			T::CrossAccountId,321			T::CrossAccountId,322			u128,323		),324325		/// * collection_id326		///327		/// * item_id328		///329		/// * sender330		///331		/// * spender332		///333		/// * amount334		Approved(335			CollectionId,336			TokenId,337			T::CrossAccountId,338			T::CrossAccountId,339			u128,340		),341342		CollectionPropertySet(CollectionId, PropertyKey),343344		CollectionPropertyDeleted(CollectionId, PropertyKey),345346		TokenPropertySet(CollectionId, TokenId, PropertyKey),347348		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),349350		PropertyPermissionSet(CollectionId, PropertyKey),351	}352353	#[pallet::error]354	pub enum Error<T> {355		/// This collection does not exist.356		CollectionNotFound,357		/// Sender parameter and item owner must be equal.358		MustBeTokenOwner,359		/// No permission to perform action360		NoPermission,361		/// Destroying only empty collections is allowed362		CantDestroyNotEmptyCollection,363		/// Collection is not in mint mode.364		PublicMintingNotAllowed,365		/// Address is not in allow list.366		AddressNotInAllowlist,367368		/// Collection name can not be longer than 63 char.369		CollectionNameLimitExceeded,370		/// Collection description can not be longer than 255 char.371		CollectionDescriptionLimitExceeded,372		/// Token prefix can not be longer than 15 char.373		CollectionTokenPrefixLimitExceeded,374		/// Total collections bound exceeded.375		TotalCollectionsLimitExceeded,376		/// Exceeded max admin count377		CollectionAdminCountExceeded,378		/// Collection limit bounds per collection exceeded379		CollectionLimitBoundsExceeded,380		/// Tried to enable permissions which are only permitted to be disabled381		OwnerPermissionsCantBeReverted,382		/// Collection settings not allowing items transferring383		TransferNotAllowed,384		/// Account token limit exceeded per collection385		AccountTokenLimitExceeded,386		/// Collection token limit exceeded387		CollectionTokenLimitExceeded,388		/// Metadata flag frozen389		MetadataFlagFrozen,390391		/// Item not exists.392		TokenNotFound,393		/// Item balance not enough.394		TokenValueTooLow,395		/// Requested value more than approved.396		ApprovedValueTooLow,397		/// Tried to approve more than owned398		CantApproveMoreThanOwned,399400		/// Can't transfer tokens to ethereum zero address401		AddressIsZero,402		/// Target collection doesn't supports this operation403		UnsupportedOperation,404405		/// Not sufficient founds to perform action406		NotSufficientFounds,407408		/// Collection has nesting disabled409		NestingIsDisabled,410		/// Only owner may nest tokens under this collection411		OnlyOwnerAllowedToNest,412		/// Only tokens from specific collections may nest tokens under this413		SourceCollectionIsNotAllowedToNest,414415		/// Tried to store more data than allowed in collection field416		CollectionFieldSizeExceeded,417418		/// Tried to store more property data than allowed419		NoSpaceForProperty,420421		/// Tried to store more property keys than allowed422		PropertyLimitReached,423424		/// Property key is too long425		PropertyKeyIsTooLong,426427		/// Only ASCII letters, digits, and '_', '-' are allowed428		InvalidCharacterInPropertyKey,429430		/// Empty property keys are forbidden431		EmptyPropertyKey,432	}433434	#[pallet::storage]435	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;436	#[pallet::storage]437	pub type DestroyedCollectionCount<T> =438		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;439440	/// Collection info441	#[pallet::storage]442	pub type CollectionById<T> = StorageMap<443		Hasher = Blake2_128Concat,444		Key = CollectionId,445		Value = Collection<<T as frame_system::Config>::AccountId>,446		QueryKind = OptionQuery,447	>;448449	/// Collection properties450	#[pallet::storage]451	#[pallet::getter(fn collection_properties)]452	pub type CollectionProperties<T> = StorageMap<453		Hasher = Blake2_128Concat,454		Key = CollectionId,455		Value = Properties,456		QueryKind = ValueQuery,457		OnEmpty = up_data_structs::CollectionProperties,458	>;459460	#[pallet::storage]461	#[pallet::getter(fn property_permissions)]462	pub type CollectionPropertyPermissions<T> = StorageMap<463		Hasher = Blake2_128Concat,464		Key = CollectionId,465		Value = PropertiesPermissionMap,466		QueryKind = ValueQuery,467	>;468469	#[pallet::storage]470	pub type AdminAmount<T> = StorageMap<471		Hasher = Blake2_128Concat,472		Key = CollectionId,473		Value = u32,474		QueryKind = ValueQuery,475	>;476477	/// List of collection admins478	#[pallet::storage]479	pub type IsAdmin<T: Config> = StorageNMap<480		Key = (481			Key<Blake2_128Concat, CollectionId>,482			Key<Blake2_128Concat, T::CrossAccountId>,483		),484		Value = bool,485		QueryKind = ValueQuery,486	>;487488	/// Allowlisted collection users489	#[pallet::storage]490	pub type Allowlist<T: Config> = StorageNMap<491		Key = (492			Key<Blake2_128Concat, CollectionId>,493			Key<Blake2_128Concat, T::CrossAccountId>,494		),495		Value = bool,496		QueryKind = ValueQuery,497	>;498499	/// Not used by code, exists only to provide some types to metadata500	#[pallet::storage]501	pub type DummyStorageValue<T: Config> = StorageValue<502		Value = (503			CollectionStats,504			CollectionId,505			TokenId,506			TokenChild,507			PhantomType<(508				TokenData<T::CrossAccountId>,509				RpcCollection<T::AccountId>,510				// RMRK511				RmrkCollectionInfo<T::AccountId>,512				RmrkInstanceInfo<T::AccountId>,513				RmrkResourceInfo,514				RmrkPropertyInfo,515				RmrkBaseInfo<T::AccountId>,516				RmrkPartType,517				RmrkTheme,518				RmrkNftChild,519			)>,520		),521		QueryKind = OptionQuery,522	>;523524	#[pallet::hooks]525	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {526		fn on_runtime_upgrade() -> Weight {527			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {528				use up_data_structs::{CollectionVersion1, CollectionVersion2};529				<CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {530					let mut props = Vec::new();531					if !v.offchain_schema.is_empty() {532						props.push(Property {533							key: b"_old_offchainSchema".to_vec().try_into().unwrap(),534							value: v535								.offchain_schema536								.clone()537								.into_inner()538								.try_into()539								.expect("offchain schema too big"),540						});541					}542					if !v.variable_on_chain_schema.is_empty() {543						props.push(Property {544							key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),545							value: v546								.variable_on_chain_schema547								.clone()548								.into_inner()549								.try_into()550								.expect("offchain schema too big"),551						});552					}553					if !v.const_on_chain_schema.is_empty() {554						props.push(Property {555							key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),556							value: v557								.const_on_chain_schema558								.clone()559								.into_inner()560								.try_into()561								.expect("offchain schema too big"),562						});563					}564					props.push(Property {565						key: b"_old_schemaVersion".to_vec().try_into().unwrap(),566						value: match v.schema_version {567							SchemaVersion::ImageURL => b"ImageUrl".as_slice(),568							SchemaVersion::Unique => b"Unique".as_slice(),569						}570						.to_vec()571						.try_into()572						.unwrap(),573					});574					Self::set_scoped_collection_properties(575						id,576						PropertyScope::None,577						props.into_iter(),578					)579					.expect("existing data larger than properties");580					let mut new = CollectionVersion2::from(v.clone());581					new.permissions.access = Some(v.access);582					new.permissions.mint_mode = Some(v.mint_mode);583					Some(new)584				});585			}586587			0588		}589	}590}591592impl<T: Config> Pallet<T> {593	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens594	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {595		ensure!(596			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,597			<Error<T>>::AddressIsZero598		);599		Ok(())600	}601	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {602		<IsAdmin<T>>::iter_prefix((collection,))603			.map(|(a, _)| a)604			.collect()605	}606	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {607		<Allowlist<T>>::iter_prefix((collection,))608			.map(|(a, _)| a)609			.collect()610	}611	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {612		<Allowlist<T>>::get((collection, user))613	}614	pub fn collection_stats() -> CollectionStats {615		let created = <CreatedCollectionCount<T>>::get();616		let destroyed = <DestroyedCollectionCount<T>>::get();617		CollectionStats {618			created: created.0,619			destroyed: destroyed.0,620			alive: created.0 - destroyed.0,621		}622	}623624	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {625		let collection = <CollectionById<T>>::get(collection);626		if collection.is_none() {627			return None;628		}629630		let collection = collection.unwrap();631		let limits = collection.limits;632		let effective_limits = CollectionLimits {633			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),634			sponsored_data_size: Some(limits.sponsored_data_size()),635			sponsored_data_rate_limit: Some(636				limits637					.sponsored_data_rate_limit638					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),639			),640			token_limit: Some(limits.token_limit()),641			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(642				match collection.mode {643					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,644					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,645					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,646				},647			)),648			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),649			owner_can_transfer: Some(limits.owner_can_transfer()),650			owner_can_destroy: Some(limits.owner_can_destroy()),651			transfers_enabled: Some(limits.transfers_enabled()),652		};653654		Some(effective_limits)655	}656657	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {658		let Collection {659			name,660			description,661			owner,662			mode,663			token_prefix,664			sponsorship,665			limits,666			permissions,667		} = <CollectionById<T>>::get(collection)?;668669		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)670			.into_iter()671			.map(|(key, permission)| PropertyKeyPermission { key, permission })672			.collect();673674		let properties = <CollectionProperties<T>>::get(collection)675			.into_iter()676			.map(|(key, value)| Property { key, value })677			.collect();678679		let permissions = CollectionPermissions {680			access: Some(permissions.access()),681			mint_mode: Some(permissions.mint_mode()),682			nesting: Some(permissions.nesting().clone()),683		};684685		Some(RpcCollection {686			name: name.into_inner(),687			description: description.into_inner(),688			owner,689			mode,690			token_prefix: token_prefix.into_inner(),691			sponsorship,692			limits,693			permissions,694			token_property_permissions,695			properties,696		})697	}698}699700macro_rules! limit_default {701	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{702		$(703			if let Some($new) = $new.$field {704				let $old = $old.$field($($arg)?);705				let _ = $new;706				let _ = $old;707				$check708			} else {709				$new.$field = $old.$field710			}711		)*712	}};713}714macro_rules! limit_default_clone {715	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{716		$(717			if let Some($new) = $new.$field.clone() {718				let $old = $old.$field($($arg)?);719				let _ = $new;720				let _ = $old;721				$check722			} else {723				$new.$field = $old.$field.clone()724			}725		)*726	}};727}728729impl<T: Config> Pallet<T> {730	pub fn init_collection(731		owner: T::CrossAccountId,732		data: CreateCollectionData<T::AccountId>,733	) -> Result<CollectionId, DispatchError> {734		{735			ensure!(736				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,737				Error::<T>::CollectionTokenPrefixLimitExceeded738			);739		}740741		let created_count = <CreatedCollectionCount<T>>::get()742			.0743			.checked_add(1)744			.ok_or(ArithmeticError::Overflow)?;745		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;746		let id = CollectionId(created_count);747748		// bound Total number of collections749		ensure!(750			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,751			<Error<T>>::TotalCollectionsLimitExceeded752		);753754		// =========755756		let collection = Collection {757			owner: owner.as_sub().clone(),758			name: data.name,759			mode: data.mode.clone(),760			description: data.description,761			token_prefix: data.token_prefix,762			sponsorship: data763				.pending_sponsor764				.map(SponsorshipState::Unconfirmed)765				.unwrap_or_default(),766			limits: data767				.limits768				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))769				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,770			permissions: data771				.permissions772				.map(|permissions| {773					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)774				})775				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,776		};777778		let mut collection_properties = up_data_structs::CollectionProperties::get();779		collection_properties780			.try_set_from_iter(data.properties.into_iter())781			.map_err(<Error<T>>::from)?;782783		CollectionProperties::<T>::insert(id, collection_properties);784785		let mut token_props_permissions = PropertiesPermissionMap::new();786		token_props_permissions787			.try_set_from_iter(data.token_property_permissions.into_iter())788			.map_err(<Error<T>>::from)?;789790		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);791792		// Take a (non-refundable) deposit of collection creation793		{794			let mut imbalance =795				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();796			imbalance.subsume(797				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(798					&T::TreasuryAccountId::get(),799					T::CollectionCreationPrice::get(),800				),801			);802			<T as Config>::Currency::settle(803				&owner.as_sub(),804				imbalance,805				WithdrawReasons::TRANSFER,806				ExistenceRequirement::KeepAlive,807			)808			.map_err(|_| Error::<T>::NotSufficientFounds)?;809		}810811		<CreatedCollectionCount<T>>::put(created_count);812		<Pallet<T>>::deposit_event(Event::CollectionCreated(813			id,814			data.mode.id(),815			owner.as_sub().clone(),816		));817		<PalletEvm<T>>::deposit_log(818			erc::CollectionHelpersEvents::CollectionCreated {819				owner: *owner.as_eth(),820				collection_id: eth::collection_id_to_address(id),821			}822			.to_log(T::ContractAddress::get()),823		);824		<CollectionById<T>>::insert(id, collection);825		Ok(id)826	}827828	pub fn destroy_collection(829		collection: CollectionHandle<T>,830		sender: &T::CrossAccountId,831	) -> DispatchResult {832		ensure!(833			collection.limits.owner_can_destroy(),834			<Error<T>>::NoPermission,835		);836		collection.check_is_owner(sender)?;837838		let destroyed_collections = <DestroyedCollectionCount<T>>::get()839			.0840			.checked_add(1)841			.ok_or(ArithmeticError::Overflow)?;842843		// =========844845		<DestroyedCollectionCount<T>>::put(destroyed_collections);846		<CollectionById<T>>::remove(collection.id);847		<AdminAmount<T>>::remove(collection.id);848		<IsAdmin<T>>::remove_prefix((collection.id,), None);849		<Allowlist<T>>::remove_prefix((collection.id,), None);850		<CollectionProperties<T>>::remove(collection.id);851852		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));853		Ok(())854	}855856	pub fn set_collection_property(857		collection: &CollectionHandle<T>,858		sender: &T::CrossAccountId,859		property: Property,860	) -> DispatchResult {861		collection.check_is_owner_or_admin(sender)?;862863		CollectionProperties::<T>::try_mutate(collection.id, |properties| {864			let property = property.clone();865			properties.try_set(property.key, property.value)866		})867		.map_err(<Error<T>>::from)?;868869		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));870871		Ok(())872	}873874	pub fn set_scoped_collection_property(875		collection_id: CollectionId,876		scope: PropertyScope,877		property: Property,878	) -> DispatchResult {879		CollectionProperties::<T>::try_mutate(collection_id, |properties| {880			properties.try_scoped_set(scope, property.key, property.value)881		})882		.map_err(<Error<T>>::from)?;883884		Ok(())885	}886887	pub fn set_scoped_collection_properties(888		collection_id: CollectionId,889		scope: PropertyScope,890		properties: impl Iterator<Item = Property>,891	) -> DispatchResult {892		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {893			stored_properties.try_scoped_set_from_iter(scope, properties)894		})895		.map_err(<Error<T>>::from)?;896897		Ok(())898	}899900	#[transactional]901	pub fn set_collection_properties(902		collection: &CollectionHandle<T>,903		sender: &T::CrossAccountId,904		properties: Vec<Property>,905	) -> DispatchResult {906		for property in properties {907			Self::set_collection_property(collection, sender, property)?;908		}909910		Ok(())911	}912913	pub fn delete_collection_property(914		collection: &CollectionHandle<T>,915		sender: &T::CrossAccountId,916		property_key: PropertyKey,917	) -> DispatchResult {918		collection.check_is_owner_or_admin(sender)?;919920		CollectionProperties::<T>::try_mutate(collection.id, |properties| {921			properties.remove(&property_key)922		})923		.map_err(<Error<T>>::from)?;924925		Self::deposit_event(Event::CollectionPropertyDeleted(926			collection.id,927			property_key,928		));929930		Ok(())931	}932933	#[transactional]934	pub fn delete_collection_properties(935		collection: &CollectionHandle<T>,936		sender: &T::CrossAccountId,937		property_keys: Vec<PropertyKey>,938	) -> DispatchResult {939		for key in property_keys {940			Self::delete_collection_property(collection, sender, key)?;941		}942943		Ok(())944	}945946	// For migrations947	pub fn set_property_permission_unchecked(948		collection: CollectionId,949		property_permission: PropertyKeyPermission,950	) -> DispatchResult {951		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {952			permissions.try_set(property_permission.key, property_permission.permission)953		})954		.map_err(<Error<T>>::from)?;955		Ok(())956	}957958	pub fn set_property_permission(959		collection: &CollectionHandle<T>,960		sender: &T::CrossAccountId,961		property_permission: PropertyKeyPermission,962	) -> DispatchResult {963		collection.check_is_owner_or_admin(sender)?;964965		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);966		let current_permission = all_permissions.get(&property_permission.key);967		if matches![968			current_permission,969			Some(PropertyPermission { mutable: false, .. })970		] {971			return Err(<Error<T>>::NoPermission.into());972		}973974		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {975			let property_permission = property_permission.clone();976			permissions.try_set(property_permission.key, property_permission.permission)977		})978		.map_err(<Error<T>>::from)?;979980		Self::deposit_event(Event::PropertyPermissionSet(981			collection.id,982			property_permission.key,983		));984985		Ok(())986	}987988	#[transactional]989	pub fn set_property_permissions(990		collection: &CollectionHandle<T>,991		sender: &T::CrossAccountId,992		property_permissions: Vec<PropertyKeyPermission>,993	) -> DispatchResult {994		for prop_pemission in property_permissions {995			Self::set_property_permission(collection, sender, prop_pemission)?;996		}997998		Ok(())999	}10001001	pub fn get_collection_property(1002		collection_id: CollectionId,1003		key: &PropertyKey,1004	) -> Option<PropertyValue> {1005		Self::collection_properties(collection_id).get(key).cloned()1006	}10071008	pub fn bytes_keys_to_property_keys(1009		keys: Vec<Vec<u8>>,1010	) -> Result<Vec<PropertyKey>, DispatchError> {1011		keys.into_iter()1012			.map(|key| -> Result<PropertyKey, DispatchError> {1013				key.try_into()1014					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1015			})1016			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1017	}10181019	pub fn filter_collection_properties(1020		collection_id: CollectionId,1021		keys: Option<Vec<PropertyKey>>,1022	) -> Result<Vec<Property>, DispatchError> {1023		let properties = Self::collection_properties(collection_id);10241025		let properties = keys1026			.map(|keys| {1027				keys.into_iter()1028					.filter_map(|key| {1029						properties.get(&key).map(|value| Property {1030							key,1031							value: value.clone(),1032						})1033					})1034					.collect()1035			})1036			.unwrap_or_else(|| {1037				properties1038					.into_iter()1039					.map(|(key, value)| Property { key, value })1040					.collect()1041			});10421043		Ok(properties)1044	}10451046	pub fn filter_property_permissions(1047		collection_id: CollectionId,1048		keys: Option<Vec<PropertyKey>>,1049	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1050		let permissions = Self::property_permissions(collection_id);10511052		let key_permissions = keys1053			.map(|keys| {1054				keys.into_iter()1055					.filter_map(|key| {1056						permissions1057							.get(&key)1058							.map(|permission| PropertyKeyPermission {1059								key,1060								permission: permission.clone(),1061							})1062					})1063					.collect()1064			})1065			.unwrap_or_else(|| {1066				permissions1067					.into_iter()1068					.map(|(key, permission)| PropertyKeyPermission { key, permission })1069					.collect()1070			});10711072		Ok(key_permissions)1073	}10741075	pub fn toggle_allowlist(1076		collection: &CollectionHandle<T>,1077		sender: &T::CrossAccountId,1078		user: &T::CrossAccountId,1079		allowed: bool,1080	) -> DispatchResult {1081		collection.check_is_owner_or_admin(sender)?;10821083		// =========10841085		if allowed {1086			<Allowlist<T>>::insert((collection.id, user), true);1087		} else {1088			<Allowlist<T>>::remove((collection.id, user));1089		}10901091		Ok(())1092	}10931094	pub fn toggle_admin(1095		collection: &CollectionHandle<T>,1096		sender: &T::CrossAccountId,1097		user: &T::CrossAccountId,1098		admin: bool,1099	) -> DispatchResult {1100		collection.check_is_owner_or_admin(sender)?;11011102		let was_admin = <IsAdmin<T>>::get((collection.id, user));1103		if was_admin == admin {1104			return Ok(());1105		}1106		let amount = <AdminAmount<T>>::get(collection.id);11071108		if admin {1109			let amount = amount1110				.checked_add(1)1111				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1112			ensure!(1113				amount <= Self::collection_admins_limit(),1114				<Error<T>>::CollectionAdminCountExceeded,1115			);11161117			// =========11181119			<AdminAmount<T>>::insert(collection.id, amount);1120			<IsAdmin<T>>::insert((collection.id, user), true);1121		} else {1122			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1123			<IsAdmin<T>>::remove((collection.id, user));1124		}11251126		Ok(())1127	}11281129	pub fn clamp_limits(1130		mode: CollectionMode,1131		old_limit: &CollectionLimits,1132		mut new_limit: CollectionLimits,1133	) -> Result<CollectionLimits, DispatchError> {1134		limit_default!(old_limit, new_limit,1135			account_token_ownership_limit => ensure!(1136				new_limit <= MAX_TOKEN_OWNERSHIP,1137				<Error<T>>::CollectionLimitBoundsExceeded,1138			),1139			sponsored_data_size => ensure!(1140				new_limit <= CUSTOM_DATA_LIMIT,1141				<Error<T>>::CollectionLimitBoundsExceeded,1142			),11431144			sponsored_data_rate_limit => {},1145			token_limit => ensure!(1146				old_limit >= new_limit && new_limit > 0,1147				<Error<T>>::CollectionTokenLimitExceeded1148			),11491150			sponsor_transfer_timeout(match mode {1151				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1152				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1153				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1154			}) => ensure!(1155				new_limit <= MAX_SPONSOR_TIMEOUT,1156				<Error<T>>::CollectionLimitBoundsExceeded,1157			),1158			sponsor_approve_timeout => {},1159			owner_can_transfer => ensure!(1160				old_limit || !new_limit,1161				<Error<T>>::OwnerPermissionsCantBeReverted,1162			),1163			owner_can_destroy => ensure!(1164				old_limit || !new_limit,1165				<Error<T>>::OwnerPermissionsCantBeReverted,1166			),1167			transfers_enabled => {},1168		);1169		Ok(new_limit)1170	}11711172	pub fn clamp_permissions(1173		_mode: CollectionMode,1174		old_limit: &CollectionPermissions,1175		mut new_limit: CollectionPermissions,1176	) -> Result<CollectionPermissions, DispatchError> {1177		limit_default_clone!(old_limit, new_limit,1178			access => {},1179			mint_mode => {},1180			nesting => {},1181		);1182		Ok(new_limit)1183	}1184}11851186#[macro_export]1187macro_rules! unsupported {1188	() => {1189		Err(<Error<T>>::UnsupportedOperation.into())1190	};1191}11921193/// Worst cases1194pub trait CommonWeightInfo<CrossAccountId> {1195	fn create_item() -> Weight;1196	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1197	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1198	fn burn_item() -> Weight;1199	fn set_collection_properties(amount: u32) -> Weight;1200	fn delete_collection_properties(amount: u32) -> Weight;1201	fn set_token_properties(amount: u32) -> Weight;1202	fn delete_token_properties(amount: u32) -> Weight;1203	fn set_property_permissions(amount: u32) -> Weight;1204	fn transfer() -> Weight;1205	fn approve() -> Weight;1206	fn transfer_from() -> Weight;1207	fn burn_from() -> Weight;12081209	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1210	/// whole users's balance1211	///1212	/// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead1213	fn burn_recursively_self_raw() -> Weight;1214	/// Cost of iterating over `amount` children while burning, without counting child burning itself1215	///1216	/// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead1217	fn burn_recursively_breadth_raw(amount: u32) -> Weight;12181219	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1220		Self::burn_recursively_self_raw()1221			.saturating_mul(max_selfs.max(1) as u64)1222			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1223	}1224}12251226pub trait CommonCollectionOperations<T: Config> {1227	fn create_item(1228		&self,1229		sender: T::CrossAccountId,1230		to: T::CrossAccountId,1231		data: CreateItemData,1232		nesting_budget: &dyn Budget,1233	) -> DispatchResultWithPostInfo;1234	fn create_multiple_items(1235		&self,1236		sender: T::CrossAccountId,1237		to: T::CrossAccountId,1238		data: Vec<CreateItemData>,1239		nesting_budget: &dyn Budget,1240	) -> DispatchResultWithPostInfo;1241	fn create_multiple_items_ex(1242		&self,1243		sender: T::CrossAccountId,1244		data: CreateItemExData<T::CrossAccountId>,1245		nesting_budget: &dyn Budget,1246	) -> DispatchResultWithPostInfo;1247	fn burn_item(1248		&self,1249		sender: T::CrossAccountId,1250		token: TokenId,1251		amount: u128,1252	) -> DispatchResultWithPostInfo;1253	fn burn_item_recursively(1254		&self,1255		sender: T::CrossAccountId,1256		token: TokenId,1257		self_budget: &dyn Budget,1258		breadth_budget: &dyn Budget,1259	) -> DispatchResultWithPostInfo;1260	fn set_collection_properties(1261		&self,1262		sender: T::CrossAccountId,1263		properties: Vec<Property>,1264	) -> DispatchResultWithPostInfo;1265	fn delete_collection_properties(1266		&self,1267		sender: &T::CrossAccountId,1268		property_keys: Vec<PropertyKey>,1269	) -> DispatchResultWithPostInfo;1270	fn set_token_properties(1271		&self,1272		sender: T::CrossAccountId,1273		token_id: TokenId,1274		property: Vec<Property>,1275	) -> DispatchResultWithPostInfo;1276	fn delete_token_properties(1277		&self,1278		sender: T::CrossAccountId,1279		token_id: TokenId,1280		property_keys: Vec<PropertyKey>,1281	) -> DispatchResultWithPostInfo;1282	fn set_property_permissions(1283		&self,1284		sender: &T::CrossAccountId,1285		property_permissions: Vec<PropertyKeyPermission>,1286	) -> DispatchResultWithPostInfo;1287	fn transfer(1288		&self,1289		sender: T::CrossAccountId,1290		to: T::CrossAccountId,1291		token: TokenId,1292		amount: u128,1293		nesting_budget: &dyn Budget,1294	) -> DispatchResultWithPostInfo;1295	fn approve(1296		&self,1297		sender: T::CrossAccountId,1298		spender: T::CrossAccountId,1299		token: TokenId,1300		amount: u128,1301	) -> DispatchResultWithPostInfo;1302	fn transfer_from(1303		&self,1304		sender: T::CrossAccountId,1305		from: T::CrossAccountId,1306		to: T::CrossAccountId,1307		token: TokenId,1308		amount: u128,1309		nesting_budget: &dyn Budget,1310	) -> DispatchResultWithPostInfo;1311	fn burn_from(1312		&self,1313		sender: T::CrossAccountId,1314		from: T::CrossAccountId,1315		token: TokenId,1316		amount: u128,1317		nesting_budget: &dyn Budget,1318	) -> DispatchResultWithPostInfo;13191320	fn check_nesting(1321		&self,1322		sender: T::CrossAccountId,1323		from: (CollectionId, TokenId),1324		under: TokenId,1325		budget: &dyn Budget,1326	) -> DispatchResult;13271328	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13291330	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13311332	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1333	fn collection_tokens(&self) -> Vec<TokenId>;1334	fn token_exists(&self, token: TokenId) -> bool;1335	fn last_token_id(&self) -> TokenId;13361337	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1338	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1339	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1340	/// Amount of unique collection tokens1341	fn total_supply(&self) -> u32;1342	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1343	fn account_balance(&self, account: T::CrossAccountId) -> u32;1344	/// Amount of specific token account have (Applicable to fungible/refungible)1345	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1346	fn allowance(1347		&self,1348		sender: T::CrossAccountId,1349		spender: T::CrossAccountId,1350		token: TokenId,1351	) -> u128;1352}13531354// Flexible enough for implementing CommonCollectionOperations1355pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1356	let post_info = PostDispatchInfo {1357		actual_weight: Some(weight),1358		pays_fee: Pays::Yes,1359	};1360	match res {1361		Ok(()) => Ok(post_info),1362		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1363	}1364}13651366impl<T: Config> From<PropertiesError> for Error<T> {1367	fn from(error: PropertiesError) -> Self {1368		match error {1369			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1370			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1371			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1372			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1373			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1374		}1375	}1376}
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#![cfg_attr(not(feature = "std"), no_std)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};25use evm_coder::ToLog;26use frame_support::{27	dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},28	ensure,29	traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},30	weights::Pays,31	transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35	COLLECTION_NUMBER_LIMIT,36	Collection,37	RpcCollection,38	CollectionId,39	CreateItemData,40	MAX_TOKEN_PREFIX_LENGTH,41	COLLECTION_ADMINS_LIMIT,42	TokenId,43	TokenChild,44	CollectionStats,45	MAX_TOKEN_OWNERSHIP,46	CollectionMode,47	NFT_SPONSOR_TRANSFER_TIMEOUT,48	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,50	MAX_SPONSOR_TIMEOUT,51	CUSTOM_DATA_LIMIT,52	CollectionLimits,53	CreateCollectionData,54	SponsorshipState,55	CreateItemExData,56	SponsoringRateLimit,57	budget::Budget,58	PhantomType,59	Property,60	Properties,61	PropertiesPermissionMap,62	PropertyKey,63	PropertyValue,64	PropertyPermission,65	PropertiesError,66	PropertyKeyPermission,67	TokenData,68	TrySetProperty,69	PropertyScope,70	// RMRK71	RmrkCollectionInfo,72	RmrkInstanceInfo,73	RmrkResourceInfo,74	RmrkPropertyInfo,75	RmrkBaseInfo,76	RmrkPartType,77	RmrkTheme,78	RmrkNftChild,79	CollectionPermissions,80	SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97	pub id: CollectionId,98	collection: Collection<T::AccountId>,99	pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102	fn recorder(&self) -> &SubstrateRecorder<T> {103		&self.recorder104	}105	fn into_recorder(self) -> SubstrateRecorder<T> {106		self.recorder107	}108}109impl<T: Config> CollectionHandle<T> {110	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111		<CollectionById<T>>::get(id).map(|collection| Self {112			id,113			collection,114			recorder: SubstrateRecorder::new(gas_limit),115		})116	}117118	pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {119		<CollectionById<T>>::get(id).map(|collection| Self {120			id,121			collection,122			recorder,123		})124	}125126	pub fn new(id: CollectionId) -> Option<Self> {127		Self::new_with_gas_limit(id, u64::MAX)128	}129130	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {131		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)132	}133134	pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {135		self.recorder136			.consume_gas(T::GasWeightMapping::weight_to_gas(137				<T as frame_system::Config>::DbWeight::get()138					.read139					.saturating_mul(reads),140			))141	}142143	pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {144		self.recorder145			.consume_gas(T::GasWeightMapping::weight_to_gas(146				<T as frame_system::Config>::DbWeight::get()147					.write148					.saturating_mul(writes),149			))150	}151152	pub fn save(self) -> DispatchResult {153		<CollectionById<T>>::insert(self.id, self.collection);154		Ok(())155	}156157	pub fn set_sponsor(&mut self, sponsor: T::AccountId) {158		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);159	}160161	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> bool {162		if self.collection.sponsorship.pending_sponsor() != Some(sender) {163			return false;164		};165166		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());167		true168	}169}170171impl<T: Config> Deref for CollectionHandle<T> {172	type Target = Collection<T::AccountId>;173174	fn deref(&self) -> &Self::Target {175		&self.collection176	}177}178179impl<T: Config> DerefMut for CollectionHandle<T> {180	fn deref_mut(&mut self) -> &mut Self::Target {181		&mut self.collection182	}183}184185impl<T: Config> CollectionHandle<T> {186	pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {187		ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);188		Ok(())189	}190	pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {191		*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))192	}193	pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {194		ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);195		Ok(())196	}197	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {198		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)199	}200	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {201		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)202	}203	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {204		ensure!(205			<Allowlist<T>>::get((self.id, user)),206			<Error<T>>::AddressNotInAllowlist207		);208		Ok(())209	}210}211212#[frame_support::pallet]213pub mod pallet {214	use super::*;215	use pallet_evm::account;216	use dispatch::CollectionDispatch;217	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};218	use frame_system::pallet_prelude::*;219	use frame_support::traits::Currency;220	use up_data_structs::{TokenId, mapping::TokenAddressMapping};221	use scale_info::TypeInfo;222	use weights::WeightInfo;223224	#[pallet::config]225	pub trait Config:226		frame_system::Config227		+ pallet_evm_coder_substrate::Config228		+ pallet_evm::Config229		+ TypeInfo230		+ account::Config231	{232		type WeightInfo: WeightInfo;233		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;234235		type Currency: Currency<Self::AccountId>;236237		#[pallet::constant]238		type CollectionCreationPrice: Get<239			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,240		>;241		type CollectionDispatch: CollectionDispatch<Self>;242243		type TreasuryAccountId: Get<Self::AccountId>;244		type ContractAddress: Get<H160>;245246		type EvmTokenAddressMapping: TokenAddressMapping<H160>;247		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;248	}249250	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);251252	#[pallet::pallet]253	#[pallet::storage_version(STORAGE_VERSION)]254	#[pallet::generate_store(pub(super) trait Store)]255	pub struct Pallet<T>(_);256257	#[pallet::extra_constants]258	impl<T: Config> Pallet<T> {259		pub fn collection_admins_limit() -> u32 {260			COLLECTION_ADMINS_LIMIT261		}262	}263264	#[pallet::event]265	#[pallet::generate_deposit(pub fn deposit_event)]266	pub enum Event<T: Config> {267		/// New collection was created268		///269		/// # Arguments270		///271		/// * collection_id: Globally unique identifier of newly created collection.272		///273		/// * mode: [CollectionMode] converted into u8.274		///275		/// * account_id: Collection owner.276		CollectionCreated(CollectionId, u8, T::AccountId),277278		/// New collection was destroyed279		///280		/// # Arguments281		///282		/// * collection_id: Globally unique identifier of collection.283		CollectionDestroyed(CollectionId),284285		/// New item was created.286		///287		/// # Arguments288		///289		/// * collection_id: Id of the collection where item was created.290		///291		/// * item_id: Id of an item. Unique within the collection.292		///293		/// * recipient: Owner of newly created item294		///295		/// * amount: Always 1 for NFT296		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),297298		/// Collection item was burned.299		///300		/// # Arguments301		///302		/// * collection_id.303		///304		/// * item_id: Identifier of burned NFT.305		///306		/// * owner: which user has destroyed its tokens307		///308		/// * amount: Always 1 for NFT309		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),310311		/// Item was transferred312		///313		/// * collection_id: Id of collection to which item is belong314		///315		/// * item_id: Id of an item316		///317		/// * sender: Original owner of item318		///319		/// * recipient: New owner of item320		///321		/// * amount: Always 1 for NFT322		Transfer(323			CollectionId,324			TokenId,325			T::CrossAccountId,326			T::CrossAccountId,327			u128,328		),329330		/// * collection_id331		///332		/// * item_id333		///334		/// * sender335		///336		/// * spender337		///338		/// * amount339		Approved(340			CollectionId,341			TokenId,342			T::CrossAccountId,343			T::CrossAccountId,344			u128,345		),346347		CollectionPropertySet(CollectionId, PropertyKey),348349		CollectionPropertyDeleted(CollectionId, PropertyKey),350351		TokenPropertySet(CollectionId, TokenId, PropertyKey),352353		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),354355		PropertyPermissionSet(CollectionId, PropertyKey),356	}357358	#[pallet::error]359	pub enum Error<T> {360		/// This collection does not exist.361		CollectionNotFound,362		/// Sender parameter and item owner must be equal.363		MustBeTokenOwner,364		/// No permission to perform action365		NoPermission,366		/// Destroying only empty collections is allowed367		CantDestroyNotEmptyCollection,368		/// Collection is not in mint mode.369		PublicMintingNotAllowed,370		/// Address is not in allow list.371		AddressNotInAllowlist,372373		/// Collection name can not be longer than 63 char.374		CollectionNameLimitExceeded,375		/// Collection description can not be longer than 255 char.376		CollectionDescriptionLimitExceeded,377		/// Token prefix can not be longer than 15 char.378		CollectionTokenPrefixLimitExceeded,379		/// Total collections bound exceeded.380		TotalCollectionsLimitExceeded,381		/// Exceeded max admin count382		CollectionAdminCountExceeded,383		/// Collection limit bounds per collection exceeded384		CollectionLimitBoundsExceeded,385		/// Tried to enable permissions which are only permitted to be disabled386		OwnerPermissionsCantBeReverted,387		/// Collection settings not allowing items transferring388		TransferNotAllowed,389		/// Account token limit exceeded per collection390		AccountTokenLimitExceeded,391		/// Collection token limit exceeded392		CollectionTokenLimitExceeded,393		/// Metadata flag frozen394		MetadataFlagFrozen,395396		/// Item not exists.397		TokenNotFound,398		/// Item balance not enough.399		TokenValueTooLow,400		/// Requested value more than approved.401		ApprovedValueTooLow,402		/// Tried to approve more than owned403		CantApproveMoreThanOwned,404405		/// Can't transfer tokens to ethereum zero address406		AddressIsZero,407		/// Target collection doesn't supports this operation408		UnsupportedOperation,409410		/// Not sufficient founds to perform action411		NotSufficientFounds,412413		/// Collection has nesting disabled414		NestingIsDisabled,415		/// Only owner may nest tokens under this collection416		OnlyOwnerAllowedToNest,417		/// Only tokens from specific collections may nest tokens under this418		SourceCollectionIsNotAllowedToNest,419420		/// Tried to store more data than allowed in collection field421		CollectionFieldSizeExceeded,422423		/// Tried to store more property data than allowed424		NoSpaceForProperty,425426		/// Tried to store more property keys than allowed427		PropertyLimitReached,428429		/// Property key is too long430		PropertyKeyIsTooLong,431432		/// Only ASCII letters, digits, and '_', '-' are allowed433		InvalidCharacterInPropertyKey,434435		/// Empty property keys are forbidden436		EmptyPropertyKey,437	}438439	#[pallet::storage]440	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;441	#[pallet::storage]442	pub type DestroyedCollectionCount<T> =443		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;444445	/// Collection info446	#[pallet::storage]447	pub type CollectionById<T> = StorageMap<448		Hasher = Blake2_128Concat,449		Key = CollectionId,450		Value = Collection<<T as frame_system::Config>::AccountId>,451		QueryKind = OptionQuery,452	>;453454	/// Collection properties455	#[pallet::storage]456	#[pallet::getter(fn collection_properties)]457	pub type CollectionProperties<T> = StorageMap<458		Hasher = Blake2_128Concat,459		Key = CollectionId,460		Value = Properties,461		QueryKind = ValueQuery,462		OnEmpty = up_data_structs::CollectionProperties,463	>;464465	#[pallet::storage]466	#[pallet::getter(fn property_permissions)]467	pub type CollectionPropertyPermissions<T> = StorageMap<468		Hasher = Blake2_128Concat,469		Key = CollectionId,470		Value = PropertiesPermissionMap,471		QueryKind = ValueQuery,472	>;473474	#[pallet::storage]475	pub type AdminAmount<T> = StorageMap<476		Hasher = Blake2_128Concat,477		Key = CollectionId,478		Value = u32,479		QueryKind = ValueQuery,480	>;481482	/// List of collection admins483	#[pallet::storage]484	pub type IsAdmin<T: Config> = StorageNMap<485		Key = (486			Key<Blake2_128Concat, CollectionId>,487			Key<Blake2_128Concat, T::CrossAccountId>,488		),489		Value = bool,490		QueryKind = ValueQuery,491	>;492493	/// Allowlisted collection users494	#[pallet::storage]495	pub type Allowlist<T: Config> = StorageNMap<496		Key = (497			Key<Blake2_128Concat, CollectionId>,498			Key<Blake2_128Concat, T::CrossAccountId>,499		),500		Value = bool,501		QueryKind = ValueQuery,502	>;503504	/// Not used by code, exists only to provide some types to metadata505	#[pallet::storage]506	pub type DummyStorageValue<T: Config> = StorageValue<507		Value = (508			CollectionStats,509			CollectionId,510			TokenId,511			TokenChild,512			PhantomType<(513				TokenData<T::CrossAccountId>,514				RpcCollection<T::AccountId>,515				// RMRK516				RmrkCollectionInfo<T::AccountId>,517				RmrkInstanceInfo<T::AccountId>,518				RmrkResourceInfo,519				RmrkPropertyInfo,520				RmrkBaseInfo<T::AccountId>,521				RmrkPartType,522				RmrkTheme,523				RmrkNftChild,524			)>,525		),526		QueryKind = OptionQuery,527	>;528529	#[pallet::hooks]530	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {531		fn on_runtime_upgrade() -> Weight {532			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {533				use up_data_structs::{CollectionVersion1, CollectionVersion2};534				<CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {535					let mut props = Vec::new();536					if !v.offchain_schema.is_empty() {537						props.push(Property {538							key: b"_old_offchainSchema".to_vec().try_into().unwrap(),539							value: v540								.offchain_schema541								.clone()542								.into_inner()543								.try_into()544								.expect("offchain schema too big"),545						});546					}547					if !v.variable_on_chain_schema.is_empty() {548						props.push(Property {549							key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),550							value: v551								.variable_on_chain_schema552								.clone()553								.into_inner()554								.try_into()555								.expect("offchain schema too big"),556						});557					}558					if !v.const_on_chain_schema.is_empty() {559						props.push(Property {560							key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),561							value: v562								.const_on_chain_schema563								.clone()564								.into_inner()565								.try_into()566								.expect("offchain schema too big"),567						});568					}569					props.push(Property {570						key: b"_old_schemaVersion".to_vec().try_into().unwrap(),571						value: match v.schema_version {572							SchemaVersion::ImageURL => b"ImageUrl".as_slice(),573							SchemaVersion::Unique => b"Unique".as_slice(),574						}575						.to_vec()576						.try_into()577						.unwrap(),578					});579					Self::set_scoped_collection_properties(580						id,581						PropertyScope::None,582						props.into_iter(),583					)584					.expect("existing data larger than properties");585					let mut new = CollectionVersion2::from(v.clone());586					new.permissions.access = Some(v.access);587					new.permissions.mint_mode = Some(v.mint_mode);588					Some(new)589				});590			}591592			0593		}594	}595}596597impl<T: Config> Pallet<T> {598	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens599	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {600		ensure!(601			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,602			<Error<T>>::AddressIsZero603		);604		Ok(())605	}606	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {607		<IsAdmin<T>>::iter_prefix((collection,))608			.map(|(a, _)| a)609			.collect()610	}611	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {612		<Allowlist<T>>::iter_prefix((collection,))613			.map(|(a, _)| a)614			.collect()615	}616	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {617		<Allowlist<T>>::get((collection, user))618	}619	pub fn collection_stats() -> CollectionStats {620		let created = <CreatedCollectionCount<T>>::get();621		let destroyed = <DestroyedCollectionCount<T>>::get();622		CollectionStats {623			created: created.0,624			destroyed: destroyed.0,625			alive: created.0 - destroyed.0,626		}627	}628629	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {630		let collection = <CollectionById<T>>::get(collection);631		if collection.is_none() {632			return None;633		}634635		let collection = collection.unwrap();636		let limits = collection.limits;637		let effective_limits = CollectionLimits {638			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),639			sponsored_data_size: Some(limits.sponsored_data_size()),640			sponsored_data_rate_limit: Some(641				limits642					.sponsored_data_rate_limit643					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),644			),645			token_limit: Some(limits.token_limit()),646			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(647				match collection.mode {648					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,649					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,650					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,651				},652			)),653			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),654			owner_can_transfer: Some(limits.owner_can_transfer()),655			owner_can_destroy: Some(limits.owner_can_destroy()),656			transfers_enabled: Some(limits.transfers_enabled()),657		};658659		Some(effective_limits)660	}661662	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {663		let Collection {664			name,665			description,666			owner,667			mode,668			token_prefix,669			sponsorship,670			limits,671			permissions,672		} = <CollectionById<T>>::get(collection)?;673674		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)675			.into_iter()676			.map(|(key, permission)| PropertyKeyPermission { key, permission })677			.collect();678679		let properties = <CollectionProperties<T>>::get(collection)680			.into_iter()681			.map(|(key, value)| Property { key, value })682			.collect();683684		let permissions = CollectionPermissions {685			access: Some(permissions.access()),686			mint_mode: Some(permissions.mint_mode()),687			nesting: Some(permissions.nesting().clone()),688		};689690		Some(RpcCollection {691			name: name.into_inner(),692			description: description.into_inner(),693			owner,694			mode,695			token_prefix: token_prefix.into_inner(),696			sponsorship,697			limits,698			permissions,699			token_property_permissions,700			properties,701		})702	}703}704705macro_rules! limit_default {706	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{707		$(708			if let Some($new) = $new.$field {709				let $old = $old.$field($($arg)?);710				let _ = $new;711				let _ = $old;712				$check713			} else {714				$new.$field = $old.$field715			}716		)*717	}};718}719macro_rules! limit_default_clone {720	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{721		$(722			if let Some($new) = $new.$field.clone() {723				let $old = $old.$field($($arg)?);724				let _ = $new;725				let _ = $old;726				$check727			} else {728				$new.$field = $old.$field.clone()729			}730		)*731	}};732}733734impl<T: Config> Pallet<T> {735	pub fn init_collection(736		owner: T::CrossAccountId,737		data: CreateCollectionData<T::AccountId>,738	) -> Result<CollectionId, DispatchError> {739		{740			ensure!(741				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,742				Error::<T>::CollectionTokenPrefixLimitExceeded743			);744		}745746		let created_count = <CreatedCollectionCount<T>>::get()747			.0748			.checked_add(1)749			.ok_or(ArithmeticError::Overflow)?;750		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;751		let id = CollectionId(created_count);752753		// bound Total number of collections754		ensure!(755			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,756			<Error<T>>::TotalCollectionsLimitExceeded757		);758759		// =========760761		let collection = Collection {762			owner: owner.as_sub().clone(),763			name: data.name,764			mode: data.mode.clone(),765			description: data.description,766			token_prefix: data.token_prefix,767			sponsorship: data768				.pending_sponsor769				.map(SponsorshipState::Unconfirmed)770				.unwrap_or_default(),771			limits: data772				.limits773				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))774				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,775			permissions: data776				.permissions777				.map(|permissions| {778					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)779				})780				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,781		};782783		let mut collection_properties = up_data_structs::CollectionProperties::get();784		collection_properties785			.try_set_from_iter(data.properties.into_iter())786			.map_err(<Error<T>>::from)?;787788		CollectionProperties::<T>::insert(id, collection_properties);789790		let mut token_props_permissions = PropertiesPermissionMap::new();791		token_props_permissions792			.try_set_from_iter(data.token_property_permissions.into_iter())793			.map_err(<Error<T>>::from)?;794795		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);796797		// Take a (non-refundable) deposit of collection creation798		{799			let mut imbalance =800				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();801			imbalance.subsume(802				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(803					&T::TreasuryAccountId::get(),804					T::CollectionCreationPrice::get(),805				),806			);807			<T as Config>::Currency::settle(808				&owner.as_sub(),809				imbalance,810				WithdrawReasons::TRANSFER,811				ExistenceRequirement::KeepAlive,812			)813			.map_err(|_| Error::<T>::NotSufficientFounds)?;814		}815816		<CreatedCollectionCount<T>>::put(created_count);817		<Pallet<T>>::deposit_event(Event::CollectionCreated(818			id,819			data.mode.id(),820			owner.as_sub().clone(),821		));822		<PalletEvm<T>>::deposit_log(823			erc::CollectionHelpersEvents::CollectionCreated {824				owner: *owner.as_eth(),825				collection_id: eth::collection_id_to_address(id),826			}827			.to_log(T::ContractAddress::get()),828		);829		<CollectionById<T>>::insert(id, collection);830		Ok(id)831	}832833	pub fn destroy_collection(834		collection: CollectionHandle<T>,835		sender: &T::CrossAccountId,836	) -> DispatchResult {837		ensure!(838			collection.limits.owner_can_destroy(),839			<Error<T>>::NoPermission,840		);841		collection.check_is_owner(sender)?;842843		let destroyed_collections = <DestroyedCollectionCount<T>>::get()844			.0845			.checked_add(1)846			.ok_or(ArithmeticError::Overflow)?;847848		// =========849850		<DestroyedCollectionCount<T>>::put(destroyed_collections);851		<CollectionById<T>>::remove(collection.id);852		<AdminAmount<T>>::remove(collection.id);853		<IsAdmin<T>>::remove_prefix((collection.id,), None);854		<Allowlist<T>>::remove_prefix((collection.id,), None);855		<CollectionProperties<T>>::remove(collection.id);856857		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));858		Ok(())859	}860861	pub fn set_collection_property(862		collection: &CollectionHandle<T>,863		sender: &T::CrossAccountId,864		property: Property,865	) -> DispatchResult {866		collection.check_is_owner_or_admin(sender)?;867868		CollectionProperties::<T>::try_mutate(collection.id, |properties| {869			let property = property.clone();870			properties.try_set(property.key, property.value)871		})872		.map_err(<Error<T>>::from)?;873874		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));875876		Ok(())877	}878879	pub fn set_scoped_collection_property(880		collection_id: CollectionId,881		scope: PropertyScope,882		property: Property,883	) -> DispatchResult {884		CollectionProperties::<T>::try_mutate(collection_id, |properties| {885			properties.try_scoped_set(scope, property.key, property.value)886		})887		.map_err(<Error<T>>::from)?;888889		Ok(())890	}891892	pub fn set_scoped_collection_properties(893		collection_id: CollectionId,894		scope: PropertyScope,895		properties: impl Iterator<Item = Property>,896	) -> DispatchResult {897		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {898			stored_properties.try_scoped_set_from_iter(scope, properties)899		})900		.map_err(<Error<T>>::from)?;901902		Ok(())903	}904905	#[transactional]906	pub fn set_collection_properties(907		collection: &CollectionHandle<T>,908		sender: &T::CrossAccountId,909		properties: Vec<Property>,910	) -> DispatchResult {911		for property in properties {912			Self::set_collection_property(collection, sender, property)?;913		}914915		Ok(())916	}917918	pub fn delete_collection_property(919		collection: &CollectionHandle<T>,920		sender: &T::CrossAccountId,921		property_key: PropertyKey,922	) -> DispatchResult {923		collection.check_is_owner_or_admin(sender)?;924925		CollectionProperties::<T>::try_mutate(collection.id, |properties| {926			properties.remove(&property_key)927		})928		.map_err(<Error<T>>::from)?;929930		Self::deposit_event(Event::CollectionPropertyDeleted(931			collection.id,932			property_key,933		));934935		Ok(())936	}937938	#[transactional]939	pub fn delete_collection_properties(940		collection: &CollectionHandle<T>,941		sender: &T::CrossAccountId,942		property_keys: Vec<PropertyKey>,943	) -> DispatchResult {944		for key in property_keys {945			Self::delete_collection_property(collection, sender, key)?;946		}947948		Ok(())949	}950951	// For migrations952	pub fn set_property_permission_unchecked(953		collection: CollectionId,954		property_permission: PropertyKeyPermission,955	) -> DispatchResult {956		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {957			permissions.try_set(property_permission.key, property_permission.permission)958		})959		.map_err(<Error<T>>::from)?;960		Ok(())961	}962963	pub fn set_property_permission(964		collection: &CollectionHandle<T>,965		sender: &T::CrossAccountId,966		property_permission: PropertyKeyPermission,967	) -> DispatchResult {968		collection.check_is_owner_or_admin(sender)?;969970		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);971		let current_permission = all_permissions.get(&property_permission.key);972		if matches![973			current_permission,974			Some(PropertyPermission { mutable: false, .. })975		] {976			return Err(<Error<T>>::NoPermission.into());977		}978979		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {980			let property_permission = property_permission.clone();981			permissions.try_set(property_permission.key, property_permission.permission)982		})983		.map_err(<Error<T>>::from)?;984985		Self::deposit_event(Event::PropertyPermissionSet(986			collection.id,987			property_permission.key,988		));989990		Ok(())991	}992993	#[transactional]994	pub fn set_property_permissions(995		collection: &CollectionHandle<T>,996		sender: &T::CrossAccountId,997		property_permissions: Vec<PropertyKeyPermission>,998	) -> DispatchResult {999		for prop_pemission in property_permissions {1000			Self::set_property_permission(collection, sender, prop_pemission)?;1001		}10021003		Ok(())1004	}10051006	pub fn get_collection_property(1007		collection_id: CollectionId,1008		key: &PropertyKey,1009	) -> Option<PropertyValue> {1010		Self::collection_properties(collection_id).get(key).cloned()1011	}10121013	pub fn bytes_keys_to_property_keys(1014		keys: Vec<Vec<u8>>,1015	) -> Result<Vec<PropertyKey>, DispatchError> {1016		keys.into_iter()1017			.map(|key| -> Result<PropertyKey, DispatchError> {1018				key.try_into()1019					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1020			})1021			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1022	}10231024	pub fn filter_collection_properties(1025		collection_id: CollectionId,1026		keys: Option<Vec<PropertyKey>>,1027	) -> Result<Vec<Property>, DispatchError> {1028		let properties = Self::collection_properties(collection_id);10291030		let properties = keys1031			.map(|keys| {1032				keys.into_iter()1033					.filter_map(|key| {1034						properties.get(&key).map(|value| Property {1035							key,1036							value: value.clone(),1037						})1038					})1039					.collect()1040			})1041			.unwrap_or_else(|| {1042				properties1043					.into_iter()1044					.map(|(key, value)| Property { key, value })1045					.collect()1046			});10471048		Ok(properties)1049	}10501051	pub fn filter_property_permissions(1052		collection_id: CollectionId,1053		keys: Option<Vec<PropertyKey>>,1054	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1055		let permissions = Self::property_permissions(collection_id);10561057		let key_permissions = keys1058			.map(|keys| {1059				keys.into_iter()1060					.filter_map(|key| {1061						permissions1062							.get(&key)1063							.map(|permission| PropertyKeyPermission {1064								key,1065								permission: permission.clone(),1066							})1067					})1068					.collect()1069			})1070			.unwrap_or_else(|| {1071				permissions1072					.into_iter()1073					.map(|(key, permission)| PropertyKeyPermission { key, permission })1074					.collect()1075			});10761077		Ok(key_permissions)1078	}10791080	pub fn toggle_allowlist(1081		collection: &CollectionHandle<T>,1082		sender: &T::CrossAccountId,1083		user: &T::CrossAccountId,1084		allowed: bool,1085	) -> DispatchResult {1086		collection.check_is_owner_or_admin(sender)?;10871088		// =========10891090		if allowed {1091			<Allowlist<T>>::insert((collection.id, user), true);1092		} else {1093			<Allowlist<T>>::remove((collection.id, user));1094		}10951096		Ok(())1097	}10981099	pub fn toggle_admin(1100		collection: &CollectionHandle<T>,1101		sender: &T::CrossAccountId,1102		user: &T::CrossAccountId,1103		admin: bool,1104	) -> DispatchResult {1105		collection.check_is_owner_or_admin(sender)?;11061107		let was_admin = <IsAdmin<T>>::get((collection.id, user));1108		if was_admin == admin {1109			return Ok(());1110		}1111		let amount = <AdminAmount<T>>::get(collection.id);11121113		if admin {1114			let amount = amount1115				.checked_add(1)1116				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1117			ensure!(1118				amount <= Self::collection_admins_limit(),1119				<Error<T>>::CollectionAdminCountExceeded,1120			);11211122			// =========11231124			<AdminAmount<T>>::insert(collection.id, amount);1125			<IsAdmin<T>>::insert((collection.id, user), true);1126		} else {1127			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1128			<IsAdmin<T>>::remove((collection.id, user));1129		}11301131		Ok(())1132	}11331134	pub fn clamp_limits(1135		mode: CollectionMode,1136		old_limit: &CollectionLimits,1137		mut new_limit: CollectionLimits,1138	) -> Result<CollectionLimits, DispatchError> {1139		limit_default!(old_limit, new_limit,1140			account_token_ownership_limit => ensure!(1141				new_limit <= MAX_TOKEN_OWNERSHIP,1142				<Error<T>>::CollectionLimitBoundsExceeded,1143			),1144			sponsored_data_size => ensure!(1145				new_limit <= CUSTOM_DATA_LIMIT,1146				<Error<T>>::CollectionLimitBoundsExceeded,1147			),11481149			sponsored_data_rate_limit => {},1150			token_limit => ensure!(1151				old_limit >= new_limit && new_limit > 0,1152				<Error<T>>::CollectionTokenLimitExceeded1153			),11541155			sponsor_transfer_timeout(match mode {1156				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1157				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1158				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1159			}) => ensure!(1160				new_limit <= MAX_SPONSOR_TIMEOUT,1161				<Error<T>>::CollectionLimitBoundsExceeded,1162			),1163			sponsor_approve_timeout => {},1164			owner_can_transfer => ensure!(1165				old_limit || !new_limit,1166				<Error<T>>::OwnerPermissionsCantBeReverted,1167			),1168			owner_can_destroy => ensure!(1169				old_limit || !new_limit,1170				<Error<T>>::OwnerPermissionsCantBeReverted,1171			),1172			transfers_enabled => {},1173		);1174		Ok(new_limit)1175	}11761177	pub fn clamp_permissions(1178		_mode: CollectionMode,1179		old_limit: &CollectionPermissions,1180		mut new_limit: CollectionPermissions,1181	) -> Result<CollectionPermissions, DispatchError> {1182		limit_default_clone!(old_limit, new_limit,1183			access => {},1184			mint_mode => {},1185			nesting => {},1186		);1187		Ok(new_limit)1188	}1189}11901191#[macro_export]1192macro_rules! unsupported {1193	() => {1194		Err(<Error<T>>::UnsupportedOperation.into())1195	};1196}11971198/// Worst cases1199pub trait CommonWeightInfo<CrossAccountId> {1200	fn create_item() -> Weight;1201	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1202	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1203	fn burn_item() -> Weight;1204	fn set_collection_properties(amount: u32) -> Weight;1205	fn delete_collection_properties(amount: u32) -> Weight;1206	fn set_token_properties(amount: u32) -> Weight;1207	fn delete_token_properties(amount: u32) -> Weight;1208	fn set_property_permissions(amount: u32) -> Weight;1209	fn transfer() -> Weight;1210	fn approve() -> Weight;1211	fn transfer_from() -> Weight;1212	fn burn_from() -> Weight;12131214	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1215	/// whole users's balance1216	///1217	/// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead1218	fn burn_recursively_self_raw() -> Weight;1219	/// Cost of iterating over `amount` children while burning, without counting child burning itself1220	///1221	/// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead1222	fn burn_recursively_breadth_raw(amount: u32) -> Weight;12231224	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1225		Self::burn_recursively_self_raw()1226			.saturating_mul(max_selfs.max(1) as u64)1227			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1228	}1229}12301231pub trait CommonCollectionOperations<T: Config> {1232	fn create_item(1233		&self,1234		sender: T::CrossAccountId,1235		to: T::CrossAccountId,1236		data: CreateItemData,1237		nesting_budget: &dyn Budget,1238	) -> DispatchResultWithPostInfo;1239	fn create_multiple_items(1240		&self,1241		sender: T::CrossAccountId,1242		to: T::CrossAccountId,1243		data: Vec<CreateItemData>,1244		nesting_budget: &dyn Budget,1245	) -> DispatchResultWithPostInfo;1246	fn create_multiple_items_ex(1247		&self,1248		sender: T::CrossAccountId,1249		data: CreateItemExData<T::CrossAccountId>,1250		nesting_budget: &dyn Budget,1251	) -> DispatchResultWithPostInfo;1252	fn burn_item(1253		&self,1254		sender: T::CrossAccountId,1255		token: TokenId,1256		amount: u128,1257	) -> DispatchResultWithPostInfo;1258	fn burn_item_recursively(1259		&self,1260		sender: T::CrossAccountId,1261		token: TokenId,1262		self_budget: &dyn Budget,1263		breadth_budget: &dyn Budget,1264	) -> DispatchResultWithPostInfo;1265	fn set_collection_properties(1266		&self,1267		sender: T::CrossAccountId,1268		properties: Vec<Property>,1269	) -> DispatchResultWithPostInfo;1270	fn delete_collection_properties(1271		&self,1272		sender: &T::CrossAccountId,1273		property_keys: Vec<PropertyKey>,1274	) -> DispatchResultWithPostInfo;1275	fn set_token_properties(1276		&self,1277		sender: T::CrossAccountId,1278		token_id: TokenId,1279		property: Vec<Property>,1280	) -> DispatchResultWithPostInfo;1281	fn delete_token_properties(1282		&self,1283		sender: T::CrossAccountId,1284		token_id: TokenId,1285		property_keys: Vec<PropertyKey>,1286	) -> DispatchResultWithPostInfo;1287	fn set_property_permissions(1288		&self,1289		sender: &T::CrossAccountId,1290		property_permissions: Vec<PropertyKeyPermission>,1291	) -> DispatchResultWithPostInfo;1292	fn transfer(1293		&self,1294		sender: T::CrossAccountId,1295		to: T::CrossAccountId,1296		token: TokenId,1297		amount: u128,1298		nesting_budget: &dyn Budget,1299	) -> DispatchResultWithPostInfo;1300	fn approve(1301		&self,1302		sender: T::CrossAccountId,1303		spender: T::CrossAccountId,1304		token: TokenId,1305		amount: u128,1306	) -> DispatchResultWithPostInfo;1307	fn transfer_from(1308		&self,1309		sender: T::CrossAccountId,1310		from: T::CrossAccountId,1311		to: T::CrossAccountId,1312		token: TokenId,1313		amount: u128,1314		nesting_budget: &dyn Budget,1315	) -> DispatchResultWithPostInfo;1316	fn burn_from(1317		&self,1318		sender: T::CrossAccountId,1319		from: T::CrossAccountId,1320		token: TokenId,1321		amount: u128,1322		nesting_budget: &dyn Budget,1323	) -> DispatchResultWithPostInfo;13241325	fn check_nesting(1326		&self,1327		sender: T::CrossAccountId,1328		from: (CollectionId, TokenId),1329		under: TokenId,1330		budget: &dyn Budget,1331	) -> DispatchResult;13321333	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13341335	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13361337	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1338	fn collection_tokens(&self) -> Vec<TokenId>;1339	fn token_exists(&self, token: TokenId) -> bool;1340	fn last_token_id(&self) -> TokenId;13411342	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1343	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1344	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1345	/// Amount of unique collection tokens1346	fn total_supply(&self) -> u32;1347	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1348	fn account_balance(&self, account: T::CrossAccountId) -> u32;1349	/// Amount of specific token account have (Applicable to fungible/refungible)1350	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1351	fn allowance(1352		&self,1353		sender: T::CrossAccountId,1354		spender: T::CrossAccountId,1355		token: TokenId,1356	) -> u128;1357}13581359// Flexible enough for implementing CommonCollectionOperations1360pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1361	let post_info = PostDispatchInfo {1362		actual_weight: Some(weight),1363		pays_fee: Pays::Yes,1364	};1365	match res {1366		Ok(()) => Ok(post_info),1367		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1368	}1369}13701371impl<T: Config> From<PropertiesError> for Error<T> {1372	fn from(error: PropertiesError) -> Self {1373		match error {1374			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1375			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1376			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1377			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1378			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1379		}1380	}1381}
modifiedpallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -51,6 +51,105 @@
 	event MintingFinished();
 }
 
+// Selector: 3a54513b
+contract Collection is Dummy, ERC165 {
+	// Selector: setCollectionProperty(string,bytes) 2f073f66
+	function setCollectionProperty(string memory key, bytes memory value)
+		public
+	{
+		require(false, stub_error);
+		key;
+		value;
+		dummy = 0;
+	}
+
+	// Selector: deleteCollectionProperty(string) 7b7debce
+	function deleteCollectionProperty(string memory key) public {
+		require(false, stub_error);
+		key;
+		dummy = 0;
+	}
+
+	// Throws error if key not found
+	//
+	// Selector: collectionProperty(string) cf24fd6d
+	function collectionProperty(string memory key)
+		public
+		view
+		returns (bytes memory)
+	{
+		require(false, stub_error);
+		key;
+		dummy;
+		return hex"";
+	}
+
+	// Selector: ethSetSponsor(address) 8f9af356
+	function ethSetSponsor(address sponsor) public {
+		require(false, stub_error);
+		sponsor;
+		dummy = 0;
+	}
+
+	// Selector: ethConfirmSponsorship() a8580d1a
+	function ethConfirmSponsorship() public {
+		require(false, stub_error);
+		dummy = 0;
+	}
+
+	// Selector: setLimit(string,uint32) 68db30ca
+	function setLimit(string memory limit, uint32 value) public {
+		require(false, stub_error);
+		limit;
+		value;
+		dummy = 0;
+	}
+
+	// Selector: setLimit(string,bool) ea67e4c2
+	function setLimit(string memory limit, bool value) public {
+		require(false, stub_error);
+		limit;
+		value;
+		dummy = 0;
+	}
+
+	// Selector: contractAddress() f6b4dfb4
+	function contractAddress() public view returns (address) {
+		require(false, stub_error);
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
+
+	// Selector: addAdmin(address) 70480275
+	function addAdmin(address newAdmin) public view {
+		require(false, stub_error);
+		newAdmin;
+		dummy;
+	}
+
+	// Selector: removeAdmin(address) 1785f53c
+	function removeAdmin(address admin) public view {
+		require(false, stub_error);
+		admin;
+		dummy;
+	}
+
+	// Selector: setNesting(bool) e8fc50dd
+	function setNesting(bool enable) public {
+		require(false, stub_error);
+		enable;
+		dummy = 0;
+	}
+
+	// Selector: setNesting(bool,address[]) 7df12a9a
+	function setNesting(bool enable, address[] memory collections) public {
+		require(false, stub_error);
+		enable;
+		collections;
+		dummy = 0;
+	}
+}
+
 // Selector: 41369377
 contract TokenProperties is Dummy, ERC165 {
 	// Selector: setTokenPropertyPermission(string,bool,bool,bool) 222d97fa
@@ -327,76 +426,6 @@
 		require(false, stub_error);
 		dummy;
 		return 0;
-	}
-}
-
-// Selector: c894dc35
-contract Collection is Dummy, ERC165 {
-	// Selector: setCollectionProperty(string,bytes) 2f073f66
-	function setCollectionProperty(string memory key, bytes memory value)
-		public
-	{
-		require(false, stub_error);
-		key;
-		value;
-		dummy = 0;
-	}
-
-	// Selector: deleteCollectionProperty(string) 7b7debce
-	function deleteCollectionProperty(string memory key) public {
-		require(false, stub_error);
-		key;
-		dummy = 0;
-	}
-
-	// Throws error if key not found
-	//
-	// Selector: collectionProperty(string) cf24fd6d
-	function collectionProperty(string memory key)
-		public
-		view
-		returns (bytes memory)
-	{
-		require(false, stub_error);
-		key;
-		dummy;
-		return hex"";
-	}
-
-	// Selector: ethSetSponsor(address) 8f9af356
-	function ethSetSponsor(address sponsor) public {
-		require(false, stub_error);
-		sponsor;
-		dummy = 0;
-	}
-
-	// Selector: ethConfirmSponsorship() a8580d1a
-	function ethConfirmSponsorship() public {
-		require(false, stub_error);
-		dummy = 0;
-	}
-
-	// Selector: setLimit(string,uint32) 68db30ca
-	function setLimit(string memory limit, uint32 value) public {
-		require(false, stub_error);
-		limit;
-		value;
-		dummy = 0;
-	}
-
-	// Selector: setLimit(string,bool) ea67e4c2
-	function setLimit(string memory limit, bool value) public {
-		require(false, stub_error);
-		limit;
-		value;
-		dummy = 0;
-	}
-
-	// Selector: contractAddress() f6b4dfb4
-	function contractAddress() public view returns (address) {
-		require(false, stub_error);
-		dummy;
-		return 0x0000000000000000000000000000000000000000;
 	}
 }
 
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -454,6 +454,8 @@
 	}
 }
 
+pub type OwnerRestrictedSet = BoundedBTreeSet<CollectionId, ConstU32<16>>;
+
 #[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen, Derivative)]
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 #[derivative(Debug)]
@@ -466,7 +468,7 @@
 	OwnerRestricted(
 		#[cfg_attr(feature = "serde1", serde(with = "bounded::set_serde"))]
 		#[derivative(Debug(format_with = "bounded::set_debug"))]
-		BoundedBTreeSet<CollectionId, ConstU32<16>>,
+		OwnerRestrictedSet,
 	),
 	/// Used for tests
 	Permissive,
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -42,6 +42,51 @@
 	event MintingFinished();
 }
 
+// Selector: 3a54513b
+interface Collection is Dummy, ERC165 {
+	// Selector: setCollectionProperty(string,bytes) 2f073f66
+	function setCollectionProperty(string memory key, bytes memory value)
+		external;
+
+	// Selector: deleteCollectionProperty(string) 7b7debce
+	function deleteCollectionProperty(string memory key) external;
+
+	// Throws error if key not found
+	//
+	// Selector: collectionProperty(string) cf24fd6d
+	function collectionProperty(string memory key)
+		external
+		view
+		returns (bytes memory);
+
+	// Selector: ethSetSponsor(address) 8f9af356
+	function ethSetSponsor(address sponsor) external;
+
+	// Selector: ethConfirmSponsorship() a8580d1a
+	function ethConfirmSponsorship() external;
+
+	// Selector: setLimit(string,uint32) 68db30ca
+	function setLimit(string memory limit, uint32 value) external;
+
+	// Selector: setLimit(string,bool) ea67e4c2
+	function setLimit(string memory limit, bool value) external;
+
+	// Selector: contractAddress() f6b4dfb4
+	function contractAddress() external view returns (address);
+
+	// Selector: addAdmin(address) 70480275
+	function addAdmin(address newAdmin) external view;
+
+	// Selector: removeAdmin(address) 1785f53c
+	function removeAdmin(address admin) external view;
+
+	// Selector: setNesting(bool) e8fc50dd
+	function setNesting(bool enable) external;
+
+	// Selector: setNesting(bool,address[]) 7df12a9a
+	function setNesting(bool enable, address[] memory collections) external;
+}
+
 // Selector: 41369377
 interface TokenProperties is Dummy, ERC165 {
 	// Selector: setTokenPropertyPermission(string,bool,bool,bool) 222d97fa
@@ -189,39 +234,6 @@
 
 	// Selector: totalSupply() 18160ddd
 	function totalSupply() external view returns (uint256);
-}
-
-// Selector: c894dc35
-interface Collection is Dummy, ERC165 {
-	// Selector: setCollectionProperty(string,bytes) 2f073f66
-	function setCollectionProperty(string memory key, bytes memory value)
-		external;
-
-	// Selector: deleteCollectionProperty(string) 7b7debce
-	function deleteCollectionProperty(string memory key) external;
-
-	// Throws error if key not found
-	//
-	// Selector: collectionProperty(string) cf24fd6d
-	function collectionProperty(string memory key)
-		external
-		view
-		returns (bytes memory);
-
-	// Selector: ethSetSponsor(address) 8f9af356
-	function ethSetSponsor(address sponsor) external;
-
-	// Selector: ethConfirmSponsorship() a8580d1a
-	function ethConfirmSponsorship() external;
-
-	// Selector: setLimit(string,uint32) 68db30ca
-	function setLimit(string memory limit, uint32 value) external;
-
-	// Selector: setLimit(string,bool) ea67e4c2
-	function setLimit(string memory limit, bool value) external;
-
-	// Selector: contractAddress() f6b4dfb4
-	function contractAddress() external view returns (address);
 }
 
 // Selector: d74d154f
modifiedtests/src/eth/createCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createCollection.test.ts
+++ b/tests/src/eth/createCollection.test.ts
@@ -30,13 +30,13 @@
 describe('Create collection from EVM', () => {
   itWeb3('Create collection', async ({api, web3}) => {
     const owner = await createEthAccountWithBalance(api, web3);
-    const helper = evmCollectionHelpers(web3, owner);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
     const collectionName = 'CollectionEVM';
     const description = 'Some description';
     const tokenPrefix = 'token prefix';
   
     const collectionCountBefore = await getCreatedCollectionCount(api);
-    const result = await helper.methods
+    const result = await collectionHelper.methods
       .createNonfungibleCollection(collectionName, description, tokenPrefix)
       .send();
     const collectionCountAfter = await getCreatedCollectionCount(api);
modifiedtests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -82,6 +82,15 @@
   },
   {
     "inputs": [
+      { "internalType": "address", "name": "newAdmin", "type": "address" }
+    ],
+    "name": "addAdmin",
+    "outputs": [],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "approved", "type": "address" },
       { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
     ],
@@ -282,6 +291,15 @@
   },
   {
     "inputs": [
+      { "internalType": "address", "name": "admin", "type": "address" }
+    ],
+    "name": "removeAdmin",
+    "outputs": [],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "from", "type": "address" },
       { "internalType": "address", "name": "to", "type": "address" },
       { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
@@ -345,6 +363,27 @@
   },
   {
     "inputs": [
+      { "internalType": "bool", "name": "enable", "type": "bool" },
+      {
+        "internalType": "address[]",
+        "name": "collections",
+        "type": "address[]"
+      }
+    ],
+    "name": "setNesting",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }],
+    "name": "setNesting",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "uint256", "name": "tokenId", "type": "uint256" },
       { "internalType": "string", "name": "key", "type": "string" },
       { "internalType": "bytes", "name": "value", "type": "bytes" }
modifiedtests/src/eth/proxy/nonFungibleProxy.test.tsdiffbeforeafterboth
--- a/tests/src/eth/proxy/nonFungibleProxy.test.ts
+++ b/tests/src/eth/proxy/nonFungibleProxy.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {createCollectionExpectSuccess, createItemExpectSuccess} from '../../util/helpers';
-import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents} from '../util/helpers';
+import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, normalizeEvents} from '../util/helpers';
 import nonFungibleAbi from '../nonFungibleAbi.json';
 import {expect} from 'chai';
 import {submitTransactionAsync} from '../../substrate/substrate-api';
@@ -87,20 +87,19 @@
 });
 
 describe('NFT (Via EVM proxy): Plain calls', () => {
-  //TODO: CORE-302 add eth methods
-  itWeb3.skip('Can perform mint()', async ({web3, api, privateKeyWrapper}) => {
-    const collection = await createCollectionExpectSuccess({
-      mode: {type: 'NFT'},
-    });
-    const alice = privateKeyWrapper('//Alice');
+  itWeb3('Can perform mint()', async ({web3, api}) => {
+    const owner = await createEthAccountWithBalance(api, web3);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
+    const result = await collectionHelper.methods
+      .createNonfungibleCollection('A', 'A', 'A')
+      .send();
+    const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
     const caller = await createEthAccountWithBalance(api, web3);
     const receiver = createEthAccount(web3);
-
-    const address = collectionIdToAddress(collection);
-    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
-
-    const changeAdminTx = api.tx.unique.addCollectionAdmin(collection, {Ethereum: contract.options.address});
-    await submitTransactionAsync(alice, changeAdminTx);
+    const collectionEvmOwned = evmCollection(web3, owner, collectionIdAddress);
+    const collectionEvm = evmCollection(web3, caller, collectionIdAddress);
+    const contract = await proxyWrap(api, web3, collectionEvm);
+    await collectionEvmOwned.methods.addAdmin(contract.options.address).send();
 
     {
       const nextTokenId = await contract.methods.nextTokenId().call();
@@ -111,10 +110,11 @@
         'Test URI',
       ).send({from: caller});
       const events = normalizeEvents(result.events);
+      events[0].address = events[0].address.toLocaleLowerCase();
 
       expect(events).to.be.deep.equal([
         {
-          address,
+          address: collectionIdAddress.toLocaleLowerCase(),
           event: 'Transfer',
           args: {
             from: '0x0000000000000000000000000000000000000000',
modifiedtests/src/interfaces/augment-api-rpc.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-rpc.ts
+++ b/tests/src/interfaces/augment-api-rpc.ts
@@ -18,7 +18,7 @@
 import type { EthAccount, EthCallRequest, EthFilter, EthFilterChanges, EthLog, EthReceipt, EthRichBlock, EthSubKind, EthSubParams, EthSyncStatus, EthTransaction, EthTransactionRequest, EthWork } from '@polkadot/types/interfaces/eth';
 import type { Extrinsic } from '@polkadot/types/interfaces/extrinsics';
 import type { EncodedFinalityProofs, JustificationNotification, ReportedRoundStates } from '@polkadot/types/interfaces/grandpa';
-import type { MmrLeafBatchProof, MmrLeafProof } from '@polkadot/types/interfaces/mmr';
+import type { MmrLeafProof } from '@polkadot/types/interfaces/mmr';
 import type { StorageKind } from '@polkadot/types/interfaces/offchain';
 import type { FeeDetails, RuntimeDispatchInfo } from '@polkadot/types/interfaces/payment';
 import type { RpcMethods } from '@polkadot/types/interfaces/rpc';
@@ -354,14 +354,10 @@
       subscribeJustifications: AugmentedRpc<() => Observable<JustificationNotification>>;
     };
     mmr: {
-      /**
-       * Generate MMR proof for the given leaf indices.
-       **/
-      generateBatchProof: AugmentedRpc<(leafIndices: Vec<u64> | (u64 | AnyNumber | Uint8Array)[], at?: BlockHash | string | Uint8Array) => Observable<MmrLeafProof>>;
       /**
        * Generate MMR proof for given leaf index.
        **/
-      generateProof: AugmentedRpc<(leafIndex: u64 | AnyNumber | Uint8Array, at?: BlockHash | string | Uint8Array) => Observable<MmrLeafBatchProof>>;
+      generateProof: AugmentedRpc<(leafIndex: u64 | AnyNumber | Uint8Array, at?: BlockHash | string | Uint8Array) => Observable<MmrLeafProof>>;
     };
     net: {
       /**
modifiedtests/src/interfaces/augment-types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-types.ts
+++ b/tests/src/interfaces/augment-types.ts
@@ -3,7 +3,7 @@
 
 import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportPalletId, FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchClass, FrameSupportWeightsDispatchInfo, FrameSupportWeightsPays, FrameSupportWeightsPerDispatchClassU32, FrameSupportWeightsPerDispatchClassU64, FrameSupportWeightsPerDispatchClassWeightsPerClass, FrameSupportWeightsRuntimeDbWeight, FrameSupportWeightsWeightToFeeCoefficient, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OpalRuntimeRuntime, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReleases, PalletBalancesReserveData, PalletCommonError, PalletCommonEvent, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEthereumFakeTransactionFinalizer, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletFungibleError, PalletInflationCall, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRefungibleItemData, PalletStructureCall, PalletStructureError, PalletStructureEvent, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTimestampCall, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletUniqueRawEvent, PalletXcmCall, PalletXcmError, PalletXcmEvent, PhantomTypeUpDataStructs, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2AbridgedHrmpChannel, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpRuntimeTransactionalError, SpTrieStorageProof, SpVersionRuntimeVersion, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionPermissions, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsCreateNftData, UpDataStructsCreateNftExData, UpDataStructsCreateReFungibleData, UpDataStructsCreateRefungibleExData, UpDataStructsNestingRule, UpDataStructsProperties, UpDataStructsPropertiesMapBoundedVec, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsProperty, UpDataStructsPropertyKeyPermission, UpDataStructsPropertyPermission, UpDataStructsRmrkAccountIdOrCollectionNftTuple, UpDataStructsRmrkBaseInfo, UpDataStructsRmrkBasicResource, UpDataStructsRmrkCollectionInfo, UpDataStructsRmrkComposableResource, UpDataStructsRmrkEquippableList, UpDataStructsRmrkFixedPart, UpDataStructsRmrkNftChild, UpDataStructsRmrkNftInfo, UpDataStructsRmrkPartType, UpDataStructsRmrkPropertyInfo, UpDataStructsRmrkResourceInfo, UpDataStructsRmrkResourceTypes, UpDataStructsRmrkRoyaltyInfo, UpDataStructsRmrkSlotPart, UpDataStructsRmrkSlotResource, UpDataStructsRmrkTheme, UpDataStructsRmrkThemeProperty, UpDataStructsRpcCollection, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipState, UpDataStructsTokenChild, UpDataStructsTokenData, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from './default';
 import type { Data, StorageKey } from '@polkadot/types';
-import type { BitVec, Bool, Bytes, I128, I16, I256, I32, I64, I8, Json, Null, OptionBool, Raw, Text, Type, U128, U16, U256, U32, U64, U8, USize, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types-codec';
+import type { BitVec, Bool, Bytes, I128, I16, I256, I32, I64, I8, Json, Null, Raw, Text, Type, U128, U16, U256, U32, U64, U8, USize, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types-codec';
 import type { AssetApproval, AssetApprovalKey, AssetBalance, AssetDestroyWitness, AssetDetails, AssetMetadata, TAssetBalance, TAssetDepositBalance } from '@polkadot/types/interfaces/assets';
 import type { BlockAttestations, IncludedBlocks, MoreAttestations } from '@polkadot/types/interfaces/attestations';
 import type { RawAuraPreDigest } from '@polkadot/types/interfaces/aura';
@@ -36,7 +36,7 @@
 import type { AuthIndex, AuthoritySignature, Heartbeat, HeartbeatTo244, OpaqueMultiaddr, OpaqueNetworkState, OpaquePeerId } from '@polkadot/types/interfaces/imOnline';
 import type { CallIndex, LotteryConfig } from '@polkadot/types/interfaces/lottery';
 import type { ErrorMetadataLatest, ErrorMetadataV10, ErrorMetadataV11, ErrorMetadataV12, ErrorMetadataV13, ErrorMetadataV14, ErrorMetadataV9, EventMetadataLatest, EventMetadataV10, EventMetadataV11, EventMetadataV12, EventMetadataV13, EventMetadataV14, EventMetadataV9, ExtrinsicMetadataLatest, ExtrinsicMetadataV11, ExtrinsicMetadataV12, ExtrinsicMetadataV13, ExtrinsicMetadataV14, FunctionArgumentMetadataLatest, FunctionArgumentMetadataV10, FunctionArgumentMetadataV11, FunctionArgumentMetadataV12, FunctionArgumentMetadataV13, FunctionArgumentMetadataV14, FunctionArgumentMetadataV9, FunctionMetadataLatest, FunctionMetadataV10, FunctionMetadataV11, FunctionMetadataV12, FunctionMetadataV13, FunctionMetadataV14, FunctionMetadataV9, MetadataAll, MetadataLatest, MetadataV10, MetadataV11, MetadataV12, MetadataV13, MetadataV14, MetadataV9, ModuleConstantMetadataV10, ModuleConstantMetadataV11, ModuleConstantMetadataV12, ModuleConstantMetadataV13, ModuleConstantMetadataV9, ModuleMetadataV10, ModuleMetadataV11, ModuleMetadataV12, ModuleMetadataV13, ModuleMetadataV9, PalletCallMetadataLatest, PalletCallMetadataV14, PalletConstantMetadataLatest, PalletConstantMetadataV14, PalletErrorMetadataLatest, PalletErrorMetadataV14, PalletEventMetadataLatest, PalletEventMetadataV14, PalletMetadataLatest, PalletMetadataV14, PalletStorageMetadataLatest, PalletStorageMetadataV14, PortableType, PortableTypeV14, SignedExtensionMetadataLatest, SignedExtensionMetadataV14, StorageEntryMetadataLatest, StorageEntryMetadataV10, StorageEntryMetadataV11, StorageEntryMetadataV12, StorageEntryMetadataV13, StorageEntryMetadataV14, StorageEntryMetadataV9, StorageEntryModifierLatest, StorageEntryModifierV10, StorageEntryModifierV11, StorageEntryModifierV12, StorageEntryModifierV13, StorageEntryModifierV14, StorageEntryModifierV9, StorageEntryTypeLatest, StorageEntryTypeV10, StorageEntryTypeV11, StorageEntryTypeV12, StorageEntryTypeV13, StorageEntryTypeV14, StorageEntryTypeV9, StorageHasher, StorageHasherV10, StorageHasherV11, StorageHasherV12, StorageHasherV13, StorageHasherV14, StorageHasherV9, StorageMetadataV10, StorageMetadataV11, StorageMetadataV12, StorageMetadataV13, StorageMetadataV9 } from '@polkadot/types/interfaces/metadata';
-import type { MmrLeafBatchProof, MmrLeafProof } from '@polkadot/types/interfaces/mmr';
+import type { MmrLeafProof } from '@polkadot/types/interfaces/mmr';
 import type { StorageKind } from '@polkadot/types/interfaces/offchain';
 import type { DeferredOffenceOf, Kind, OffenceDetails, Offender, OpaqueTimeSlot, ReportIdOf, Reporter } from '@polkadot/types/interfaces/offences';
 import type { AbridgedCandidateReceipt, AbridgedHostConfiguration, AbridgedHrmpChannel, AssignmentId, AssignmentKind, AttestedCandidate, AuctionIndex, AuthorityDiscoveryId, AvailabilityBitfield, AvailabilityBitfieldRecord, BackedCandidate, Bidder, BufferedSessionChange, CandidateCommitments, CandidateDescriptor, CandidateHash, CandidateInfo, CandidatePendingAvailability, CandidateReceipt, CollatorId, CollatorSignature, CommittedCandidateReceipt, CoreAssignment, CoreIndex, CoreOccupied, DisputeLocation, DisputeResult, DisputeState, DisputeStatement, DisputeStatementSet, DoubleVoteReport, DownwardMessage, ExplicitDisputeStatement, GlobalValidationData, GlobalValidationSchedule, GroupIndex, HeadData, HostConfiguration, HrmpChannel, HrmpChannelId, HrmpOpenChannelRequest, InboundDownwardMessage, InboundHrmpMessage, InboundHrmpMessages, IncomingParachain, IncomingParachainDeploy, IncomingParachainFixed, InvalidDisputeStatementKind, LeasePeriod, LeasePeriodOf, LocalValidationData, MessageIngestionType, MessageQueueChain, MessagingStateSnapshot, MessagingStateSnapshotEgressEntry, MultiDisputeStatementSet, NewBidder, OutboundHrmpMessage, ParaGenesisArgs, ParaId, ParaInfo, ParaLifecycle, ParaPastCodeMeta, ParaScheduling, ParaValidatorIndex, ParachainDispatchOrigin, ParachainInherentData, ParachainProposal, ParachainsInherentData, ParathreadClaim, ParathreadClaimQueue, ParathreadEntry, PersistedValidationData, QueuedParathread, RegisteredParachainInfo, RelayBlockNumber, RelayChainBlockNumber, RelayChainHash, RelayHash, Remark, ReplacementTimes, Retriable, Scheduling, ServiceQuality, SessionInfo, SessionInfoValidatorGroup, SignedAvailabilityBitfield, SignedAvailabilityBitfields, SigningContext, SlotRange, SlotRange10, Statement, SubId, SystemInherentData, TransientValidationData, UpgradeGoAhead, UpgradeRestriction, UpwardMessage, ValidDisputeStatementKind, ValidationCode, ValidationCodeHash, ValidationData, ValidationDataType, ValidationFunctionParams, ValidatorSignature, ValidityAttestation, VecInboundHrmpMessage, WinnersData, WinnersData10, WinnersDataTuple, WinnersDataTuple10, WinningData, WinningData10, WinningDataEntry } from '@polkadot/types/interfaces/parachains';
@@ -661,7 +661,6 @@
     MetadataV14: MetadataV14;
     MetadataV9: MetadataV9;
     MigrationStatusResult: MigrationStatusResult;
-    MmrLeafBatchProof: MmrLeafBatchProof;
     MmrLeafProof: MmrLeafProof;
     MmrRootHash: MmrRootHash;
     ModuleConstantMetadataV10: ModuleConstantMetadataV10;
@@ -728,7 +727,6 @@
     OpenTipTip: OpenTipTip;
     OpenTipTo225: OpenTipTo225;
     OperatingMode: OperatingMode;
-    OptionBool: OptionBool;
     Origin: Origin;
     OriginCaller: OriginCaller;
     OriginKindV0: OriginKindV0;