git.delta.rocks / unique-network / refs/commits / 2050a76c8808

difftreelog

feat external-internal api collection creation segreation

Fahrrader2022-06-08parent: #0cb9e74.patch.diff
in: master

11 files changed

modifiedpallets/common/src/dispatch.rsdiffbeforeafterboth
--- a/pallets/common/src/dispatch.rs
+++ b/pallets/common/src/dispatch.rs
@@ -36,6 +36,13 @@
 			},
 			error,
 		})?;
+	handle.check_is_internal().map_err(|error| DispatchErrorWithPostInfo {
+		post_info: PostDispatchInfo {
+			actual_weight: Some(dispatch_weight::<T>()),
+			pays_fee: Pays::Yes,
+		},
+		error,
+	})?;
 	let dispatched = T::CollectionDispatch::dispatch(handle);
 	let mut result = call(dispatched.as_dyn());
 	match &mut result {
modifiedpallets/common/src/erc.rsdiffbeforeafterboth
--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -316,8 +316,9 @@
 }
 
 fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {
+	// TODO possibly delete for the lack of transaction
 	collection
-		.check_is_mutable()
+		.check_is_internal()
 		.map_err(dispatch_to_evm::<T>)?;
 	<crate::CollectionById<T>>::insert(collection.id, collection.collection.clone());
 	Ok(())
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	}129130	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {131		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)132	}133134	pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {135		self.recorder136			.consume_gas(T::GasWeightMapping::weight_to_gas(137				<T as frame_system::Config>::DbWeight::get()138					.read139					.saturating_mul(reads),140			))141	}142143	pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {144		self.recorder145			.consume_gas(T::GasWeightMapping::weight_to_gas(146				<T as frame_system::Config>::DbWeight::get()147					.write148					.saturating_mul(writes),149			))150	}151	pub fn save(self) -> Result<(), DispatchError> {152		self.check_is_mutable()?;153		<CollectionById<T>>::insert(self.id, self.collection);154		Ok(())155	}156157	pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {158		self.check_is_mutable()?;159		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);160		Ok(())161	}162163	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {164		self.check_is_mutable()?;165166		if self.collection.sponsorship.pending_sponsor() != Some(sender) {167			return Ok(false);168		}169170		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());171		Ok(true)172	}173174	/// Checks that collection is can be mutate.175	/// Now check only `external_collection` flag and if it **true**, than return `CollectionIsReadOnly` error.176	pub fn check_is_mutable(&self) -> DispatchResult {177		if self.external_collection {178			return Err(<Error<T>>::CollectionIsReadOnly)?;179		}180181		Ok(())182	}183}184185impl<T: Config> Deref for CollectionHandle<T> {186	type Target = Collection<T::AccountId>;187188	fn deref(&self) -> &Self::Target {189		&self.collection190	}191}192193impl<T: Config> DerefMut for CollectionHandle<T> {194	fn deref_mut(&mut self) -> &mut Self::Target {195		&mut self.collection196	}197}198199impl<T: Config> CollectionHandle<T> {200	pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {201		ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);202		Ok(())203	}204	pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {205		*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))206	}207	pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {208		ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);209		Ok(())210	}211	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {212		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)213	}214	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {215		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)216	}217	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {218		ensure!(219			<Allowlist<T>>::get((self.id, user)),220			<Error<T>>::AddressNotInAllowlist221		);222		Ok(())223	}224}225226#[frame_support::pallet]227pub mod pallet {228	use super::*;229	use pallet_evm::account;230	use dispatch::CollectionDispatch;231	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};232	use frame_system::pallet_prelude::*;233	use frame_support::traits::Currency;234	use up_data_structs::{TokenId, mapping::TokenAddressMapping};235	use scale_info::TypeInfo;236	use weights::WeightInfo;237238	#[pallet::config]239	pub trait Config:240		frame_system::Config241		+ pallet_evm_coder_substrate::Config242		+ pallet_evm::Config243		+ TypeInfo244		+ account::Config245	{246		type WeightInfo: WeightInfo;247		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;248249		type Currency: Currency<Self::AccountId>;250251		#[pallet::constant]252		type CollectionCreationPrice: Get<253			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,254		>;255		type CollectionDispatch: CollectionDispatch<Self>;256257		type TreasuryAccountId: Get<Self::AccountId>;258		type ContractAddress: Get<H160>;259260		type EvmTokenAddressMapping: TokenAddressMapping<H160>;261		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;262	}263264	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);265266	#[pallet::pallet]267	#[pallet::storage_version(STORAGE_VERSION)]268	#[pallet::generate_store(pub(super) trait Store)]269	pub struct Pallet<T>(_);270271	#[pallet::extra_constants]272	impl<T: Config> Pallet<T> {273		pub fn collection_admins_limit() -> u32 {274			COLLECTION_ADMINS_LIMIT275		}276	}277278	#[pallet::event]279	#[pallet::generate_deposit(pub fn deposit_event)]280	pub enum Event<T: Config> {281		/// New collection was created282		///283		/// # Arguments284		///285		/// * collection_id: Globally unique identifier of newly created collection.286		///287		/// * mode: [CollectionMode] converted into u8.288		///289		/// * account_id: Collection owner.290		CollectionCreated(CollectionId, u8, T::AccountId),291292		/// New collection was destroyed293		///294		/// # Arguments295		///296		/// * collection_id: Globally unique identifier of collection.297		CollectionDestroyed(CollectionId),298299		/// New item was created.300		///301		/// # Arguments302		///303		/// * collection_id: Id of the collection where item was created.304		///305		/// * item_id: Id of an item. Unique within the collection.306		///307		/// * recipient: Owner of newly created item308		///309		/// * amount: Always 1 for NFT310		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),311312		/// Collection item was burned.313		///314		/// # Arguments315		///316		/// * collection_id.317		///318		/// * item_id: Identifier of burned NFT.319		///320		/// * owner: which user has destroyed its tokens321		///322		/// * amount: Always 1 for NFT323		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),324325		/// Item was transferred326		///327		/// * collection_id: Id of collection to which item is belong328		///329		/// * item_id: Id of an item330		///331		/// * sender: Original owner of item332		///333		/// * recipient: New owner of item334		///335		/// * amount: Always 1 for NFT336		Transfer(337			CollectionId,338			TokenId,339			T::CrossAccountId,340			T::CrossAccountId,341			u128,342		),343344		/// * collection_id345		///346		/// * item_id347		///348		/// * sender349		///350		/// * spender351		///352		/// * amount353		Approved(354			CollectionId,355			TokenId,356			T::CrossAccountId,357			T::CrossAccountId,358			u128,359		),360361		CollectionPropertySet(CollectionId, PropertyKey),362363		CollectionPropertyDeleted(CollectionId, PropertyKey),364365		TokenPropertySet(CollectionId, TokenId, PropertyKey),366367		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),368369		PropertyPermissionSet(CollectionId, PropertyKey),370	}371372	#[pallet::error]373	pub enum Error<T> {374		/// This collection does not exist.375		CollectionNotFound,376		/// Sender parameter and item owner must be equal.377		MustBeTokenOwner,378		/// No permission to perform action379		NoPermission,380		/// Destroying only empty collections is allowed381		CantDestroyNotEmptyCollection,382		/// Collection is not in mint mode.383		PublicMintingNotAllowed,384		/// Address is not in allow list.385		AddressNotInAllowlist,386387		/// Collection name can not be longer than 63 char.388		CollectionNameLimitExceeded,389		/// Collection description can not be longer than 255 char.390		CollectionDescriptionLimitExceeded,391		/// Token prefix can not be longer than 15 char.392		CollectionTokenPrefixLimitExceeded,393		/// Total collections bound exceeded.394		TotalCollectionsLimitExceeded,395		/// Exceeded max admin count396		CollectionAdminCountExceeded,397		/// Collection limit bounds per collection exceeded398		CollectionLimitBoundsExceeded,399		/// Tried to enable permissions which are only permitted to be disabled400		OwnerPermissionsCantBeReverted,401		/// Collection settings not allowing items transferring402		TransferNotAllowed,403		/// Account token limit exceeded per collection404		AccountTokenLimitExceeded,405		/// Collection token limit exceeded406		CollectionTokenLimitExceeded,407		/// Metadata flag frozen408		MetadataFlagFrozen,409410		/// Item not exists.411		TokenNotFound,412		/// Item balance not enough.413		TokenValueTooLow,414		/// Requested value more than approved.415		ApprovedValueTooLow,416		/// Tried to approve more than owned417		CantApproveMoreThanOwned,418419		/// Can't transfer tokens to ethereum zero address420		AddressIsZero,421		/// Target collection doesn't supports this operation422		UnsupportedOperation,423424		/// Not sufficient founds to perform action425		NotSufficientFounds,426427		/// Collection has nesting disabled428		NestingIsDisabled,429		/// Only owner may nest tokens under this collection430		OnlyOwnerAllowedToNest,431		/// Only tokens from specific collections may nest tokens under this432		SourceCollectionIsNotAllowedToNest,433434		/// Tried to store more data than allowed in collection field435		CollectionFieldSizeExceeded,436437		/// Tried to store more property data than allowed438		NoSpaceForProperty,439440		/// Tried to store more property keys than allowed441		PropertyLimitReached,442443		/// Property key is too long444		PropertyKeyIsTooLong,445446		/// Only ASCII letters, digits, and '_', '-' are allowed447		InvalidCharacterInPropertyKey,448449		/// Empty property keys are forbidden450		EmptyPropertyKey,451452		/// Collection is read only453		CollectionIsReadOnly,454	}455456	#[pallet::storage]457	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;458	#[pallet::storage]459	pub type DestroyedCollectionCount<T> =460		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;461462	/// Collection info463	#[pallet::storage]464	pub type CollectionById<T> = StorageMap<465		Hasher = Blake2_128Concat,466		Key = CollectionId,467		Value = Collection<<T as frame_system::Config>::AccountId>,468		QueryKind = OptionQuery,469	>;470471	/// Collection properties472	#[pallet::storage]473	#[pallet::getter(fn collection_properties)]474	pub type CollectionProperties<T> = StorageMap<475		Hasher = Blake2_128Concat,476		Key = CollectionId,477		Value = Properties,478		QueryKind = ValueQuery,479		OnEmpty = up_data_structs::CollectionProperties,480	>;481482	#[pallet::storage]483	#[pallet::getter(fn property_permissions)]484	pub type CollectionPropertyPermissions<T> = StorageMap<485		Hasher = Blake2_128Concat,486		Key = CollectionId,487		Value = PropertiesPermissionMap,488		QueryKind = ValueQuery,489	>;490491	#[pallet::storage]492	pub type AdminAmount<T> = StorageMap<493		Hasher = Blake2_128Concat,494		Key = CollectionId,495		Value = u32,496		QueryKind = ValueQuery,497	>;498499	/// List of collection admins500	#[pallet::storage]501	pub type IsAdmin<T: Config> = StorageNMap<502		Key = (503			Key<Blake2_128Concat, CollectionId>,504			Key<Blake2_128Concat, T::CrossAccountId>,505		),506		Value = bool,507		QueryKind = ValueQuery,508	>;509510	/// Allowlisted collection users511	#[pallet::storage]512	pub type Allowlist<T: Config> = StorageNMap<513		Key = (514			Key<Blake2_128Concat, CollectionId>,515			Key<Blake2_128Concat, T::CrossAccountId>,516		),517		Value = bool,518		QueryKind = ValueQuery,519	>;520521	/// Not used by code, exists only to provide some types to metadata522	#[pallet::storage]523	pub type DummyStorageValue<T: Config> = StorageValue<524		Value = (525			CollectionStats,526			CollectionId,527			TokenId,528			TokenChild,529			PhantomType<(530				TokenData<T::CrossAccountId>,531				RpcCollection<T::AccountId>,532				// RMRK533				RmrkCollectionInfo<T::AccountId>,534				RmrkInstanceInfo<T::AccountId>,535				RmrkResourceInfo,536				RmrkPropertyInfo,537				RmrkBaseInfo<T::AccountId>,538				RmrkPartType,539				RmrkTheme,540				RmrkNftChild,541			)>,542		),543		QueryKind = OptionQuery,544	>;545546	#[pallet::hooks]547	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {548		fn on_runtime_upgrade() -> Weight {549			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {550				use up_data_structs::{CollectionVersion1, CollectionVersion2};551				<CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {552					let mut props = Vec::new();553					if !v.offchain_schema.is_empty() {554						props.push(Property {555							key: b"_old_offchainSchema".to_vec().try_into().unwrap(),556							value: v557								.offchain_schema558								.clone()559								.into_inner()560								.try_into()561								.expect("offchain schema too big"),562						});563					}564					if !v.variable_on_chain_schema.is_empty() {565						props.push(Property {566							key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),567							value: v568								.variable_on_chain_schema569								.clone()570								.into_inner()571								.try_into()572								.expect("offchain schema too big"),573						});574					}575					if !v.const_on_chain_schema.is_empty() {576						props.push(Property {577							key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),578							value: v579								.const_on_chain_schema580								.clone()581								.into_inner()582								.try_into()583								.expect("offchain schema too big"),584						});585					}586					props.push(Property {587						key: b"_old_schemaVersion".to_vec().try_into().unwrap(),588						value: match v.schema_version {589							SchemaVersion::ImageURL => b"ImageUrl".as_slice(),590							SchemaVersion::Unique => b"Unique".as_slice(),591						}592						.to_vec()593						.try_into()594						.unwrap(),595					});596					Self::set_scoped_collection_properties(597						id,598						PropertyScope::None,599						props.into_iter(),600					)601					.expect("existing data larger than properties");602					let mut new = CollectionVersion2::from(v.clone());603					new.permissions.access = Some(v.access);604					new.permissions.mint_mode = Some(v.mint_mode);605					Some(new)606				});607			}608609			0610		}611	}612}613614impl<T: Config> Pallet<T> {615	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens616	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {617		ensure!(618			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,619			<Error<T>>::AddressIsZero620		);621		Ok(())622	}623	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {624		<IsAdmin<T>>::iter_prefix((collection,))625			.map(|(a, _)| a)626			.collect()627	}628	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {629		<Allowlist<T>>::iter_prefix((collection,))630			.map(|(a, _)| a)631			.collect()632	}633	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {634		<Allowlist<T>>::get((collection, user))635	}636	pub fn collection_stats() -> CollectionStats {637		let created = <CreatedCollectionCount<T>>::get();638		let destroyed = <DestroyedCollectionCount<T>>::get();639		CollectionStats {640			created: created.0,641			destroyed: destroyed.0,642			alive: created.0 - destroyed.0,643		}644	}645646	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {647		let collection = <CollectionById<T>>::get(collection);648		if collection.is_none() {649			return None;650		}651652		let collection = collection.unwrap();653		let limits = collection.limits;654		let effective_limits = CollectionLimits {655			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),656			sponsored_data_size: Some(limits.sponsored_data_size()),657			sponsored_data_rate_limit: Some(658				limits659					.sponsored_data_rate_limit660					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),661			),662			token_limit: Some(limits.token_limit()),663			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(664				match collection.mode {665					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,666					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,667					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,668				},669			)),670			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),671			owner_can_transfer: Some(limits.owner_can_transfer()),672			owner_can_destroy: Some(limits.owner_can_destroy()),673			transfers_enabled: Some(limits.transfers_enabled()),674		};675676		Some(effective_limits)677	}678679	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {680		let Collection {681			name,682			description,683			owner,684			mode,685			token_prefix,686			sponsorship,687			limits,688			permissions,689			external_collection,690		} = <CollectionById<T>>::get(collection)?;691692		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)693			.into_iter()694			.map(|(key, permission)| PropertyKeyPermission { key, permission })695			.collect();696697		let properties = <CollectionProperties<T>>::get(collection)698			.into_iter()699			.map(|(key, value)| Property { key, value })700			.collect();701702		let permissions = CollectionPermissions {703			access: Some(permissions.access()),704			mint_mode: Some(permissions.mint_mode()),705			nesting: Some(permissions.nesting().clone()),706		};707708		Some(RpcCollection {709			name: name.into_inner(),710			description: description.into_inner(),711			owner,712			mode,713			token_prefix: token_prefix.into_inner(),714			sponsorship,715			limits,716			permissions,717			token_property_permissions,718			properties,719			read_only: external_collection,720		})721	}722}723724macro_rules! limit_default {725	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{726		$(727			if let Some($new) = $new.$field {728				let $old = $old.$field($($arg)?);729				let _ = $new;730				let _ = $old;731				$check732			} else {733				$new.$field = $old.$field734			}735		)*736	}};737}738macro_rules! limit_default_clone {739	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{740		$(741			if let Some($new) = $new.$field.clone() {742				let $old = $old.$field($($arg)?);743				let _ = $new;744				let _ = $old;745				$check746			} else {747				$new.$field = $old.$field.clone()748			}749		)*750	}};751}752753impl<T: Config> Pallet<T> {754	pub fn init_collection(755		owner: T::CrossAccountId,756		data: CreateCollectionData<T::AccountId>,757	) -> Result<CollectionId, DispatchError> {758		{759			ensure!(760				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,761				Error::<T>::CollectionTokenPrefixLimitExceeded762			);763		}764765		let created_count = <CreatedCollectionCount<T>>::get()766			.0767			.checked_add(1)768			.ok_or(ArithmeticError::Overflow)?;769		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;770		let id = CollectionId(created_count);771772		// bound Total number of collections773		ensure!(774			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,775			<Error<T>>::TotalCollectionsLimitExceeded776		);777778		// =========779780		let collection = Collection {781			owner: owner.as_sub().clone(),782			name: data.name,783			mode: data.mode.clone(),784			description: data.description,785			token_prefix: data.token_prefix,786			sponsorship: data787				.pending_sponsor788				.map(SponsorshipState::Unconfirmed)789				.unwrap_or_default(),790			limits: data791				.limits792				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))793				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,794			permissions: data795				.permissions796				.map(|permissions| {797					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)798				})799				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,800			external_collection: false,801		};802803		let mut collection_properties = up_data_structs::CollectionProperties::get();804		collection_properties805			.try_set_from_iter(data.properties.into_iter())806			.map_err(<Error<T>>::from)?;807808		CollectionProperties::<T>::insert(id, collection_properties);809810		let mut token_props_permissions = PropertiesPermissionMap::new();811		token_props_permissions812			.try_set_from_iter(data.token_property_permissions.into_iter())813			.map_err(<Error<T>>::from)?;814815		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);816817		// Take a (non-refundable) deposit of collection creation818		{819			let mut imbalance =820				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();821			imbalance.subsume(822				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(823					&T::TreasuryAccountId::get(),824					T::CollectionCreationPrice::get(),825				),826			);827			<T as Config>::Currency::settle(828				&owner.as_sub(),829				imbalance,830				WithdrawReasons::TRANSFER,831				ExistenceRequirement::KeepAlive,832			)833			.map_err(|_| Error::<T>::NotSufficientFounds)?;834		}835836		<CreatedCollectionCount<T>>::put(created_count);837		<Pallet<T>>::deposit_event(Event::CollectionCreated(838			id,839			data.mode.id(),840			owner.as_sub().clone(),841		));842		<PalletEvm<T>>::deposit_log(843			erc::CollectionHelpersEvents::CollectionCreated {844				owner: *owner.as_eth(),845				collection_id: eth::collection_id_to_address(id),846			}847			.to_log(T::ContractAddress::get()),848		);849		<CollectionById<T>>::insert(id, collection);850		Ok(id)851	}852853	pub fn destroy_collection(854		collection: CollectionHandle<T>,855		sender: &T::CrossAccountId,856	) -> DispatchResult {857		collection.check_is_mutable()?;858		ensure!(859			collection.limits.owner_can_destroy(),860			<Error<T>>::NoPermission,861		);862		collection.check_is_owner(sender)?;863864		let destroyed_collections = <DestroyedCollectionCount<T>>::get()865			.0866			.checked_add(1)867			.ok_or(ArithmeticError::Overflow)?;868869		// =========870871		<DestroyedCollectionCount<T>>::put(destroyed_collections);872		<CollectionById<T>>::remove(collection.id);873		<AdminAmount<T>>::remove(collection.id);874		<IsAdmin<T>>::remove_prefix((collection.id,), None);875		<Allowlist<T>>::remove_prefix((collection.id,), None);876		<CollectionProperties<T>>::remove(collection.id);877878		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));879		Ok(())880	}881882	pub fn set_collection_property(883		collection: &CollectionHandle<T>,884		sender: &T::CrossAccountId,885		property: Property,886	) -> DispatchResult {887		collection.check_is_mutable()?;888		collection.check_is_owner_or_admin(sender)?;889890		CollectionProperties::<T>::try_mutate(collection.id, |properties| {891			let property = property.clone();892			properties.try_set(property.key, property.value)893		})894		.map_err(<Error<T>>::from)?;895896		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));897898		Ok(())899	}900901	pub fn set_scoped_collection_property(902		collection_id: CollectionId,903		scope: PropertyScope,904		property: Property,905	) -> DispatchResult {906		CollectionProperties::<T>::try_mutate(collection_id, |properties| {907			properties.try_scoped_set(scope, property.key, property.value)908		})909		.map_err(<Error<T>>::from)?;910911		Ok(())912	}913914	pub fn set_scoped_collection_properties(915		collection_id: CollectionId,916		scope: PropertyScope,917		properties: impl Iterator<Item = Property>,918	) -> DispatchResult {919		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {920			stored_properties.try_scoped_set_from_iter(scope, properties)921		})922		.map_err(<Error<T>>::from)?;923924		Ok(())925	}926927	#[transactional]928	pub fn set_collection_properties(929		collection: &CollectionHandle<T>,930		sender: &T::CrossAccountId,931		properties: Vec<Property>,932	) -> DispatchResult {933		collection.check_is_mutable()?;934935		for property in properties {936			Self::set_collection_property(collection, sender, property)?;937		}938939		Ok(())940	}941942	pub fn delete_collection_property(943		collection: &CollectionHandle<T>,944		sender: &T::CrossAccountId,945		property_key: PropertyKey,946	) -> DispatchResult {947		collection.check_is_mutable()?;948		collection.check_is_owner_or_admin(sender)?;949950		CollectionProperties::<T>::try_mutate(collection.id, |properties| {951			properties.remove(&property_key)952		})953		.map_err(<Error<T>>::from)?;954955		Self::deposit_event(Event::CollectionPropertyDeleted(956			collection.id,957			property_key,958		));959960		Ok(())961	}962963	#[transactional]964	pub fn delete_collection_properties(965		collection: &CollectionHandle<T>,966		sender: &T::CrossAccountId,967		property_keys: Vec<PropertyKey>,968	) -> DispatchResult {969		collection.check_is_mutable()?;970971		for key in property_keys {972			Self::delete_collection_property(collection, sender, key)?;973		}974975		Ok(())976	}977978	// For migrations979	pub fn set_property_permission_unchecked(980		collection: CollectionId,981		property_permission: PropertyKeyPermission,982	) -> DispatchResult {983		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {984			permissions.try_set(property_permission.key, property_permission.permission)985		})986		.map_err(<Error<T>>::from)?;987		Ok(())988	}989990	pub fn set_property_permission(991		collection: &CollectionHandle<T>,992		sender: &T::CrossAccountId,993		property_permission: PropertyKeyPermission,994	) -> DispatchResult {995		collection.check_is_mutable()?;996		collection.check_is_owner_or_admin(sender)?;997998		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);999		let current_permission = all_permissions.get(&property_permission.key);1000		if matches![1001			current_permission,1002			Some(PropertyPermission { mutable: false, .. })1003		] {1004			return Err(<Error<T>>::NoPermission.into());1005		}10061007		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1008			let property_permission = property_permission.clone();1009			permissions.try_set(property_permission.key, property_permission.permission)1010		})1011		.map_err(<Error<T>>::from)?;10121013		Self::deposit_event(Event::PropertyPermissionSet(1014			collection.id,1015			property_permission.key,1016		));10171018		Ok(())1019	}10201021	#[transactional]1022	pub fn set_property_permissions(1023		collection: &CollectionHandle<T>,1024		sender: &T::CrossAccountId,1025		property_permissions: Vec<PropertyKeyPermission>,1026	) -> DispatchResult {1027		collection.check_is_mutable()?;10281029		for prop_pemission in property_permissions {1030			Self::set_property_permission(collection, sender, prop_pemission)?;1031		}10321033		Ok(())1034	}10351036	pub fn get_collection_property(1037		collection_id: CollectionId,1038		key: &PropertyKey,1039	) -> Option<PropertyValue> {1040		Self::collection_properties(collection_id).get(key).cloned()1041	}10421043	pub fn bytes_keys_to_property_keys(1044		keys: Vec<Vec<u8>>,1045	) -> Result<Vec<PropertyKey>, DispatchError> {1046		keys.into_iter()1047			.map(|key| -> Result<PropertyKey, DispatchError> {1048				key.try_into()1049					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1050			})1051			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1052	}10531054	pub fn filter_collection_properties(1055		collection_id: CollectionId,1056		keys: Option<Vec<PropertyKey>>,1057	) -> Result<Vec<Property>, DispatchError> {1058		let properties = Self::collection_properties(collection_id);10591060		let properties = keys1061			.map(|keys| {1062				keys.into_iter()1063					.filter_map(|key| {1064						properties.get(&key).map(|value| Property {1065							key,1066							value: value.clone(),1067						})1068					})1069					.collect()1070			})1071			.unwrap_or_else(|| {1072				properties1073					.into_iter()1074					.map(|(key, value)| Property { key, value })1075					.collect()1076			});10771078		Ok(properties)1079	}10801081	pub fn filter_property_permissions(1082		collection_id: CollectionId,1083		keys: Option<Vec<PropertyKey>>,1084	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1085		let permissions = Self::property_permissions(collection_id);10861087		let key_permissions = keys1088			.map(|keys| {1089				keys.into_iter()1090					.filter_map(|key| {1091						permissions1092							.get(&key)1093							.map(|permission| PropertyKeyPermission {1094								key,1095								permission: permission.clone(),1096							})1097					})1098					.collect()1099			})1100			.unwrap_or_else(|| {1101				permissions1102					.into_iter()1103					.map(|(key, permission)| PropertyKeyPermission { key, permission })1104					.collect()1105			});11061107		Ok(key_permissions)1108	}11091110	pub fn toggle_allowlist(1111		collection: &CollectionHandle<T>,1112		sender: &T::CrossAccountId,1113		user: &T::CrossAccountId,1114		allowed: bool,1115	) -> DispatchResult {1116		collection.check_is_mutable()?;1117		collection.check_is_owner_or_admin(sender)?;11181119		// =========11201121		if allowed {1122			<Allowlist<T>>::insert((collection.id, user), true);1123		} else {1124			<Allowlist<T>>::remove((collection.id, user));1125		}11261127		Ok(())1128	}11291130	pub fn toggle_admin(1131		collection: &CollectionHandle<T>,1132		sender: &T::CrossAccountId,1133		user: &T::CrossAccountId,1134		admin: bool,1135	) -> DispatchResult {1136		collection.check_is_mutable()?;1137		collection.check_is_owner_or_admin(sender)?;11381139		let was_admin = <IsAdmin<T>>::get((collection.id, user));1140		if was_admin == admin {1141			return Ok(());1142		}1143		let amount = <AdminAmount<T>>::get(collection.id);11441145		if admin {1146			let amount = amount1147				.checked_add(1)1148				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1149			ensure!(1150				amount <= Self::collection_admins_limit(),1151				<Error<T>>::CollectionAdminCountExceeded,1152			);11531154			// =========11551156			<AdminAmount<T>>::insert(collection.id, amount);1157			<IsAdmin<T>>::insert((collection.id, user), true);1158		} else {1159			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1160			<IsAdmin<T>>::remove((collection.id, user));1161		}11621163		Ok(())1164	}11651166	pub fn clamp_limits(1167		mode: CollectionMode,1168		old_limit: &CollectionLimits,1169		mut new_limit: CollectionLimits,1170	) -> Result<CollectionLimits, DispatchError> {1171		limit_default!(old_limit, new_limit,1172			account_token_ownership_limit => ensure!(1173				new_limit <= MAX_TOKEN_OWNERSHIP,1174				<Error<T>>::CollectionLimitBoundsExceeded,1175			),1176			sponsored_data_size => ensure!(1177				new_limit <= CUSTOM_DATA_LIMIT,1178				<Error<T>>::CollectionLimitBoundsExceeded,1179			),11801181			sponsored_data_rate_limit => {},1182			token_limit => ensure!(1183				old_limit >= new_limit && new_limit > 0,1184				<Error<T>>::CollectionTokenLimitExceeded1185			),11861187			sponsor_transfer_timeout(match mode {1188				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1189				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1190				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1191			}) => ensure!(1192				new_limit <= MAX_SPONSOR_TIMEOUT,1193				<Error<T>>::CollectionLimitBoundsExceeded,1194			),1195			sponsor_approve_timeout => {},1196			owner_can_transfer => ensure!(1197				old_limit || !new_limit,1198				<Error<T>>::OwnerPermissionsCantBeReverted,1199			),1200			owner_can_destroy => ensure!(1201				old_limit || !new_limit,1202				<Error<T>>::OwnerPermissionsCantBeReverted,1203			),1204			transfers_enabled => {},1205		);1206		Ok(new_limit)1207	}12081209	pub fn clamp_permissions(1210		_mode: CollectionMode,1211		old_limit: &CollectionPermissions,1212		mut new_limit: CollectionPermissions,1213	) -> Result<CollectionPermissions, DispatchError> {1214		limit_default_clone!(old_limit, new_limit,1215			access => {},1216			mint_mode => {},1217			nesting => {},1218		);1219		Ok(new_limit)1220	}1221}12221223#[macro_export]1224macro_rules! unsupported {1225	() => {1226		Err(<Error<T>>::UnsupportedOperation.into())1227	};1228}12291230/// Worst cases1231pub trait CommonWeightInfo<CrossAccountId> {1232	fn create_item() -> Weight;1233	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1234	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1235	fn burn_item() -> Weight;1236	fn set_collection_properties(amount: u32) -> Weight;1237	fn delete_collection_properties(amount: u32) -> Weight;1238	fn set_token_properties(amount: u32) -> Weight;1239	fn delete_token_properties(amount: u32) -> Weight;1240	fn set_property_permissions(amount: u32) -> Weight;1241	fn transfer() -> Weight;1242	fn approve() -> Weight;1243	fn transfer_from() -> Weight;1244	fn burn_from() -> Weight;12451246	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1247	/// whole users's balance1248	///1249	/// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead1250	fn burn_recursively_self_raw() -> Weight;1251	/// Cost of iterating over `amount` children while burning, without counting child burning itself1252	///1253	/// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead1254	fn burn_recursively_breadth_raw(amount: u32) -> Weight;12551256	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1257		Self::burn_recursively_self_raw()1258			.saturating_mul(max_selfs.max(1) as u64)1259			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1260	}1261}12621263pub trait CommonCollectionOperations<T: Config> {1264	fn create_item(1265		&self,1266		sender: T::CrossAccountId,1267		to: T::CrossAccountId,1268		data: CreateItemData,1269		nesting_budget: &dyn Budget,1270	) -> DispatchResultWithPostInfo;1271	fn create_multiple_items(1272		&self,1273		sender: T::CrossAccountId,1274		to: T::CrossAccountId,1275		data: Vec<CreateItemData>,1276		nesting_budget: &dyn Budget,1277	) -> DispatchResultWithPostInfo;1278	fn create_multiple_items_ex(1279		&self,1280		sender: T::CrossAccountId,1281		data: CreateItemExData<T::CrossAccountId>,1282		nesting_budget: &dyn Budget,1283	) -> DispatchResultWithPostInfo;1284	fn burn_item(1285		&self,1286		sender: T::CrossAccountId,1287		token: TokenId,1288		amount: u128,1289	) -> DispatchResultWithPostInfo;1290	fn burn_item_recursively(1291		&self,1292		sender: T::CrossAccountId,1293		token: TokenId,1294		self_budget: &dyn Budget,1295		breadth_budget: &dyn Budget,1296	) -> DispatchResultWithPostInfo;1297	fn set_collection_properties(1298		&self,1299		sender: T::CrossAccountId,1300		properties: Vec<Property>,1301	) -> DispatchResultWithPostInfo;1302	fn delete_collection_properties(1303		&self,1304		sender: &T::CrossAccountId,1305		property_keys: Vec<PropertyKey>,1306	) -> DispatchResultWithPostInfo;1307	fn set_token_properties(1308		&self,1309		sender: T::CrossAccountId,1310		token_id: TokenId,1311		property: Vec<Property>,1312	) -> DispatchResultWithPostInfo;1313	fn delete_token_properties(1314		&self,1315		sender: T::CrossAccountId,1316		token_id: TokenId,1317		property_keys: Vec<PropertyKey>,1318	) -> DispatchResultWithPostInfo;1319	fn set_property_permissions(1320		&self,1321		sender: &T::CrossAccountId,1322		property_permissions: Vec<PropertyKeyPermission>,1323	) -> DispatchResultWithPostInfo;1324	fn transfer(1325		&self,1326		sender: T::CrossAccountId,1327		to: T::CrossAccountId,1328		token: TokenId,1329		amount: u128,1330		nesting_budget: &dyn Budget,1331	) -> DispatchResultWithPostInfo;1332	fn approve(1333		&self,1334		sender: T::CrossAccountId,1335		spender: T::CrossAccountId,1336		token: TokenId,1337		amount: u128,1338	) -> DispatchResultWithPostInfo;1339	fn transfer_from(1340		&self,1341		sender: T::CrossAccountId,1342		from: T::CrossAccountId,1343		to: T::CrossAccountId,1344		token: TokenId,1345		amount: u128,1346		nesting_budget: &dyn Budget,1347	) -> DispatchResultWithPostInfo;1348	fn burn_from(1349		&self,1350		sender: T::CrossAccountId,1351		from: T::CrossAccountId,1352		token: TokenId,1353		amount: u128,1354		nesting_budget: &dyn Budget,1355	) -> DispatchResultWithPostInfo;13561357	fn check_nesting(1358		&self,1359		sender: T::CrossAccountId,1360		from: (CollectionId, TokenId),1361		under: TokenId,1362		budget: &dyn Budget,1363	) -> DispatchResult;13641365	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13661367	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13681369	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1370	fn collection_tokens(&self) -> Vec<TokenId>;1371	fn token_exists(&self, token: TokenId) -> bool;1372	fn last_token_id(&self) -> TokenId;13731374	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1375	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1376	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1377	/// Amount of unique collection tokens1378	fn total_supply(&self) -> u32;1379	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1380	fn account_balance(&self, account: T::CrossAccountId) -> u32;1381	/// Amount of specific token account have (Applicable to fungible/refungible)1382	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1383	fn allowance(1384		&self,1385		sender: T::CrossAccountId,1386		spender: T::CrossAccountId,1387		token: TokenId,1388	) -> u128;1389}13901391// Flexible enough for implementing CommonCollectionOperations1392pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1393	let post_info = PostDispatchInfo {1394		actual_weight: Some(weight),1395		pays_fee: Pays::Yes,1396	};1397	match res {1398		Ok(()) => Ok(post_info),1399		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1400	}1401}14021403impl<T: Config> From<PropertiesError> for Error<T> {1404	fn from(error: PropertiesError) -> Self {1405		match error {1406			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1407			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1408			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1409			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1410			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1411		}1412	}1413}
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	}151	pub fn save(self) -> Result<(), DispatchError> {152		<CollectionById<T>>::insert(self.id, self.collection);153		Ok(())154	}155156	pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {157		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);158		Ok(())159	}160161	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {162		if self.collection.sponsorship.pending_sponsor() != Some(sender) {163			return Ok(false);164		}165166		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());167		Ok(true)168	}169170	/// Checks that the collection was created with, and must be operated upon through **Unique API**.171	/// Now check only the `external_collection` flag and if it's **true**, then return `CollectionIsExternal` error.172	pub fn check_is_internal(&self) -> DispatchResult {173		if self.external_collection {174			return Err(<Error<T>>::CollectionIsExternal)?;175		}176177		Ok(())178	}179180	/// Checks that the collection was created with, and must be operated upon through an **assimilated API**.181	/// Now check only the `external_collection` flag and if it's **false**, then return `CollectionIsInternal` error.182	pub fn check_is_external(&self) -> DispatchResult {183		if !self.external_collection {184			return Err(<Error<T>>::CollectionIsInternal)?;185		}186187		Ok(())188	}189}190191impl<T: Config> Deref for CollectionHandle<T> {192	type Target = Collection<T::AccountId>;193194	fn deref(&self) -> &Self::Target {195		&self.collection196	}197}198199impl<T: Config> DerefMut for CollectionHandle<T> {200	fn deref_mut(&mut self) -> &mut Self::Target {201		&mut self.collection202	}203}204205impl<T: Config> CollectionHandle<T> {206	pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {207		ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);208		Ok(())209	}210	pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {211		*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))212	}213	pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {214		ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);215		Ok(())216	}217	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {218		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)219	}220	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {221		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)222	}223	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {224		ensure!(225			<Allowlist<T>>::get((self.id, user)),226			<Error<T>>::AddressNotInAllowlist227		);228		Ok(())229	}230}231232#[frame_support::pallet]233pub mod pallet {234	use super::*;235	use pallet_evm::account;236	use dispatch::CollectionDispatch;237	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};238	use frame_system::pallet_prelude::*;239	use frame_support::traits::Currency;240	use up_data_structs::{TokenId, mapping::TokenAddressMapping};241	use scale_info::TypeInfo;242	use weights::WeightInfo;243244	#[pallet::config]245	pub trait Config:246		frame_system::Config247		+ pallet_evm_coder_substrate::Config248		+ pallet_evm::Config249		+ TypeInfo250		+ account::Config251	{252		type WeightInfo: WeightInfo;253		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;254255		type Currency: Currency<Self::AccountId>;256257		#[pallet::constant]258		type CollectionCreationPrice: Get<259			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,260		>;261		type CollectionDispatch: CollectionDispatch<Self>;262263		type TreasuryAccountId: Get<Self::AccountId>;264		type ContractAddress: Get<H160>;265266		type EvmTokenAddressMapping: TokenAddressMapping<H160>;267		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;268	}269270	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);271272	#[pallet::pallet]273	#[pallet::storage_version(STORAGE_VERSION)]274	#[pallet::generate_store(pub(super) trait Store)]275	pub struct Pallet<T>(_);276277	#[pallet::extra_constants]278	impl<T: Config> Pallet<T> {279		pub fn collection_admins_limit() -> u32 {280			COLLECTION_ADMINS_LIMIT281		}282	}283284	#[pallet::event]285	#[pallet::generate_deposit(pub fn deposit_event)]286	pub enum Event<T: Config> {287		/// New collection was created288		///289		/// # Arguments290		///291		/// * collection_id: Globally unique identifier of newly created collection.292		///293		/// * mode: [CollectionMode] converted into u8.294		///295		/// * account_id: Collection owner.296		CollectionCreated(CollectionId, u8, T::AccountId),297298		/// New collection was destroyed299		///300		/// # Arguments301		///302		/// * collection_id: Globally unique identifier of collection.303		CollectionDestroyed(CollectionId),304305		/// New item was created.306		///307		/// # Arguments308		///309		/// * collection_id: Id of the collection where item was created.310		///311		/// * item_id: Id of an item. Unique within the collection.312		///313		/// * recipient: Owner of newly created item314		///315		/// * amount: Always 1 for NFT316		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),317318		/// Collection item was burned.319		///320		/// # Arguments321		///322		/// * collection_id.323		///324		/// * item_id: Identifier of burned NFT.325		///326		/// * owner: which user has destroyed its tokens327		///328		/// * amount: Always 1 for NFT329		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),330331		/// Item was transferred332		///333		/// * collection_id: Id of collection to which item is belong334		///335		/// * item_id: Id of an item336		///337		/// * sender: Original owner of item338		///339		/// * recipient: New owner of item340		///341		/// * amount: Always 1 for NFT342		Transfer(343			CollectionId,344			TokenId,345			T::CrossAccountId,346			T::CrossAccountId,347			u128,348		),349350		/// * collection_id351		///352		/// * item_id353		///354		/// * sender355		///356		/// * spender357		///358		/// * amount359		Approved(360			CollectionId,361			TokenId,362			T::CrossAccountId,363			T::CrossAccountId,364			u128,365		),366367		CollectionPropertySet(CollectionId, PropertyKey),368369		CollectionPropertyDeleted(CollectionId, PropertyKey),370371		TokenPropertySet(CollectionId, TokenId, PropertyKey),372373		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),374375		PropertyPermissionSet(CollectionId, PropertyKey),376	}377378	#[pallet::error]379	pub enum Error<T> {380		/// This collection does not exist.381		CollectionNotFound,382		/// Sender parameter and item owner must be equal.383		MustBeTokenOwner,384		/// No permission to perform action385		NoPermission,386		/// Destroying only empty collections is allowed387		CantDestroyNotEmptyCollection,388		/// Collection is not in mint mode.389		PublicMintingNotAllowed,390		/// Address is not in allow list.391		AddressNotInAllowlist,392393		/// Collection name can not be longer than 63 char.394		CollectionNameLimitExceeded,395		/// Collection description can not be longer than 255 char.396		CollectionDescriptionLimitExceeded,397		/// Token prefix can not be longer than 15 char.398		CollectionTokenPrefixLimitExceeded,399		/// Total collections bound exceeded.400		TotalCollectionsLimitExceeded,401		/// Exceeded max admin count402		CollectionAdminCountExceeded,403		/// Collection limit bounds per collection exceeded404		CollectionLimitBoundsExceeded,405		/// Tried to enable permissions which are only permitted to be disabled406		OwnerPermissionsCantBeReverted,407		/// Collection settings not allowing items transferring408		TransferNotAllowed,409		/// Account token limit exceeded per collection410		AccountTokenLimitExceeded,411		/// Collection token limit exceeded412		CollectionTokenLimitExceeded,413		/// Metadata flag frozen414		MetadataFlagFrozen,415416		/// Item not exists.417		TokenNotFound,418		/// Item balance not enough.419		TokenValueTooLow,420		/// Requested value more than approved.421		ApprovedValueTooLow,422		/// Tried to approve more than owned423		CantApproveMoreThanOwned,424425		/// Can't transfer tokens to ethereum zero address426		AddressIsZero,427		/// Target collection doesn't supports this operation428		UnsupportedOperation,429430		/// Not sufficient founds to perform action431		NotSufficientFounds,432433		/// Collection has nesting disabled434		NestingIsDisabled,435		/// Only owner may nest tokens under this collection436		OnlyOwnerAllowedToNest,437		/// Only tokens from specific collections may nest tokens under this438		SourceCollectionIsNotAllowedToNest,439440		/// Tried to store more data than allowed in collection field441		CollectionFieldSizeExceeded,442443		/// Tried to store more property data than allowed444		NoSpaceForProperty,445446		/// Tried to store more property keys than allowed447		PropertyLimitReached,448449		/// Property key is too long450		PropertyKeyIsTooLong,451452		/// Only ASCII letters, digits, and '_', '-' are allowed453		InvalidCharacterInPropertyKey,454455		/// Empty property keys are forbidden456		EmptyPropertyKey,457458		/// Tried to access an external collection with an internal API459		CollectionIsExternal,460461		/// Tried to access an internal collection with an external API462		CollectionIsInternal,463	}464465	#[pallet::storage]466	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;467	#[pallet::storage]468	pub type DestroyedCollectionCount<T> =469		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;470471	/// Collection info472	#[pallet::storage]473	pub type CollectionById<T> = StorageMap<474		Hasher = Blake2_128Concat,475		Key = CollectionId,476		Value = Collection<<T as frame_system::Config>::AccountId>,477		QueryKind = OptionQuery,478	>;479480	/// Collection properties481	#[pallet::storage]482	#[pallet::getter(fn collection_properties)]483	pub type CollectionProperties<T> = StorageMap<484		Hasher = Blake2_128Concat,485		Key = CollectionId,486		Value = Properties,487		QueryKind = ValueQuery,488		OnEmpty = up_data_structs::CollectionProperties,489	>;490491	#[pallet::storage]492	#[pallet::getter(fn property_permissions)]493	pub type CollectionPropertyPermissions<T> = StorageMap<494		Hasher = Blake2_128Concat,495		Key = CollectionId,496		Value = PropertiesPermissionMap,497		QueryKind = ValueQuery,498	>;499500	#[pallet::storage]501	pub type AdminAmount<T> = StorageMap<502		Hasher = Blake2_128Concat,503		Key = CollectionId,504		Value = u32,505		QueryKind = ValueQuery,506	>;507508	/// List of collection admins509	#[pallet::storage]510	pub type IsAdmin<T: Config> = StorageNMap<511		Key = (512			Key<Blake2_128Concat, CollectionId>,513			Key<Blake2_128Concat, T::CrossAccountId>,514		),515		Value = bool,516		QueryKind = ValueQuery,517	>;518519	/// Allowlisted collection users520	#[pallet::storage]521	pub type Allowlist<T: Config> = StorageNMap<522		Key = (523			Key<Blake2_128Concat, CollectionId>,524			Key<Blake2_128Concat, T::CrossAccountId>,525		),526		Value = bool,527		QueryKind = ValueQuery,528	>;529530	/// Not used by code, exists only to provide some types to metadata531	#[pallet::storage]532	pub type DummyStorageValue<T: Config> = StorageValue<533		Value = (534			CollectionStats,535			CollectionId,536			TokenId,537			TokenChild,538			PhantomType<(539				TokenData<T::CrossAccountId>,540				RpcCollection<T::AccountId>,541				// RMRK542				RmrkCollectionInfo<T::AccountId>,543				RmrkInstanceInfo<T::AccountId>,544				RmrkResourceInfo,545				RmrkPropertyInfo,546				RmrkBaseInfo<T::AccountId>,547				RmrkPartType,548				RmrkTheme,549				RmrkNftChild,550			)>,551		),552		QueryKind = OptionQuery,553	>;554555	#[pallet::hooks]556	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {557		fn on_runtime_upgrade() -> Weight {558			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {559				use up_data_structs::{CollectionVersion1, CollectionVersion2};560				<CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {561					let mut props = Vec::new();562					if !v.offchain_schema.is_empty() {563						props.push(Property {564							key: b"_old_offchainSchema".to_vec().try_into().unwrap(),565							value: v566								.offchain_schema567								.clone()568								.into_inner()569								.try_into()570								.expect("offchain schema too big"),571						});572					}573					if !v.variable_on_chain_schema.is_empty() {574						props.push(Property {575							key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),576							value: v577								.variable_on_chain_schema578								.clone()579								.into_inner()580								.try_into()581								.expect("offchain schema too big"),582						});583					}584					if !v.const_on_chain_schema.is_empty() {585						props.push(Property {586							key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),587							value: v588								.const_on_chain_schema589								.clone()590								.into_inner()591								.try_into()592								.expect("offchain schema too big"),593						});594					}595					props.push(Property {596						key: b"_old_schemaVersion".to_vec().try_into().unwrap(),597						value: match v.schema_version {598							SchemaVersion::ImageURL => b"ImageUrl".as_slice(),599							SchemaVersion::Unique => b"Unique".as_slice(),600						}601						.to_vec()602						.try_into()603						.unwrap(),604					});605					Self::set_scoped_collection_properties(606						id,607						PropertyScope::None,608						props.into_iter(),609					)610					.expect("existing data larger than properties");611					let mut new = CollectionVersion2::from(v.clone());612					new.permissions.access = Some(v.access);613					new.permissions.mint_mode = Some(v.mint_mode);614					Some(new)615				});616			}617618			0619		}620	}621}622623impl<T: Config> Pallet<T> {624	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens625	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {626		ensure!(627			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,628			<Error<T>>::AddressIsZero629		);630		Ok(())631	}632	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {633		<IsAdmin<T>>::iter_prefix((collection,))634			.map(|(a, _)| a)635			.collect()636	}637	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {638		<Allowlist<T>>::iter_prefix((collection,))639			.map(|(a, _)| a)640			.collect()641	}642	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {643		<Allowlist<T>>::get((collection, user))644	}645	pub fn collection_stats() -> CollectionStats {646		let created = <CreatedCollectionCount<T>>::get();647		let destroyed = <DestroyedCollectionCount<T>>::get();648		CollectionStats {649			created: created.0,650			destroyed: destroyed.0,651			alive: created.0 - destroyed.0,652		}653	}654655	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {656		let collection = <CollectionById<T>>::get(collection);657		if collection.is_none() {658			return None;659		}660661		let collection = collection.unwrap();662		let limits = collection.limits;663		let effective_limits = CollectionLimits {664			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),665			sponsored_data_size: Some(limits.sponsored_data_size()),666			sponsored_data_rate_limit: Some(667				limits668					.sponsored_data_rate_limit669					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),670			),671			token_limit: Some(limits.token_limit()),672			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(673				match collection.mode {674					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,675					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,676					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,677				},678			)),679			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),680			owner_can_transfer: Some(limits.owner_can_transfer()),681			owner_can_destroy: Some(limits.owner_can_destroy()),682			transfers_enabled: Some(limits.transfers_enabled()),683		};684685		Some(effective_limits)686	}687688	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {689		let Collection {690			name,691			description,692			owner,693			mode,694			token_prefix,695			sponsorship,696			limits,697			permissions,698			external_collection,699		} = <CollectionById<T>>::get(collection)?;700701		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)702			.into_iter()703			.map(|(key, permission)| PropertyKeyPermission { key, permission })704			.collect();705706		let properties = <CollectionProperties<T>>::get(collection)707			.into_iter()708			.map(|(key, value)| Property { key, value })709			.collect();710711		let permissions = CollectionPermissions {712			access: Some(permissions.access()),713			mint_mode: Some(permissions.mint_mode()),714			nesting: Some(permissions.nesting().clone()),715		};716717		Some(RpcCollection {718			name: name.into_inner(),719			description: description.into_inner(),720			owner,721			mode,722			token_prefix: token_prefix.into_inner(),723			sponsorship,724			limits,725			permissions,726			token_property_permissions,727			properties,728			read_only: external_collection,729		})730	}731}732733macro_rules! limit_default {734	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{735		$(736			if let Some($new) = $new.$field {737				let $old = $old.$field($($arg)?);738				let _ = $new;739				let _ = $old;740				$check741			} else {742				$new.$field = $old.$field743			}744		)*745	}};746}747macro_rules! limit_default_clone {748	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{749		$(750			if let Some($new) = $new.$field.clone() {751				let $old = $old.$field($($arg)?);752				let _ = $new;753				let _ = $old;754				$check755			} else {756				$new.$field = $old.$field.clone()757			}758		)*759	}};760}761762impl<T: Config> Pallet<T> {763	pub fn init_collection(764		owner: T::CrossAccountId,765		data: CreateCollectionData<T::AccountId>,766		is_external: bool,767	) -> Result<CollectionId, DispatchError> {768		{769			ensure!(770				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,771				Error::<T>::CollectionTokenPrefixLimitExceeded772			);773		}774775		let created_count = <CreatedCollectionCount<T>>::get()776			.0777			.checked_add(1)778			.ok_or(ArithmeticError::Overflow)?;779		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;780		let id = CollectionId(created_count);781782		// bound Total number of collections783		ensure!(784			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,785			<Error<T>>::TotalCollectionsLimitExceeded786		);787788		// =========789790		let collection = Collection {791			owner: owner.as_sub().clone(),792			name: data.name,793			mode: data.mode.clone(),794			description: data.description,795			token_prefix: data.token_prefix,796			sponsorship: data797				.pending_sponsor798				.map(SponsorshipState::Unconfirmed)799				.unwrap_or_default(),800			limits: data801				.limits802				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))803				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,804			permissions: data805				.permissions806				.map(|permissions| {807					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)808				})809				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,810			external_collection: is_external,811		};812813		let mut collection_properties = up_data_structs::CollectionProperties::get();814		collection_properties815			.try_set_from_iter(data.properties.into_iter())816			.map_err(<Error<T>>::from)?;817818		CollectionProperties::<T>::insert(id, collection_properties);819820		let mut token_props_permissions = PropertiesPermissionMap::new();821		token_props_permissions822			.try_set_from_iter(data.token_property_permissions.into_iter())823			.map_err(<Error<T>>::from)?;824825		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);826827		// Take a (non-refundable) deposit of collection creation828		{829			let mut imbalance =830				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();831			imbalance.subsume(832				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(833					&T::TreasuryAccountId::get(),834					T::CollectionCreationPrice::get(),835				),836			);837			<T as Config>::Currency::settle(838				&owner.as_sub(),839				imbalance,840				WithdrawReasons::TRANSFER,841				ExistenceRequirement::KeepAlive,842			)843			.map_err(|_| Error::<T>::NotSufficientFounds)?;844		}845846		<CreatedCollectionCount<T>>::put(created_count);847		<Pallet<T>>::deposit_event(Event::CollectionCreated(848			id,849			data.mode.id(),850			owner.as_sub().clone(),851		));852		<PalletEvm<T>>::deposit_log(853			erc::CollectionHelpersEvents::CollectionCreated {854				owner: *owner.as_eth(),855				collection_id: eth::collection_id_to_address(id),856			}857			.to_log(T::ContractAddress::get()),858		);859		<CollectionById<T>>::insert(id, collection);860		Ok(id)861	}862863	pub fn destroy_collection(864		collection: CollectionHandle<T>,865		sender: &T::CrossAccountId,866	) -> DispatchResult {867		ensure!(868			collection.limits.owner_can_destroy(),869			<Error<T>>::NoPermission,870		);871		collection.check_is_owner(sender)?;872873		let destroyed_collections = <DestroyedCollectionCount<T>>::get()874			.0875			.checked_add(1)876			.ok_or(ArithmeticError::Overflow)?;877878		// =========879880		<DestroyedCollectionCount<T>>::put(destroyed_collections);881		<CollectionById<T>>::remove(collection.id);882		<AdminAmount<T>>::remove(collection.id);883		<IsAdmin<T>>::remove_prefix((collection.id,), None);884		<Allowlist<T>>::remove_prefix((collection.id,), None);885		<CollectionProperties<T>>::remove(collection.id);886887		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));888		Ok(())889	}890891	pub fn set_collection_property(892		collection: &CollectionHandle<T>,893		sender: &T::CrossAccountId,894		property: Property,895	) -> DispatchResult {896		collection.check_is_owner_or_admin(sender)?;897898		CollectionProperties::<T>::try_mutate(collection.id, |properties| {899			let property = property.clone();900			properties.try_set(property.key, property.value)901		})902		.map_err(<Error<T>>::from)?;903904		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));905906		Ok(())907	}908909	pub fn set_scoped_collection_property(910		collection_id: CollectionId,911		scope: PropertyScope,912		property: Property,913	) -> DispatchResult {914		CollectionProperties::<T>::try_mutate(collection_id, |properties| {915			properties.try_scoped_set(scope, property.key, property.value)916		})917		.map_err(<Error<T>>::from)?;918919		Ok(())920	}921922	pub fn set_scoped_collection_properties(923		collection_id: CollectionId,924		scope: PropertyScope,925		properties: impl Iterator<Item = Property>,926	) -> DispatchResult {927		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {928			stored_properties.try_scoped_set_from_iter(scope, properties)929		})930		.map_err(<Error<T>>::from)?;931932		Ok(())933	}934935	#[transactional]936	pub fn set_collection_properties(937		collection: &CollectionHandle<T>,938		sender: &T::CrossAccountId,939		properties: Vec<Property>,940	) -> DispatchResult {941		for property in properties {942			Self::set_collection_property(collection, sender, property)?;943		}944945		Ok(())946	}947948	pub fn delete_collection_property(949		collection: &CollectionHandle<T>,950		sender: &T::CrossAccountId,951		property_key: PropertyKey,952	) -> DispatchResult {953		collection.check_is_owner_or_admin(sender)?;954955		CollectionProperties::<T>::try_mutate(collection.id, |properties| {956			properties.remove(&property_key)957		})958		.map_err(<Error<T>>::from)?;959960		Self::deposit_event(Event::CollectionPropertyDeleted(961			collection.id,962			property_key,963		));964965		Ok(())966	}967968	#[transactional]969	pub fn delete_collection_properties(970		collection: &CollectionHandle<T>,971		sender: &T::CrossAccountId,972		property_keys: Vec<PropertyKey>,973	) -> DispatchResult {974		for key in property_keys {975			Self::delete_collection_property(collection, sender, key)?;976		}977978		Ok(())979	}980981	// For migrations982	pub fn set_property_permission_unchecked(983		collection: CollectionId,984		property_permission: PropertyKeyPermission,985	) -> DispatchResult {986		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {987			permissions.try_set(property_permission.key, property_permission.permission)988		})989		.map_err(<Error<T>>::from)?;990		Ok(())991	}992993	pub fn set_property_permission(994		collection: &CollectionHandle<T>,995		sender: &T::CrossAccountId,996		property_permission: PropertyKeyPermission,997	) -> DispatchResult {998		collection.check_is_owner_or_admin(sender)?;9991000		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1001		let current_permission = all_permissions.get(&property_permission.key);1002		if matches![1003			current_permission,1004			Some(PropertyPermission { mutable: false, .. })1005		] {1006			return Err(<Error<T>>::NoPermission.into());1007		}10081009		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1010			let property_permission = property_permission.clone();1011			permissions.try_set(property_permission.key, property_permission.permission)1012		})1013		.map_err(<Error<T>>::from)?;10141015		Self::deposit_event(Event::PropertyPermissionSet(1016			collection.id,1017			property_permission.key,1018		));10191020		Ok(())1021	}10221023	#[transactional]1024	pub fn set_property_permissions(1025		collection: &CollectionHandle<T>,1026		sender: &T::CrossAccountId,1027		property_permissions: Vec<PropertyKeyPermission>,1028	) -> DispatchResult {1029		for prop_pemission in property_permissions {1030			Self::set_property_permission(collection, sender, prop_pemission)?;1031		}10321033		Ok(())1034	}10351036	pub fn get_collection_property(1037		collection_id: CollectionId,1038		key: &PropertyKey,1039	) -> Option<PropertyValue> {1040		Self::collection_properties(collection_id).get(key).cloned()1041	}10421043	pub fn bytes_keys_to_property_keys(1044		keys: Vec<Vec<u8>>,1045	) -> Result<Vec<PropertyKey>, DispatchError> {1046		keys.into_iter()1047			.map(|key| -> Result<PropertyKey, DispatchError> {1048				key.try_into()1049					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1050			})1051			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1052	}10531054	pub fn filter_collection_properties(1055		collection_id: CollectionId,1056		keys: Option<Vec<PropertyKey>>,1057	) -> Result<Vec<Property>, DispatchError> {1058		let properties = Self::collection_properties(collection_id);10591060		let properties = keys1061			.map(|keys| {1062				keys.into_iter()1063					.filter_map(|key| {1064						properties.get(&key).map(|value| Property {1065							key,1066							value: value.clone(),1067						})1068					})1069					.collect()1070			})1071			.unwrap_or_else(|| {1072				properties1073					.into_iter()1074					.map(|(key, value)| Property { key, value })1075					.collect()1076			});10771078		Ok(properties)1079	}10801081	pub fn filter_property_permissions(1082		collection_id: CollectionId,1083		keys: Option<Vec<PropertyKey>>,1084	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1085		let permissions = Self::property_permissions(collection_id);10861087		let key_permissions = keys1088			.map(|keys| {1089				keys.into_iter()1090					.filter_map(|key| {1091						permissions1092							.get(&key)1093							.map(|permission| PropertyKeyPermission {1094								key,1095								permission: permission.clone(),1096							})1097					})1098					.collect()1099			})1100			.unwrap_or_else(|| {1101				permissions1102					.into_iter()1103					.map(|(key, permission)| PropertyKeyPermission { key, permission })1104					.collect()1105			});11061107		Ok(key_permissions)1108	}11091110	pub fn toggle_allowlist(1111		collection: &CollectionHandle<T>,1112		sender: &T::CrossAccountId,1113		user: &T::CrossAccountId,1114		allowed: bool,1115	) -> DispatchResult {1116		collection.check_is_owner_or_admin(sender)?;11171118		// =========11191120		if allowed {1121			<Allowlist<T>>::insert((collection.id, user), true);1122		} else {1123			<Allowlist<T>>::remove((collection.id, user));1124		}11251126		Ok(())1127	}11281129	pub fn toggle_admin(1130		collection: &CollectionHandle<T>,1131		sender: &T::CrossAccountId,1132		user: &T::CrossAccountId,1133		admin: bool,1134	) -> DispatchResult {1135		collection.check_is_owner_or_admin(sender)?;11361137		let was_admin = <IsAdmin<T>>::get((collection.id, user));1138		if was_admin == admin {1139			return Ok(());1140		}1141		let amount = <AdminAmount<T>>::get(collection.id);11421143		if admin {1144			let amount = amount1145				.checked_add(1)1146				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1147			ensure!(1148				amount <= Self::collection_admins_limit(),1149				<Error<T>>::CollectionAdminCountExceeded,1150			);11511152			// =========11531154			<AdminAmount<T>>::insert(collection.id, amount);1155			<IsAdmin<T>>::insert((collection.id, user), true);1156		} else {1157			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1158			<IsAdmin<T>>::remove((collection.id, user));1159		}11601161		Ok(())1162	}11631164	pub fn clamp_limits(1165		mode: CollectionMode,1166		old_limit: &CollectionLimits,1167		mut new_limit: CollectionLimits,1168	) -> Result<CollectionLimits, DispatchError> {1169		limit_default!(old_limit, new_limit,1170			account_token_ownership_limit => ensure!(1171				new_limit <= MAX_TOKEN_OWNERSHIP,1172				<Error<T>>::CollectionLimitBoundsExceeded,1173			),1174			sponsored_data_size => ensure!(1175				new_limit <= CUSTOM_DATA_LIMIT,1176				<Error<T>>::CollectionLimitBoundsExceeded,1177			),11781179			sponsored_data_rate_limit => {},1180			token_limit => ensure!(1181				old_limit >= new_limit && new_limit > 0,1182				<Error<T>>::CollectionTokenLimitExceeded1183			),11841185			sponsor_transfer_timeout(match mode {1186				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1187				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1188				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1189			}) => ensure!(1190				new_limit <= MAX_SPONSOR_TIMEOUT,1191				<Error<T>>::CollectionLimitBoundsExceeded,1192			),1193			sponsor_approve_timeout => {},1194			owner_can_transfer => ensure!(1195				old_limit || !new_limit,1196				<Error<T>>::OwnerPermissionsCantBeReverted,1197			),1198			owner_can_destroy => ensure!(1199				old_limit || !new_limit,1200				<Error<T>>::OwnerPermissionsCantBeReverted,1201			),1202			transfers_enabled => {},1203		);1204		Ok(new_limit)1205	}12061207	pub fn clamp_permissions(1208		_mode: CollectionMode,1209		old_limit: &CollectionPermissions,1210		mut new_limit: CollectionPermissions,1211	) -> Result<CollectionPermissions, DispatchError> {1212		limit_default_clone!(old_limit, new_limit,1213			access => {},1214			mint_mode => {},1215			nesting => {},1216		);1217		Ok(new_limit)1218	}1219}12201221#[macro_export]1222macro_rules! unsupported {1223	() => {1224		Err(<Error<T>>::UnsupportedOperation.into())1225	};1226}12271228/// Worst cases1229pub trait CommonWeightInfo<CrossAccountId> {1230	fn create_item() -> Weight;1231	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1232	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1233	fn burn_item() -> Weight;1234	fn set_collection_properties(amount: u32) -> Weight;1235	fn delete_collection_properties(amount: u32) -> Weight;1236	fn set_token_properties(amount: u32) -> Weight;1237	fn delete_token_properties(amount: u32) -> Weight;1238	fn set_property_permissions(amount: u32) -> Weight;1239	fn transfer() -> Weight;1240	fn approve() -> Weight;1241	fn transfer_from() -> Weight;1242	fn burn_from() -> Weight;12431244	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1245	/// whole users's balance1246	///1247	/// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead1248	fn burn_recursively_self_raw() -> Weight;1249	/// Cost of iterating over `amount` children while burning, without counting child burning itself1250	///1251	/// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead1252	fn burn_recursively_breadth_raw(amount: u32) -> Weight;12531254	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1255		Self::burn_recursively_self_raw()1256			.saturating_mul(max_selfs.max(1) as u64)1257			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1258	}1259}12601261pub trait CommonCollectionOperations<T: Config> {1262	fn create_item(1263		&self,1264		sender: T::CrossAccountId,1265		to: T::CrossAccountId,1266		data: CreateItemData,1267		nesting_budget: &dyn Budget,1268	) -> DispatchResultWithPostInfo;1269	fn create_multiple_items(1270		&self,1271		sender: T::CrossAccountId,1272		to: T::CrossAccountId,1273		data: Vec<CreateItemData>,1274		nesting_budget: &dyn Budget,1275	) -> DispatchResultWithPostInfo;1276	fn create_multiple_items_ex(1277		&self,1278		sender: T::CrossAccountId,1279		data: CreateItemExData<T::CrossAccountId>,1280		nesting_budget: &dyn Budget,1281	) -> DispatchResultWithPostInfo;1282	fn burn_item(1283		&self,1284		sender: T::CrossAccountId,1285		token: TokenId,1286		amount: u128,1287	) -> DispatchResultWithPostInfo;1288	fn burn_item_recursively(1289		&self,1290		sender: T::CrossAccountId,1291		token: TokenId,1292		self_budget: &dyn Budget,1293		breadth_budget: &dyn Budget,1294	) -> DispatchResultWithPostInfo;1295	fn set_collection_properties(1296		&self,1297		sender: T::CrossAccountId,1298		properties: Vec<Property>,1299	) -> DispatchResultWithPostInfo;1300	fn delete_collection_properties(1301		&self,1302		sender: &T::CrossAccountId,1303		property_keys: Vec<PropertyKey>,1304	) -> DispatchResultWithPostInfo;1305	fn set_token_properties(1306		&self,1307		sender: T::CrossAccountId,1308		token_id: TokenId,1309		property: Vec<Property>,1310	) -> DispatchResultWithPostInfo;1311	fn delete_token_properties(1312		&self,1313		sender: T::CrossAccountId,1314		token_id: TokenId,1315		property_keys: Vec<PropertyKey>,1316	) -> DispatchResultWithPostInfo;1317	fn set_property_permissions(1318		&self,1319		sender: &T::CrossAccountId,1320		property_permissions: Vec<PropertyKeyPermission>,1321	) -> DispatchResultWithPostInfo;1322	fn transfer(1323		&self,1324		sender: T::CrossAccountId,1325		to: T::CrossAccountId,1326		token: TokenId,1327		amount: u128,1328		nesting_budget: &dyn Budget,1329	) -> DispatchResultWithPostInfo;1330	fn approve(1331		&self,1332		sender: T::CrossAccountId,1333		spender: T::CrossAccountId,1334		token: TokenId,1335		amount: u128,1336	) -> DispatchResultWithPostInfo;1337	fn transfer_from(1338		&self,1339		sender: T::CrossAccountId,1340		from: T::CrossAccountId,1341		to: T::CrossAccountId,1342		token: TokenId,1343		amount: u128,1344		nesting_budget: &dyn Budget,1345	) -> DispatchResultWithPostInfo;1346	fn burn_from(1347		&self,1348		sender: T::CrossAccountId,1349		from: T::CrossAccountId,1350		token: TokenId,1351		amount: u128,1352		nesting_budget: &dyn Budget,1353	) -> DispatchResultWithPostInfo;13541355	fn check_nesting(1356		&self,1357		sender: T::CrossAccountId,1358		from: (CollectionId, TokenId),1359		under: TokenId,1360		budget: &dyn Budget,1361	) -> DispatchResult;13621363	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13641365	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13661367	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1368	fn collection_tokens(&self) -> Vec<TokenId>;1369	fn token_exists(&self, token: TokenId) -> bool;1370	fn last_token_id(&self) -> TokenId;13711372	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1373	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1374	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1375	/// Amount of unique collection tokens1376	fn total_supply(&self) -> u32;1377	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1378	fn account_balance(&self, account: T::CrossAccountId) -> u32;1379	/// Amount of specific token account have (Applicable to fungible/refungible)1380	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1381	fn allowance(1382		&self,1383		sender: T::CrossAccountId,1384		spender: T::CrossAccountId,1385		token: TokenId,1386	) -> u128;1387}13881389// Flexible enough for implementing CommonCollectionOperations1390pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1391	let post_info = PostDispatchInfo {1392		actual_weight: Some(weight),1393		pays_fee: Pays::Yes,1394	};1395	match res {1396		Ok(()) => Ok(post_info),1397		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1398	}1399}14001401impl<T: Config> From<PropertiesError> for Error<T> {1402	fn from(error: PropertiesError) -> Self {1403		match error {1404			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1405			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1406			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1407			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1408			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1409		}1410	}1411}
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -137,7 +137,7 @@
 		owner: T::CrossAccountId,
 		data: CreateCollectionData<T::AccountId>,
 	) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(owner, data)
+		<PalletCommon<T>>::init_collection(owner, data, false)
 	}
 	pub fn destroy_collection(
 		collection: FungibleHandle<T>,
@@ -168,8 +168,6 @@
 		owner: &T::CrossAccountId,
 		amount: u128,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
-
 		let total_supply = <TotalSupply<T>>::get(collection.id)
 			.checked_sub(amount)
 			.ok_or(<CommonError<T>>::TokenValueTooLow)?;
@@ -216,8 +214,6 @@
 		amount: u128,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
-
 		ensure!(
 			collection.limits.transfers_enabled(),
 			<CommonError<T>>::TransferNotAllowed,
@@ -287,8 +283,6 @@
 		data: BTreeMap<T::CrossAccountId, u128>,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
-
 		if !collection.is_owner_or_admin(sender) {
 			ensure!(
 				collection.permissions.mint_mode(),
@@ -390,7 +384,6 @@
 		spender: &T::CrossAccountId,
 		amount: u128,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
 		if collection.permissions.access() == AccessMode::AllowList {
 			collection.check_allowlist(owner)?;
 			collection.check_allowlist(spender)?;
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -304,8 +304,9 @@
 	pub fn init_collection(
 		owner: T::CrossAccountId,
 		data: CreateCollectionData<T::AccountId>,
+		is_external: bool,
 	) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(owner, data)
+		<PalletCommon<T>>::init_collection(owner, data, is_external)
 	}
 	pub fn destroy_collection(
 		collection: NonfungibleHandle<T>,
@@ -336,8 +337,6 @@
 		sender: &T::CrossAccountId,
 		token: TokenId,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
-
 		let token_data =
 			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;
 		ensure!(
@@ -458,7 +457,6 @@
 			&property.key,
 			is_token_create,
 		)?;
-		collection.check_is_mutable()?;
 
 		<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {
 			let property = property.clone();
@@ -496,7 +494,6 @@
 		token_id: TokenId,
 		property_key: PropertyKey,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
 		Self::check_token_change_permission(collection, sender, token_id, &property_key, false)?;
 
 		<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {
@@ -574,8 +571,6 @@
 		token_id: TokenId,
 		property_keys: Vec<PropertyKey>,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
-
 		for key in property_keys {
 			Self::delete_token_property(collection, sender, token_id, key)?;
 		}
@@ -622,8 +617,6 @@
 		token: TokenId,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
-
 		ensure!(
 			collection.limits.transfers_enabled(),
 			<CommonError<T>>::TransferNotAllowed
@@ -902,8 +895,6 @@
 		token: TokenId,
 		spender: Option<&T::CrossAccountId>,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
-
 		if collection.permissions.access() == AccessMode::AllowList {
 			collection.check_allowlist(sender)?;
 			if let Some(spender) = spender {
modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/lib.rs
+++ b/pallets/proxy-rmrk-core/src/lib.rs
@@ -235,6 +235,7 @@
 				Self::unique_collection_id(collection_id)?,
 				misc::CollectionType::Regular,
 			)?;
+			collection.check_is_external()?;
 
 			<PalletNft<T>>::destroy_collection(collection, &cross_sender)
 				.map_err(Self::map_unique_err_to_proxy)?;
@@ -256,6 +257,9 @@
 		) -> DispatchResult {
 			let sender = ensure_signed(origin)?;
 
+			let collection = Self::get_nft_collection(Self::unique_collection_id(collection_id)?)?;
+			collection.check_is_external()?;
+
 			let new_issuer = T::Lookup::lookup(new_issuer)?;
 
 			Self::change_collection_owner(
@@ -287,6 +291,7 @@
 				Self::unique_collection_id(collection_id)?,
 				misc::CollectionType::Regular,
 			)?;
+			collection.check_is_external()?;
 
 			Self::check_collection_owner(&collection, &cross_sender)?;
 
@@ -318,17 +323,18 @@
 			let sender = ensure_signed(origin)?;
 			let sender = T::CrossAccountId::from_sub(sender);
 			let cross_owner = T::CrossAccountId::from_sub(owner.clone());
-
-			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {
-				recipient: recipient.unwrap_or_else(|| owner.clone()),
-				amount,
-			});
 
 			let collection = Self::get_typed_nft_collection(
 				Self::unique_collection_id(collection_id)?,
 				misc::CollectionType::Regular,
 			)?;
+			collection.check_is_external()?;
 
+			let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {
+				recipient: recipient.unwrap_or_else(|| owner.clone()),
+				amount,
+			});
+
 			let nft_id = Self::create_nft(
 				&sender,
 				&cross_owner,
@@ -382,6 +388,12 @@
 			let sender = ensure_signed(origin)?;
 			let cross_sender = T::CrossAccountId::from_sub(sender.clone());
 
+			let collection = Self::get_typed_nft_collection(
+				Self::unique_collection_id(collection_id)?,
+				misc::CollectionType::Regular,
+			)?;
+			collection.check_is_external()?;
+
 			Self::destroy_nft(
 				cross_sender,
 				Self::unique_collection_id(collection_id)?,
@@ -411,13 +423,14 @@
 			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;
 			let nft_id = rmrk_nft_id.into();
 
+			let collection =
+				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;
+			collection.check_is_external()?;
+
 			let token_data =
 				<TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;
 
 			let from = token_data.owner;
-
-			let collection =
-				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;
 
 			ensure!(
 				Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,
@@ -516,6 +529,7 @@
 
 			let collection =
 				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;
+			collection.check_is_external()?;
 
 			let new_cross_owner = match new_owner {
 				RmrkAccountIdOrCollectionNftTuple::AccountId(ref account_id) => {
@@ -581,6 +595,10 @@
 			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;
 			let nft_id = rmrk_nft_id.into();
 
+			let collection =
+				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;
+			collection.check_is_external()?;
+
 			Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {
 				if err == <CommonError<T>>::NoPermission.into()
 					|| err == <CommonError<T>>::ApprovedValueTooLow.into()
@@ -613,6 +631,9 @@
 
 			let collection_id = Self::unique_collection_id(rmrk_collection_id)
 				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;
+			let collection =
+				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;
+			collection.check_is_external()?;
 
 			let nft_id = rmrk_nft_id.into();
 			let resource_id = rmrk_resource_id.into();
@@ -666,6 +687,9 @@
 
 			let collection_id = Self::unique_collection_id(rmrk_collection_id)
 				.map_err(|_| <Error<T>>::ResourceDoesntExist)?;
+			let collection =
+				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;
+			collection.check_is_external()?;
 
 			let nft_id = rmrk_nft_id.into();
 			let resource_id = rmrk_resource_id.into();
@@ -720,6 +744,10 @@
 			let sender = T::CrossAccountId::from_sub(sender);
 
 			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;
+			let collection =
+				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;
+			collection.check_is_external()?;
+
 			let budget = budget::Value::new(NESTING_BUDGET);
 
 			match maybe_nft_id {
@@ -775,6 +803,11 @@
 
 			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;
 			let nft_id = rmrk_nft_id.into();
+
+			let collection =
+				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;
+			collection.check_is_external()?;
+
 			let budget = budget::Value::new(NESTING_BUDGET);
 
 			Self::ensure_nft_type(collection_id, nft_id, NftType::Regular)?;
@@ -799,15 +832,20 @@
 		#[transactional]
 		pub fn add_basic_resource(
 			origin: OriginFor<T>,
-			collection_id: RmrkCollectionId,
+			rmrk_collection_id: RmrkCollectionId,
 			nft_id: RmrkNftId,
 			resource: RmrkBasicResource,
 		) -> DispatchResult {
 			let sender = ensure_signed(origin.clone())?;
 
+			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;
+			let collection =
+				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;
+			collection.check_is_external()?;
+
 			let resource_id = Self::resource_add(
 				sender,
-				Self::unique_collection_id(collection_id)?,
+				collection_id,
 				nft_id.into(),
 				[
 					Self::rmrk_property(TokenType, &NftType::Resource)?,
@@ -831,16 +869,21 @@
 		#[transactional]
 		pub fn add_composable_resource(
 			origin: OriginFor<T>,
-			collection_id: RmrkCollectionId,
+			rmrk_collection_id: RmrkCollectionId,
 			nft_id: RmrkNftId,
 			_resource_id: RmrkBoundedResource,
 			resource: RmrkComposableResource,
 		) -> DispatchResult {
 			let sender = ensure_signed(origin.clone())?;
 
+			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;
+			let collection =
+				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;
+			collection.check_is_external()?;
+
 			let resource_id = Self::resource_add(
 				sender,
-				Self::unique_collection_id(collection_id)?,
+				collection_id,
 				nft_id.into(),
 				[
 					Self::rmrk_property(TokenType, &NftType::Resource)?,
@@ -866,15 +909,20 @@
 		#[transactional]
 		pub fn add_slot_resource(
 			origin: OriginFor<T>,
-			collection_id: RmrkCollectionId,
+			rmrk_collection_id: RmrkCollectionId,
 			nft_id: RmrkNftId,
 			resource: RmrkSlotResource,
 		) -> DispatchResult {
 			let sender = ensure_signed(origin.clone())?;
 
+			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;
+			let collection =
+				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;
+			collection.check_is_external()?;
+
 			let resource_id = Self::resource_add(
 				sender,
-				Self::unique_collection_id(collection_id)?,
+				collection_id,
 				nft_id.into(),
 				[
 					Self::rmrk_property(TokenType, &NftType::Resource)?,
@@ -900,18 +948,18 @@
 		#[transactional]
 		pub fn remove_resource(
 			origin: OriginFor<T>,
-			collection_id: RmrkCollectionId,
+			rmrk_collection_id: RmrkCollectionId,
 			nft_id: RmrkNftId,
 			resource_id: RmrkResourceId,
 		) -> DispatchResult {
 			let sender = ensure_signed(origin.clone())?;
 
-			Self::resource_remove(
-				sender,
-				Self::unique_collection_id(collection_id)?,
-				nft_id.into(),
-				resource_id.into(),
-			)?;
+			let collection_id = Self::unique_collection_id(rmrk_collection_id)?;
+			let collection =
+				Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;
+			collection.check_is_external()?;
+
+			Self::resource_remove(sender, collection_id, nft_id.into(), resource_id.into())?;
 
 			Self::deposit_event(Event::ResourceRemoval {
 				nft_id,
@@ -968,7 +1016,7 @@
 		data: CreateCollectionData<T::AccountId>,
 		properties: impl Iterator<Item = Property>,
 	) -> Result<CollectionId, DispatchError> {
-		let collection_id = <PalletNft<T>>::init_collection(sender, data);
+		let collection_id = <PalletNft<T>>::init_collection(sender, data, true);
 
 		if let Err(DispatchError::Arithmetic(_)) = &collection_id {
 			return Err(<Error<T>>::NoAvailableCollectionId.into());
modifiedpallets/proxy-rmrk-equip/src/lib.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-equip/src/lib.rs
+++ b/pallets/proxy-rmrk-equip/src/lib.rs
@@ -94,7 +94,8 @@
 				..Default::default()
 			};
 
-			let collection_id_res = <PalletNft<T>>::init_collection(cross_sender.clone(), data);
+			let collection_id_res =
+				<PalletNft<T>>::init_collection(cross_sender.clone(), data, true);
 
 			if let Err(DispatchError::Arithmetic(_)) = &collection_id_res {
 				return Err(<Error<T>>::NoAvailableBaseId.into());
@@ -155,6 +156,7 @@
 				misc::CollectionType::Base,
 			)
 			.map_err(|_| <Error<T>>::BaseDoesntExist)?;
+			collection.check_is_external()?;
 
 			if theme.name.as_slice() == b"default" {
 				<BaseHasDefaultTheme<T>>::insert(collection_id, true);
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -200,7 +200,7 @@
 		owner: T::CrossAccountId,
 		data: CreateCollectionData<T::AccountId>,
 	) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(owner, data)
+		<PalletCommon<T>>::init_collection(owner, data, false)
 	}
 	pub fn destroy_collection(
 		collection: RefungibleHandle<T>,
@@ -234,7 +234,6 @@
 	}
 
 	pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {
-		collection.check_is_mutable()?;
 		let burnt = <TokensBurnt<T>>::get(collection.id)
 			.checked_add(1)
 			.ok_or(ArithmeticError::Overflow)?;
@@ -254,7 +253,6 @@
 		token: TokenId,
 		amount: u128,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
 		let total_supply = <TotalSupply<T>>::get((collection.id, token))
 			.checked_sub(amount)
 			.ok_or(<CommonError<T>>::TokenValueTooLow)?;
@@ -327,7 +325,6 @@
 		amount: u128,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
 		ensure!(
 			collection.limits.transfers_enabled(),
 			<CommonError<T>>::TransferNotAllowed
@@ -576,7 +573,6 @@
 		token: TokenId,
 		amount: u128,
 	) -> DispatchResult {
-		collection.check_is_mutable()?;
 		if collection.permissions.access() == AccessMode::AllowList {
 			collection.check_allowlist(sender)?;
 			collection.check_allowlist(spender)?;
modifiedpallets/unique/src/eth/mod.rsdiffbeforeafterboth
--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -92,8 +92,9 @@
 			..Default::default()
 		};
 
-		let collection_id = <pallet_nonfungible::Pallet<T>>::init_collection(caller.clone(), data)
-			.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
+		let collection_id =
+			<pallet_nonfungible::Pallet<T>>::init_collection(caller.clone(), data, false)
+				.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
 
 		let address = pallet_common::eth::collection_id_to_address(collection_id);
 		Ok(address)
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -304,7 +304,7 @@
 		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
-			collection.check_is_mutable()?;
+			collection.check_is_internal()?;
 
 			// =========
 
@@ -339,6 +339,7 @@
 
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			collection.check_is_internal()?;
 
 			<PalletCommon<T>>::toggle_allowlist(
 				&collection,
@@ -373,6 +374,7 @@
 
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			collection.check_is_internal()?;
 
 			<PalletCommon<T>>::toggle_allowlist(
 				&collection,
@@ -407,7 +409,7 @@
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
-			target_collection.check_is_mutable()?;
+			target_collection.check_is_internal()?;
 			target_collection.check_is_owner(&sender)?;
 
 			target_collection.owner = new_owner.clone();
@@ -437,6 +439,7 @@
 		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			collection.check_is_internal()?;
 
 			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(
 				collection_id,
@@ -463,6 +466,7 @@
 		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			collection.check_is_internal()?;
 
 			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(
 				collection_id,
@@ -488,6 +492,7 @@
 
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			target_collection.check_is_owner(&sender)?;
+			target_collection.check_is_internal()?;
 
 			target_collection.set_sponsor(new_sponsor.clone())?;
 
@@ -512,6 +517,7 @@
 			let sender = ensure_signed(origin)?;
 
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			target_collection.check_is_internal()?;
 			ensure!(
 				target_collection.confirm_sponsorship(&sender)?,
 				Error::<T>::ConfirmUnsetSponsorFail
@@ -540,6 +546,7 @@
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			target_collection.check_is_internal()?;
 			target_collection.check_is_owner(&sender)?;
 
 			target_collection.sponsorship = SponsorshipState::Disabled;
@@ -704,6 +711,7 @@
 		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			target_collection.check_is_internal()?;
 			target_collection.check_is_owner(&sender)?;
 
 			// =========
@@ -858,6 +866,7 @@
 		) -> DispatchResult {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			target_collection.check_is_internal()?;
 			target_collection.check_is_owner(&sender)?;
 			let old_limit = &target_collection.limits;
 
@@ -879,6 +888,7 @@
 		) -> DispatchResult {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
+			target_collection.check_is_internal()?;
 			target_collection.check_is_owner(&sender)?;
 			let old_limit = &target_collection.permissions;
 
modifiedruntime/common/src/dispatch.rsdiffbeforeafterboth
--- a/runtime/common/src/dispatch.rs
+++ b/runtime/common/src/dispatch.rs
@@ -35,7 +35,7 @@
 		data: CreateCollectionData<T::AccountId>,
 	) -> DispatchResult {
 		let _id = match data.mode {
-			CollectionMode::NFT => <PalletNonfungible<T>>::init_collection(sender, data)?,
+			CollectionMode::NFT => <PalletNonfungible<T>>::init_collection(sender, data, false)?,
 			CollectionMode::Fungible(decimal_points) => {
 				// check params
 				ensure!(