git.delta.rocks / unique-network / refs/commits / 6035f8d62cec

difftreelog

style cargo fmt

Farhad Hakimov2022-07-11parent: #1a56db7.patch.diff
in: master

3 files changed

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	RmrkBoundedTheme,78	RmrkNftChild,79	CollectionPermissions,80	SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97	pub id: CollectionId,98	collection: Collection<T::AccountId>,99	pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102	fn recorder(&self) -> &SubstrateRecorder<T> {103		&self.recorder104	}105	fn into_recorder(self) -> SubstrateRecorder<T> {106		self.recorder107	}108}109impl<T: Config> CollectionHandle<T> {110	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111		<CollectionById<T>>::get(id).map(|collection| Self {112			id,113			collection,114			recorder: SubstrateRecorder::new(gas_limit),115		})116	}117118	pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {119		<CollectionById<T>>::get(id).map(|collection| Self {120			id,121			collection,122			recorder,123		})124	}125126	pub fn new(id: CollectionId) -> Option<Self> {127		Self::new_with_gas_limit(id, u64::MAX)128	}129130	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {131		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)132	}133134	pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {135		self.recorder136			.consume_gas(T::GasWeightMapping::weight_to_gas(137				<T as frame_system::Config>::DbWeight::get()138					.read139					.saturating_mul(reads),140			))141	}142143	pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {144		self.recorder145			.consume_gas(T::GasWeightMapping::weight_to_gas(146				<T as frame_system::Config>::DbWeight::get()147					.write148					.saturating_mul(writes),149			))150	}151	pub fn save(self) -> DispatchResult {152		<CollectionById<T>>::insert(self.id, self.collection);153		Ok(())154	}155156	pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {157		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);158		Ok(())159	}160161	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {162		if self.collection.sponsorship.pending_sponsor() != Some(sender) {163			return Ok(false);164		}165166		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());167		Ok(true)168	}169170	/// Checks that the collection was created with, and must be operated upon through **Unique API**.171	/// Now check only the `external_collection` flag and if it's **true**, then return `CollectionIsExternal` error.172	pub fn check_is_internal(&self) -> DispatchResult {173		if self.external_collection {174			return Err(<Error<T>>::CollectionIsExternal)?;175		}176177		Ok(())178	}179180	/// Checks that the collection was created with, and must be operated upon through an **assimilated API**.181	/// Now check only the `external_collection` flag and if it's **false**, then return `CollectionIsInternal` error.182	pub fn check_is_external(&self) -> DispatchResult {183		if !self.external_collection {184			return Err(<Error<T>>::CollectionIsInternal)?;185		}186187		Ok(())188	}189}190191impl<T: Config> Deref for CollectionHandle<T> {192	type Target = Collection<T::AccountId>;193194	fn deref(&self) -> &Self::Target {195		&self.collection196	}197}198199impl<T: Config> DerefMut for CollectionHandle<T> {200	fn deref_mut(&mut self) -> &mut Self::Target {201		&mut self.collection202	}203}204205impl<T: Config> CollectionHandle<T> {206	pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {207		ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);208		Ok(())209	}210	pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {211		*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))212	}213	pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {214		ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);215		Ok(())216	}217	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {218		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)219	}220	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {221		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)222	}223	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {224		ensure!(225			<Allowlist<T>>::get((self.id, user)),226			<Error<T>>::AddressNotInAllowlist227		);228		Ok(())229	}230}231232#[frame_support::pallet]233pub mod pallet {234	use super::*;235	use pallet_evm::account;236	use dispatch::CollectionDispatch;237	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};238	use frame_system::pallet_prelude::*;239	use frame_support::traits::Currency;240	use up_data_structs::{TokenId, mapping::TokenAddressMapping};241	use scale_info::TypeInfo;242	use weights::WeightInfo;243244	#[pallet::config]245	pub trait Config:246		frame_system::Config247		+ pallet_evm_coder_substrate::Config248		+ pallet_evm::Config249		+ TypeInfo250		+ account::Config251	{252		type WeightInfo: WeightInfo;253		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;254255		type Currency: Currency<Self::AccountId>;256257		#[pallet::constant]258		type CollectionCreationPrice: Get<259			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,260		>;261		type CollectionDispatch: CollectionDispatch<Self>;262263		type TreasuryAccountId: Get<Self::AccountId>;264		type ContractAddress: Get<H160>;265266		type EvmTokenAddressMapping: TokenAddressMapping<H160>;267		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;268	}269270	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);271272	#[pallet::pallet]273	#[pallet::storage_version(STORAGE_VERSION)]274	#[pallet::generate_store(pub(super) trait Store)]275	pub struct Pallet<T>(_);276277	#[pallet::extra_constants]278	impl<T: Config> Pallet<T> {279		pub fn collection_admins_limit() -> u32 {280			COLLECTION_ADMINS_LIMIT281		}282	}283284	#[pallet::event]285	#[pallet::generate_deposit(pub fn deposit_event)]286	pub enum Event<T: Config> {287		/// New collection was created288		///289		/// # Arguments290		///291		/// * collection_id: Globally unique identifier of newly created collection.292		///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 that has been destroyed.303		CollectionDestroyed(CollectionId),304305		/// New item was created.306		///307		/// # Arguments308		///309		/// * collection_id: ID of the collection where the item was created.310		///311		/// * item_id: ID of the item. Unique within the collection.312		///313		/// * recipient: Owner of the newly created item.314		///315		/// * amount: The amount of tokens that were created (always 1 for NFT).316		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),317318		/// Collection item was burned.319		///320		/// # Arguments321		///322		/// * collection_id: Identifier of the collection to which the burned NFT belonged.323		///324		/// * item_id: Identifier of burned NFT.325		///326		/// * owner: Which user has destroyed their tokens.327		///328		/// * amount: The amount of tokens that were destroyed (always 1 for NFT).329		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),330331		/// Item was transferred.332		/// 333		/// # Arguments334		///335		/// * collection_id: ID of the collection to which the item belongs.336		///337		/// * item_id: ID of the item trasnferred.338		///339		/// * sender: Original owner of the item.340		///341		/// * recipient: New owner of the item.342		///343		/// * amount: The amount of tokens that were transferred (always 1 for NFT).344		Transfer(345			CollectionId,346			TokenId,347			T::CrossAccountId,348			T::CrossAccountId,349			u128,350		),351352		/// Sponsoring allowance was approved.353		/// 354		/// # Arguments355		/// 356		/// * collection_id357		///358		/// * item_id359		///360		/// * sender361		///362		/// * spender363		///364		/// * amount365		Approved(366			CollectionId,367			TokenId,368			T::CrossAccountId,369			T::CrossAccountId,370			u128,371		),372373		/// Collection property was added or edited.374		/// 375		/// # Arguments376		/// 377		/// * collection_id: ID of the collection, whose property was just set.378		/// 379		/// * property_key: Key of the property that was just set.380		CollectionPropertySet(CollectionId, PropertyKey),381382		/// Collection property was deleted.383		/// 384		/// # Arguments385		/// 386		/// * collection_id: ID of the collection, whose property was just deleted.387		/// 388		/// * property_key: Key of the property that was just deleted.389		CollectionPropertyDeleted(CollectionId, PropertyKey),390391		/// Item property was added or edited.392		/// 393		/// # Arguments394		/// 395		/// * collection_id: ID of the collection, whose token's property was just set.396		/// 397		/// * item_id: ID of the item, whose property was just set.398		/// 399		/// * property_key: Key of the property that was just set.400		TokenPropertySet(CollectionId, TokenId, PropertyKey),401402		/// Item property was deleted.403		/// 404		/// # Arguments405		/// 406		/// * collection_id: ID of the collection, whose token's property was just deleted.407		/// 408		/// * item_id: ID of the item, whose property was just deleted.409		/// 410		/// * property_key: Key of the property that was just deleted.411		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),412413		/// Token property permission was added or updated for a collection.414		/// 415		/// # Arguments416		/// 417		/// * collection_id: ID of the collection, whose permissions were just set/updated.418		/// 419		/// * property_key: Key of the property of the set/updated permission.420		PropertyPermissionSet(CollectionId, PropertyKey),421	}422423	#[pallet::error]424	pub enum Error<T> {425		/// This collection does not exist.426		CollectionNotFound,427		/// Sender parameter and item owner must be equal.428		MustBeTokenOwner,429		/// No permission to perform action430		NoPermission,431		/// Destroying only empty collections is allowed432		CantDestroyNotEmptyCollection,433		/// Collection is not in mint mode.434		PublicMintingNotAllowed,435		/// Address is not in allow list.436		AddressNotInAllowlist,437438		/// Collection name can not be longer than 63 char.439		CollectionNameLimitExceeded,440		/// Collection description can not be longer than 255 char.441		CollectionDescriptionLimitExceeded,442		/// Token prefix can not be longer than 15 char.443		CollectionTokenPrefixLimitExceeded,444		/// Total collections bound exceeded.445		TotalCollectionsLimitExceeded,446		/// Exceeded max admin count447		CollectionAdminCountExceeded,448		/// Collection limit bounds per collection exceeded449		CollectionLimitBoundsExceeded,450		/// Tried to enable permissions which are only permitted to be disabled451		OwnerPermissionsCantBeReverted,452		/// Collection settings not allowing items transferring453		TransferNotAllowed,454		/// Account token limit exceeded per collection455		AccountTokenLimitExceeded,456		/// Collection token limit exceeded457		CollectionTokenLimitExceeded,458		/// Metadata flag frozen459		MetadataFlagFrozen,460461		/// Item does not exist462		TokenNotFound,463		/// Item is balance not enough464		TokenValueTooLow,465		/// Requested value is more than the approved466		ApprovedValueTooLow,467		/// Tried to approve more than owned468		CantApproveMoreThanOwned,469470		/// Can't transfer tokens to ethereum zero address471		AddressIsZero,472		/// Target collection doesn't support this operation473		UnsupportedOperation,474475		/// Insufficient funds to perform an action476		NotSufficientFounds,477478		/// User does not satisfy the nesting rule479		UserIsNotAllowedToNest,480		/// Only tokens from specific collections may nest tokens under this one481		SourceCollectionIsNotAllowedToNest,482483		/// Tried to store more data than allowed in collection field484		CollectionFieldSizeExceeded,485486		/// Tried to store more property data than allowed487		NoSpaceForProperty,488489		/// Tried to store more property keys than allowed490		PropertyLimitReached,491492		/// Property key is too long493		PropertyKeyIsTooLong,494495		/// Only ASCII letters, digits, and symbols '_', '-', and '.' are allowed496		InvalidCharacterInPropertyKey,497498		/// Empty property keys are forbidden499		EmptyPropertyKey,500501		/// Tried to access an external collection with an internal API502		CollectionIsExternal,503504		/// Tried to access an internal collection with an external API505		CollectionIsInternal,506	}507508	/// The number of created collections. Essentially contains the last collection ID.509	#[pallet::storage]510	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;511512	/// The number of destroyed collections513	#[pallet::storage]514	pub type DestroyedCollectionCount<T> =515		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;516517	/// Collection info518	#[pallet::storage]519	pub type CollectionById<T> = StorageMap<520		Hasher = Blake2_128Concat,521		Key = CollectionId,522		Value = Collection<<T as frame_system::Config>::AccountId>,523		QueryKind = OptionQuery,524	>;525526	/// Collection properties527	#[pallet::storage]528	#[pallet::getter(fn collection_properties)]529	pub type CollectionProperties<T> = StorageMap<530		Hasher = Blake2_128Concat,531		Key = CollectionId,532		Value = Properties,533		QueryKind = ValueQuery,534		OnEmpty = up_data_structs::CollectionProperties,535	>;536537	/// Token permissions of a collection538	#[pallet::storage]539	#[pallet::getter(fn property_permissions)]540	pub type CollectionPropertyPermissions<T> = StorageMap<541		Hasher = Blake2_128Concat,542		Key = CollectionId,543		Value = PropertiesPermissionMap,544		QueryKind = ValueQuery,545	>;546547	/// Amount of collection admins548	#[pallet::storage]549	pub type AdminAmount<T> = StorageMap<550		Hasher = Blake2_128Concat,551		Key = CollectionId,552		Value = u32,553		QueryKind = ValueQuery,554	>;555556	/// List of collection admins557	#[pallet::storage]558	pub type IsAdmin<T: Config> = StorageNMap<559		Key = (560			Key<Blake2_128Concat, CollectionId>,561			Key<Blake2_128Concat, T::CrossAccountId>,562		),563		Value = bool,564		QueryKind = ValueQuery,565	>;566567	/// Allowlisted collection users568	#[pallet::storage]569	pub type Allowlist<T: Config> = StorageNMap<570		Key = (571			Key<Blake2_128Concat, CollectionId>,572			Key<Blake2_128Concat, T::CrossAccountId>,573		),574		Value = bool,575		QueryKind = ValueQuery,576	>;577578	/// Not used by code, exists only to provide some types to metadata579	#[pallet::storage]580	pub type DummyStorageValue<T: Config> = StorageValue<581		Value = (582			CollectionStats,583			CollectionId,584			TokenId,585			TokenChild,586			PhantomType<(587				TokenData<T::CrossAccountId>,588				RpcCollection<T::AccountId>,589				// RMRK590				RmrkCollectionInfo<T::AccountId>,591				RmrkInstanceInfo<T::AccountId>,592				RmrkResourceInfo,593				RmrkPropertyInfo,594				RmrkBaseInfo<T::AccountId>,595				RmrkPartType,596				RmrkBoundedTheme,597				RmrkNftChild,598			)>,599		),600		QueryKind = OptionQuery,601	>;602603	#[pallet::hooks]604	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {605		fn on_runtime_upgrade() -> Weight {606			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {607				use up_data_structs::{CollectionVersion1, CollectionVersion2};608				<CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {609					let mut props = Vec::new();610					if !v.offchain_schema.is_empty() {611						props.push(Property {612							key: b"_old_offchainSchema".to_vec().try_into().unwrap(),613							value: v614								.offchain_schema615								.clone()616								.into_inner()617								.try_into()618								.expect("offchain schema too big"),619						});620					}621					if !v.variable_on_chain_schema.is_empty() {622						props.push(Property {623							key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),624							value: v625								.variable_on_chain_schema626								.clone()627								.into_inner()628								.try_into()629								.expect("offchain schema too big"),630						});631					}632					if !v.const_on_chain_schema.is_empty() {633						props.push(Property {634							key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),635							value: v636								.const_on_chain_schema637								.clone()638								.into_inner()639								.try_into()640								.expect("offchain schema too big"),641						});642					}643					props.push(Property {644						key: b"_old_schemaVersion".to_vec().try_into().unwrap(),645						value: match v.schema_version {646							SchemaVersion::ImageURL => b"ImageUrl".as_slice(),647							SchemaVersion::Unique => b"Unique".as_slice(),648						}649						.to_vec()650						.try_into()651						.unwrap(),652					});653					Self::set_scoped_collection_properties(654						id,655						PropertyScope::None,656						props.into_iter(),657					)658					.expect("existing data larger than properties");659					let mut new = CollectionVersion2::from(v.clone());660					new.permissions.access = Some(v.access);661					new.permissions.mint_mode = Some(v.mint_mode);662					Some(new)663				});664			}665666			0667		}668	}669}670671impl<T: Config> Pallet<T> {672	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens673	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {674		ensure!(675			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,676			<Error<T>>::AddressIsZero677		);678		Ok(())679	}680	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {681		<IsAdmin<T>>::iter_prefix((collection,))682			.map(|(a, _)| a)683			.collect()684	}685	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {686		<Allowlist<T>>::iter_prefix((collection,))687			.map(|(a, _)| a)688			.collect()689	}690	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {691		<Allowlist<T>>::get((collection, user))692	}693	pub fn collection_stats() -> CollectionStats {694		let created = <CreatedCollectionCount<T>>::get();695		let destroyed = <DestroyedCollectionCount<T>>::get();696		CollectionStats {697			created: created.0,698			destroyed: destroyed.0,699			alive: created.0 - destroyed.0,700		}701	}702703	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {704		let collection = <CollectionById<T>>::get(collection);705		if collection.is_none() {706			return None;707		}708709		let collection = collection.unwrap();710		let limits = collection.limits;711		let effective_limits = CollectionLimits {712			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),713			sponsored_data_size: Some(limits.sponsored_data_size()),714			sponsored_data_rate_limit: Some(715				limits716					.sponsored_data_rate_limit717					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),718			),719			token_limit: Some(limits.token_limit()),720			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(721				match collection.mode {722					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,723					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,724					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,725				},726			)),727			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),728			owner_can_transfer: Some(limits.owner_can_transfer()),729			owner_can_destroy: Some(limits.owner_can_destroy()),730			transfers_enabled: Some(limits.transfers_enabled()),731		};732733		Some(effective_limits)734	}735736	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {737		let Collection {738			name,739			description,740			owner,741			mode,742			token_prefix,743			sponsorship,744			limits,745			permissions,746			external_collection,747		} = <CollectionById<T>>::get(collection)?;748749		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)750			.into_iter()751			.map(|(key, permission)| PropertyKeyPermission { key, permission })752			.collect();753754		let properties = <CollectionProperties<T>>::get(collection)755			.into_iter()756			.map(|(key, value)| Property { key, value })757			.collect();758759		let permissions = CollectionPermissions {760			access: Some(permissions.access()),761			mint_mode: Some(permissions.mint_mode()),762			nesting: Some(permissions.nesting().clone()),763		};764765		Some(RpcCollection {766			name: name.into_inner(),767			description: description.into_inner(),768			owner,769			mode,770			token_prefix: token_prefix.into_inner(),771			sponsorship,772			limits,773			permissions,774			token_property_permissions,775			properties,776			read_only: external_collection,777		})778	}779}780781macro_rules! limit_default {782	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{783		$(784			if let Some($new) = $new.$field {785				let $old = $old.$field($($arg)?);786				let _ = $new;787				let _ = $old;788				$check789			} else {790				$new.$field = $old.$field791			}792		)*793	}};794}795macro_rules! limit_default_clone {796	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{797		$(798			if let Some($new) = $new.$field.clone() {799				let $old = $old.$field($($arg)?);800				let _ = $new;801				let _ = $old;802				$check803			} else {804				$new.$field = $old.$field.clone()805			}806		)*807	}};808}809810impl<T: Config> Pallet<T> {811	pub fn init_collection(812		owner: T::CrossAccountId,813		data: CreateCollectionData<T::AccountId>,814		is_external: bool,815	) -> Result<CollectionId, DispatchError> {816		{817			ensure!(818				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,819				Error::<T>::CollectionTokenPrefixLimitExceeded820			);821		}822823		let created_count = <CreatedCollectionCount<T>>::get()824			.0825			.checked_add(1)826			.ok_or(ArithmeticError::Overflow)?;827		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;828		let id = CollectionId(created_count);829830		// bound Total number of collections831		ensure!(832			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,833			<Error<T>>::TotalCollectionsLimitExceeded834		);835836		// =========837838		let collection = Collection {839			owner: owner.as_sub().clone(),840			name: data.name,841			mode: data.mode.clone(),842			description: data.description,843			token_prefix: data.token_prefix,844			sponsorship: data845				.pending_sponsor846				.map(SponsorshipState::Unconfirmed)847				.unwrap_or_default(),848			limits: data849				.limits850				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))851				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,852			permissions: data853				.permissions854				.map(|permissions| {855					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)856				})857				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,858			external_collection: is_external,859		};860861		let mut collection_properties = up_data_structs::CollectionProperties::get();862		collection_properties863			.try_set_from_iter(data.properties.into_iter())864			.map_err(<Error<T>>::from)?;865866		CollectionProperties::<T>::insert(id, collection_properties);867868		let mut token_props_permissions = PropertiesPermissionMap::new();869		token_props_permissions870			.try_set_from_iter(data.token_property_permissions.into_iter())871			.map_err(<Error<T>>::from)?;872873		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);874875		// Take a (non-refundable) deposit of collection creation876		{877			let mut imbalance =878				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();879			imbalance.subsume(880				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(881					&T::TreasuryAccountId::get(),882					T::CollectionCreationPrice::get(),883				),884			);885			<T as Config>::Currency::settle(886				&owner.as_sub(),887				imbalance,888				WithdrawReasons::TRANSFER,889				ExistenceRequirement::KeepAlive,890			)891			.map_err(|_| Error::<T>::NotSufficientFounds)?;892		}893894		<CreatedCollectionCount<T>>::put(created_count);895		<Pallet<T>>::deposit_event(Event::CollectionCreated(896			id,897			data.mode.id(),898			owner.as_sub().clone(),899		));900		<PalletEvm<T>>::deposit_log(901			erc::CollectionHelpersEvents::CollectionCreated {902				owner: *owner.as_eth(),903				collection_id: eth::collection_id_to_address(id),904			}905			.to_log(T::ContractAddress::get()),906		);907		<CollectionById<T>>::insert(id, collection);908		Ok(id)909	}910911	pub fn destroy_collection(912		collection: CollectionHandle<T>,913		sender: &T::CrossAccountId,914	) -> DispatchResult {915		ensure!(916			collection.limits.owner_can_destroy(),917			<Error<T>>::NoPermission,918		);919		collection.check_is_owner(sender)?;920921		let destroyed_collections = <DestroyedCollectionCount<T>>::get()922			.0923			.checked_add(1)924			.ok_or(ArithmeticError::Overflow)?;925926		// =========927928		<DestroyedCollectionCount<T>>::put(destroyed_collections);929		<CollectionById<T>>::remove(collection.id);930		<AdminAmount<T>>::remove(collection.id);931		<IsAdmin<T>>::remove_prefix((collection.id,), None);932		<Allowlist<T>>::remove_prefix((collection.id,), None);933		<CollectionProperties<T>>::remove(collection.id);934935		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));936		Ok(())937	}938939	pub fn set_collection_property(940		collection: &CollectionHandle<T>,941		sender: &T::CrossAccountId,942		property: Property,943	) -> DispatchResult {944		collection.check_is_owner_or_admin(sender)?;945946		CollectionProperties::<T>::try_mutate(collection.id, |properties| {947			let property = property.clone();948			properties.try_set(property.key, property.value)949		})950		.map_err(<Error<T>>::from)?;951952		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));953954		Ok(())955	}956957	pub fn set_scoped_collection_property(958		collection_id: CollectionId,959		scope: PropertyScope,960		property: Property,961	) -> DispatchResult {962		CollectionProperties::<T>::try_mutate(collection_id, |properties| {963			properties.try_scoped_set(scope, property.key, property.value)964		})965		.map_err(<Error<T>>::from)?;966967		Ok(())968	}969970	pub fn set_scoped_collection_properties(971		collection_id: CollectionId,972		scope: PropertyScope,973		properties: impl Iterator<Item = Property>,974	) -> DispatchResult {975		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {976			stored_properties.try_scoped_set_from_iter(scope, properties)977		})978		.map_err(<Error<T>>::from)?;979980		Ok(())981	}982983	#[transactional]984	pub fn set_collection_properties(985		collection: &CollectionHandle<T>,986		sender: &T::CrossAccountId,987		properties: Vec<Property>,988	) -> DispatchResult {989		for property in properties {990			Self::set_collection_property(collection, sender, property)?;991		}992993		Ok(())994	}995996	pub fn delete_collection_property(997		collection: &CollectionHandle<T>,998		sender: &T::CrossAccountId,999		property_key: PropertyKey,1000	) -> DispatchResult {1001		collection.check_is_owner_or_admin(sender)?;10021003		CollectionProperties::<T>::try_mutate(collection.id, |properties| {1004			properties.remove(&property_key)1005		})1006		.map_err(<Error<T>>::from)?;10071008		Self::deposit_event(Event::CollectionPropertyDeleted(1009			collection.id,1010			property_key,1011		));10121013		Ok(())1014	}10151016	#[transactional]1017	pub fn delete_collection_properties(1018		collection: &CollectionHandle<T>,1019		sender: &T::CrossAccountId,1020		property_keys: Vec<PropertyKey>,1021	) -> DispatchResult {1022		for key in property_keys {1023			Self::delete_collection_property(collection, sender, key)?;1024		}10251026		Ok(())1027	}10281029	// For migrations1030	pub fn set_property_permission_unchecked(1031		collection: CollectionId,1032		property_permission: PropertyKeyPermission,1033	) -> DispatchResult {1034		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1035			permissions.try_set(property_permission.key, property_permission.permission)1036		})1037		.map_err(<Error<T>>::from)?;1038		Ok(())1039	}10401041	pub fn set_property_permission(1042		collection: &CollectionHandle<T>,1043		sender: &T::CrossAccountId,1044		property_permission: PropertyKeyPermission,1045	) -> DispatchResult {1046		collection.check_is_owner_or_admin(sender)?;10471048		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1049		let current_permission = all_permissions.get(&property_permission.key);1050		if matches![1051			current_permission,1052			Some(PropertyPermission { mutable: false, .. })1053		] {1054			return Err(<Error<T>>::NoPermission.into());1055		}10561057		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1058			let property_permission = property_permission.clone();1059			permissions.try_set(property_permission.key, property_permission.permission)1060		})1061		.map_err(<Error<T>>::from)?;10621063		Self::deposit_event(Event::PropertyPermissionSet(1064			collection.id,1065			property_permission.key,1066		));10671068		Ok(())1069	}10701071	#[transactional]1072	pub fn set_token_property_permissions(1073		collection: &CollectionHandle<T>,1074		sender: &T::CrossAccountId,1075		property_permissions: Vec<PropertyKeyPermission>,1076	) -> DispatchResult {1077		for prop_pemission in property_permissions {1078			Self::set_property_permission(collection, sender, prop_pemission)?;1079		}10801081		Ok(())1082	}10831084	pub fn get_collection_property(1085		collection_id: CollectionId,1086		key: &PropertyKey,1087	) -> Option<PropertyValue> {1088		Self::collection_properties(collection_id).get(key).cloned()1089	}10901091	pub fn bytes_keys_to_property_keys(1092		keys: Vec<Vec<u8>>,1093	) -> Result<Vec<PropertyKey>, DispatchError> {1094		keys.into_iter()1095			.map(|key| -> Result<PropertyKey, DispatchError> {1096				key.try_into()1097					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1098			})1099			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1100	}11011102	pub fn filter_collection_properties(1103		collection_id: CollectionId,1104		keys: Option<Vec<PropertyKey>>,1105	) -> Result<Vec<Property>, DispatchError> {1106		let properties = Self::collection_properties(collection_id);11071108		let properties = keys1109			.map(|keys| {1110				keys.into_iter()1111					.filter_map(|key| {1112						properties.get(&key).map(|value| Property {1113							key,1114							value: value.clone(),1115						})1116					})1117					.collect()1118			})1119			.unwrap_or_else(|| {1120				properties1121					.into_iter()1122					.map(|(key, value)| Property { key, value })1123					.collect()1124			});11251126		Ok(properties)1127	}11281129	pub fn filter_property_permissions(1130		collection_id: CollectionId,1131		keys: Option<Vec<PropertyKey>>,1132	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1133		let permissions = Self::property_permissions(collection_id);11341135		let key_permissions = keys1136			.map(|keys| {1137				keys.into_iter()1138					.filter_map(|key| {1139						permissions1140							.get(&key)1141							.map(|permission| PropertyKeyPermission {1142								key,1143								permission: permission.clone(),1144							})1145					})1146					.collect()1147			})1148			.unwrap_or_else(|| {1149				permissions1150					.into_iter()1151					.map(|(key, permission)| PropertyKeyPermission { key, permission })1152					.collect()1153			});11541155		Ok(key_permissions)1156	}11571158	pub fn toggle_allowlist(1159		collection: &CollectionHandle<T>,1160		sender: &T::CrossAccountId,1161		user: &T::CrossAccountId,1162		allowed: bool,1163	) -> DispatchResult {1164		collection.check_is_owner_or_admin(sender)?;11651166		// =========11671168		if allowed {1169			<Allowlist<T>>::insert((collection.id, user), true);1170		} else {1171			<Allowlist<T>>::remove((collection.id, user));1172		}11731174		Ok(())1175	}11761177	pub fn toggle_admin(1178		collection: &CollectionHandle<T>,1179		sender: &T::CrossAccountId,1180		user: &T::CrossAccountId,1181		admin: bool,1182	) -> DispatchResult {1183		collection.check_is_owner(sender)?;11841185		let was_admin = <IsAdmin<T>>::get((collection.id, user));1186		if was_admin == admin {1187			return Ok(());1188		}1189		let amount = <AdminAmount<T>>::get(collection.id);11901191		if admin {1192			let amount = amount1193				.checked_add(1)1194				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1195			ensure!(1196				amount <= Self::collection_admins_limit(),1197				<Error<T>>::CollectionAdminCountExceeded,1198			);11991200			// =========12011202			<AdminAmount<T>>::insert(collection.id, amount);1203			<IsAdmin<T>>::insert((collection.id, user), true);1204		} else {1205			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1206			<IsAdmin<T>>::remove((collection.id, user));1207		}12081209		Ok(())1210	}12111212	pub fn clamp_limits(1213		mode: CollectionMode,1214		old_limit: &CollectionLimits,1215		mut new_limit: CollectionLimits,1216	) -> Result<CollectionLimits, DispatchError> {1217		let limits = old_limit;1218		limit_default!(old_limit, new_limit,1219			account_token_ownership_limit => ensure!(1220				new_limit <= MAX_TOKEN_OWNERSHIP,1221				<Error<T>>::CollectionLimitBoundsExceeded,1222			),1223			sponsored_data_size => ensure!(1224				new_limit <= CUSTOM_DATA_LIMIT,1225				<Error<T>>::CollectionLimitBoundsExceeded,1226			),12271228			sponsored_data_rate_limit => {},1229			token_limit => ensure!(1230				old_limit >= new_limit && new_limit > 0,1231				<Error<T>>::CollectionTokenLimitExceeded1232			),12331234			sponsor_transfer_timeout(match mode {1235				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1236				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1237				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1238			}) => ensure!(1239				new_limit <= MAX_SPONSOR_TIMEOUT,1240				<Error<T>>::CollectionLimitBoundsExceeded,1241			),1242			sponsor_approve_timeout => {},1243			owner_can_transfer => ensure!(1244				!limits.owner_can_transfer_instaled() ||1245				old_limit || !new_limit,1246				<Error<T>>::OwnerPermissionsCantBeReverted,1247			),1248			owner_can_destroy => ensure!(1249				old_limit || !new_limit,1250				<Error<T>>::OwnerPermissionsCantBeReverted,1251			),1252			transfers_enabled => {},1253		);1254		Ok(new_limit)1255	}12561257	pub fn clamp_permissions(1258		_mode: CollectionMode,1259		old_limit: &CollectionPermissions,1260		mut new_limit: CollectionPermissions,1261	) -> Result<CollectionPermissions, DispatchError> {1262		limit_default_clone!(old_limit, new_limit,1263			access => {},1264			mint_mode => {},1265			nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1266		);1267		Ok(new_limit)1268	}1269}12701271#[macro_export]1272macro_rules! unsupported {1273	() => {1274		Err(<Error<T>>::UnsupportedOperation.into())1275	};1276}12771278/// Worst cases1279pub trait CommonWeightInfo<CrossAccountId> {1280	fn create_item() -> Weight;1281	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1282	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1283	fn burn_item() -> Weight;1284	fn set_collection_properties(amount: u32) -> Weight;1285	fn delete_collection_properties(amount: u32) -> Weight;1286	fn set_token_properties(amount: u32) -> Weight;1287	fn delete_token_properties(amount: u32) -> Weight;1288	fn set_token_property_permissions(amount: u32) -> Weight;1289	fn transfer() -> Weight;1290	fn approve() -> Weight;1291	fn transfer_from() -> Weight;1292	fn burn_from() -> Weight;12931294	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1295	/// whole users's balance1296	///1297	/// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead1298	fn burn_recursively_self_raw() -> Weight;1299	/// Cost of iterating over `amount` children while burning, without counting child burning itself1300	///1301	/// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead1302	fn burn_recursively_breadth_raw(amount: u32) -> Weight;13031304	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1305		Self::burn_recursively_self_raw()1306			.saturating_mul(max_selfs.max(1) as u64)1307			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1308	}1309}13101311pub trait RefungibleExtensionsWeightInfo {1312	fn repartition() -> Weight;1313}13141315pub trait CommonCollectionOperations<T: Config> {1316	fn create_item(1317		&self,1318		sender: T::CrossAccountId,1319		to: T::CrossAccountId,1320		data: CreateItemData,1321		nesting_budget: &dyn Budget,1322	) -> DispatchResultWithPostInfo;1323	fn create_multiple_items(1324		&self,1325		sender: T::CrossAccountId,1326		to: T::CrossAccountId,1327		data: Vec<CreateItemData>,1328		nesting_budget: &dyn Budget,1329	) -> DispatchResultWithPostInfo;1330	fn create_multiple_items_ex(1331		&self,1332		sender: T::CrossAccountId,1333		data: CreateItemExData<T::CrossAccountId>,1334		nesting_budget: &dyn Budget,1335	) -> DispatchResultWithPostInfo;1336	fn burn_item(1337		&self,1338		sender: T::CrossAccountId,1339		token: TokenId,1340		amount: u128,1341	) -> DispatchResultWithPostInfo;1342	fn burn_item_recursively(1343		&self,1344		sender: T::CrossAccountId,1345		token: TokenId,1346		self_budget: &dyn Budget,1347		breadth_budget: &dyn Budget,1348	) -> DispatchResultWithPostInfo;1349	fn set_collection_properties(1350		&self,1351		sender: T::CrossAccountId,1352		properties: Vec<Property>,1353	) -> DispatchResultWithPostInfo;1354	fn delete_collection_properties(1355		&self,1356		sender: &T::CrossAccountId,1357		property_keys: Vec<PropertyKey>,1358	) -> DispatchResultWithPostInfo;1359	fn set_token_properties(1360		&self,1361		sender: T::CrossAccountId,1362		token_id: TokenId,1363		property: Vec<Property>,1364		nesting_budget: &dyn Budget,1365	) -> DispatchResultWithPostInfo;1366	fn delete_token_properties(1367		&self,1368		sender: T::CrossAccountId,1369		token_id: TokenId,1370		property_keys: Vec<PropertyKey>,1371		nesting_budget: &dyn Budget,1372	) -> DispatchResultWithPostInfo;1373	fn set_token_property_permissions(1374		&self,1375		sender: &T::CrossAccountId,1376		property_permissions: Vec<PropertyKeyPermission>,1377	) -> DispatchResultWithPostInfo;1378	fn transfer(1379		&self,1380		sender: T::CrossAccountId,1381		to: T::CrossAccountId,1382		token: TokenId,1383		amount: u128,1384		nesting_budget: &dyn Budget,1385	) -> DispatchResultWithPostInfo;1386	fn approve(1387		&self,1388		sender: T::CrossAccountId,1389		spender: T::CrossAccountId,1390		token: TokenId,1391		amount: u128,1392	) -> DispatchResultWithPostInfo;1393	fn transfer_from(1394		&self,1395		sender: T::CrossAccountId,1396		from: T::CrossAccountId,1397		to: T::CrossAccountId,1398		token: TokenId,1399		amount: u128,1400		nesting_budget: &dyn Budget,1401	) -> DispatchResultWithPostInfo;1402	fn burn_from(1403		&self,1404		sender: T::CrossAccountId,1405		from: T::CrossAccountId,1406		token: TokenId,1407		amount: u128,1408		nesting_budget: &dyn Budget,1409	) -> DispatchResultWithPostInfo;14101411	fn check_nesting(1412		&self,1413		sender: T::CrossAccountId,1414		from: (CollectionId, TokenId),1415		under: TokenId,1416		nesting_budget: &dyn Budget,1417	) -> DispatchResult;14181419	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));14201421	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));14221423	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1424	fn collection_tokens(&self) -> Vec<TokenId>;1425	fn token_exists(&self, token: TokenId) -> bool;1426	fn last_token_id(&self) -> TokenId;14271428	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1429	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1430	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1431	/// Amount of unique collection tokens1432	fn total_supply(&self) -> u32;1433	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1434	fn account_balance(&self, account: T::CrossAccountId) -> u32;1435	/// Amount of specific token account have (Applicable to fungible/refungible)1436	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1437	/// Amount of token pieces1438	fn total_pieces(&self, token: TokenId) -> Option<u128>;1439	fn allowance(1440		&self,1441		sender: T::CrossAccountId,1442		spender: T::CrossAccountId,1443		token: TokenId,1444	) -> u128;1445	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1446}14471448pub trait RefungibleExtensions<T>1449where1450	T: Config,1451{1452	fn repartition(1453		&self,1454		owner: &T::CrossAccountId,1455		token: TokenId,1456		amount: u128,1457	) -> DispatchResultWithPostInfo;1458}14591460// Flexible enough for implementing CommonCollectionOperations1461pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1462	let post_info = PostDispatchInfo {1463		actual_weight: Some(weight),1464		pays_fee: Pays::Yes,1465	};1466	match res {1467		Ok(()) => Ok(post_info),1468		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1469	}1470}14711472impl<T: Config> From<PropertiesError> for Error<T> {1473	fn from(error: PropertiesError) -> Self {1474		match error {1475			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1476			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1477			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1478			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1479			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1480		}1481	}1482}
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	RmrkBoundedTheme,78	RmrkNftChild,79	CollectionPermissions,80	SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97	pub id: CollectionId,98	collection: Collection<T::AccountId>,99	pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102	fn recorder(&self) -> &SubstrateRecorder<T> {103		&self.recorder104	}105	fn into_recorder(self) -> SubstrateRecorder<T> {106		self.recorder107	}108}109impl<T: Config> CollectionHandle<T> {110	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111		<CollectionById<T>>::get(id).map(|collection| Self {112			id,113			collection,114			recorder: SubstrateRecorder::new(gas_limit),115		})116	}117118	pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {119		<CollectionById<T>>::get(id).map(|collection| Self {120			id,121			collection,122			recorder,123		})124	}125126	pub fn new(id: CollectionId) -> Option<Self> {127		Self::new_with_gas_limit(id, u64::MAX)128	}129130	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {131		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)132	}133134	pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {135		self.recorder136			.consume_gas(T::GasWeightMapping::weight_to_gas(137				<T as frame_system::Config>::DbWeight::get()138					.read139					.saturating_mul(reads),140			))141	}142143	pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {144		self.recorder145			.consume_gas(T::GasWeightMapping::weight_to_gas(146				<T as frame_system::Config>::DbWeight::get()147					.write148					.saturating_mul(writes),149			))150	}151	pub fn save(self) -> DispatchResult {152		<CollectionById<T>>::insert(self.id, self.collection);153		Ok(())154	}155156	pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {157		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);158		Ok(())159	}160161	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {162		if self.collection.sponsorship.pending_sponsor() != Some(sender) {163			return Ok(false);164		}165166		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());167		Ok(true)168	}169170	/// Checks that the collection was created with, and must be operated upon through **Unique API**.171	/// Now check only the `external_collection` flag and if it's **true**, then return `CollectionIsExternal` error.172	pub fn check_is_internal(&self) -> DispatchResult {173		if self.external_collection {174			return Err(<Error<T>>::CollectionIsExternal)?;175		}176177		Ok(())178	}179180	/// Checks that the collection was created with, and must be operated upon through an **assimilated API**.181	/// Now check only the `external_collection` flag and if it's **false**, then return `CollectionIsInternal` error.182	pub fn check_is_external(&self) -> DispatchResult {183		if !self.external_collection {184			return Err(<Error<T>>::CollectionIsInternal)?;185		}186187		Ok(())188	}189}190191impl<T: Config> Deref for CollectionHandle<T> {192	type Target = Collection<T::AccountId>;193194	fn deref(&self) -> &Self::Target {195		&self.collection196	}197}198199impl<T: Config> DerefMut for CollectionHandle<T> {200	fn deref_mut(&mut self) -> &mut Self::Target {201		&mut self.collection202	}203}204205impl<T: Config> CollectionHandle<T> {206	pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {207		ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);208		Ok(())209	}210	pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {211		*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))212	}213	pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {214		ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);215		Ok(())216	}217	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {218		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)219	}220	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {221		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)222	}223	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {224		ensure!(225			<Allowlist<T>>::get((self.id, user)),226			<Error<T>>::AddressNotInAllowlist227		);228		Ok(())229	}230}231232#[frame_support::pallet]233pub mod pallet {234	use super::*;235	use pallet_evm::account;236	use dispatch::CollectionDispatch;237	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};238	use frame_system::pallet_prelude::*;239	use frame_support::traits::Currency;240	use up_data_structs::{TokenId, mapping::TokenAddressMapping};241	use scale_info::TypeInfo;242	use weights::WeightInfo;243244	#[pallet::config]245	pub trait Config:246		frame_system::Config247		+ pallet_evm_coder_substrate::Config248		+ pallet_evm::Config249		+ TypeInfo250		+ account::Config251	{252		type WeightInfo: WeightInfo;253		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;254255		type Currency: Currency<Self::AccountId>;256257		#[pallet::constant]258		type CollectionCreationPrice: Get<259			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,260		>;261		type CollectionDispatch: CollectionDispatch<Self>;262263		type TreasuryAccountId: Get<Self::AccountId>;264		type ContractAddress: Get<H160>;265266		type EvmTokenAddressMapping: TokenAddressMapping<H160>;267		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;268	}269270	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);271272	#[pallet::pallet]273	#[pallet::storage_version(STORAGE_VERSION)]274	#[pallet::generate_store(pub(super) trait Store)]275	pub struct Pallet<T>(_);276277	#[pallet::extra_constants]278	impl<T: Config> Pallet<T> {279		pub fn collection_admins_limit() -> u32 {280			COLLECTION_ADMINS_LIMIT281		}282	}283284	#[pallet::event]285	#[pallet::generate_deposit(pub fn deposit_event)]286	pub enum Event<T: Config> {287		/// New collection was created288		///289		/// # Arguments290		///291		/// * collection_id: Globally unique identifier of newly created collection.292		///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 that has been destroyed.303		CollectionDestroyed(CollectionId),304305		/// New item was created.306		///307		/// # Arguments308		///309		/// * collection_id: ID of the collection where the item was created.310		///311		/// * item_id: ID of the item. Unique within the collection.312		///313		/// * recipient: Owner of the newly created item.314		///315		/// * amount: The amount of tokens that were created (always 1 for NFT).316		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),317318		/// Collection item was burned.319		///320		/// # Arguments321		///322		/// * collection_id: Identifier of the collection to which the burned NFT belonged.323		///324		/// * item_id: Identifier of burned NFT.325		///326		/// * owner: Which user has destroyed their tokens.327		///328		/// * amount: The amount of tokens that were destroyed (always 1 for NFT).329		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),330331		/// Item was transferred.332		///333		/// # Arguments334		///335		/// * collection_id: ID of the collection to which the item belongs.336		///337		/// * item_id: ID of the item trasnferred.338		///339		/// * sender: Original owner of the item.340		///341		/// * recipient: New owner of the item.342		///343		/// * amount: The amount of tokens that were transferred (always 1 for NFT).344		Transfer(345			CollectionId,346			TokenId,347			T::CrossAccountId,348			T::CrossAccountId,349			u128,350		),351352		/// Sponsoring allowance was approved.353		///354		/// # Arguments355		///356		/// * collection_id357		///358		/// * item_id359		///360		/// * sender361		///362		/// * spender363		///364		/// * amount365		Approved(366			CollectionId,367			TokenId,368			T::CrossAccountId,369			T::CrossAccountId,370			u128,371		),372373		/// Collection property was added or edited.374		///375		/// # Arguments376		///377		/// * collection_id: ID of the collection, whose property was just set.378		///379		/// * property_key: Key of the property that was just set.380		CollectionPropertySet(CollectionId, PropertyKey),381382		/// Collection property was deleted.383		///384		/// # Arguments385		///386		/// * collection_id: ID of the collection, whose property was just deleted.387		///388		/// * property_key: Key of the property that was just deleted.389		CollectionPropertyDeleted(CollectionId, PropertyKey),390391		/// Item property was added or edited.392		///393		/// # Arguments394		///395		/// * collection_id: ID of the collection, whose token's property was just set.396		///397		/// * item_id: ID of the item, whose property was just set.398		///399		/// * property_key: Key of the property that was just set.400		TokenPropertySet(CollectionId, TokenId, PropertyKey),401402		/// Item property was deleted.403		///404		/// # Arguments405		///406		/// * collection_id: ID of the collection, whose token's property was just deleted.407		///408		/// * item_id: ID of the item, whose property was just deleted.409		///410		/// * property_key: Key of the property that was just deleted.411		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),412413		/// Token property permission was added or updated for a collection.414		///415		/// # Arguments416		///417		/// * collection_id: ID of the collection, whose permissions were just set/updated.418		///419		/// * property_key: Key of the property of the set/updated permission.420		PropertyPermissionSet(CollectionId, PropertyKey),421	}422423	#[pallet::error]424	pub enum Error<T> {425		/// This collection does not exist.426		CollectionNotFound,427		/// Sender parameter and item owner must be equal.428		MustBeTokenOwner,429		/// No permission to perform action430		NoPermission,431		/// Destroying only empty collections is allowed432		CantDestroyNotEmptyCollection,433		/// Collection is not in mint mode.434		PublicMintingNotAllowed,435		/// Address is not in allow list.436		AddressNotInAllowlist,437438		/// Collection name can not be longer than 63 char.439		CollectionNameLimitExceeded,440		/// Collection description can not be longer than 255 char.441		CollectionDescriptionLimitExceeded,442		/// Token prefix can not be longer than 15 char.443		CollectionTokenPrefixLimitExceeded,444		/// Total collections bound exceeded.445		TotalCollectionsLimitExceeded,446		/// Exceeded max admin count447		CollectionAdminCountExceeded,448		/// Collection limit bounds per collection exceeded449		CollectionLimitBoundsExceeded,450		/// Tried to enable permissions which are only permitted to be disabled451		OwnerPermissionsCantBeReverted,452		/// Collection settings not allowing items transferring453		TransferNotAllowed,454		/// Account token limit exceeded per collection455		AccountTokenLimitExceeded,456		/// Collection token limit exceeded457		CollectionTokenLimitExceeded,458		/// Metadata flag frozen459		MetadataFlagFrozen,460461		/// Item does not exist462		TokenNotFound,463		/// Item is balance not enough464		TokenValueTooLow,465		/// Requested value is more than the approved466		ApprovedValueTooLow,467		/// Tried to approve more than owned468		CantApproveMoreThanOwned,469470		/// Can't transfer tokens to ethereum zero address471		AddressIsZero,472		/// Target collection doesn't support this operation473		UnsupportedOperation,474475		/// Insufficient funds to perform an action476		NotSufficientFounds,477478		/// User does not satisfy the nesting rule479		UserIsNotAllowedToNest,480		/// Only tokens from specific collections may nest tokens under this one481		SourceCollectionIsNotAllowedToNest,482483		/// Tried to store more data than allowed in collection field484		CollectionFieldSizeExceeded,485486		/// Tried to store more property data than allowed487		NoSpaceForProperty,488489		/// Tried to store more property keys than allowed490		PropertyLimitReached,491492		/// Property key is too long493		PropertyKeyIsTooLong,494495		/// Only ASCII letters, digits, and symbols '_', '-', and '.' are allowed496		InvalidCharacterInPropertyKey,497498		/// Empty property keys are forbidden499		EmptyPropertyKey,500501		/// Tried to access an external collection with an internal API502		CollectionIsExternal,503504		/// Tried to access an internal collection with an external API505		CollectionIsInternal,506	}507508	/// The number of created collections. Essentially contains the last collection ID.509	#[pallet::storage]510	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;511512	/// The number of destroyed collections513	#[pallet::storage]514	pub type DestroyedCollectionCount<T> =515		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;516517	/// Collection info518	#[pallet::storage]519	pub type CollectionById<T> = StorageMap<520		Hasher = Blake2_128Concat,521		Key = CollectionId,522		Value = Collection<<T as frame_system::Config>::AccountId>,523		QueryKind = OptionQuery,524	>;525526	/// Collection properties527	#[pallet::storage]528	#[pallet::getter(fn collection_properties)]529	pub type CollectionProperties<T> = StorageMap<530		Hasher = Blake2_128Concat,531		Key = CollectionId,532		Value = Properties,533		QueryKind = ValueQuery,534		OnEmpty = up_data_structs::CollectionProperties,535	>;536537	/// Token permissions of a collection538	#[pallet::storage]539	#[pallet::getter(fn property_permissions)]540	pub type CollectionPropertyPermissions<T> = StorageMap<541		Hasher = Blake2_128Concat,542		Key = CollectionId,543		Value = PropertiesPermissionMap,544		QueryKind = ValueQuery,545	>;546547	/// Amount of collection admins548	#[pallet::storage]549	pub type AdminAmount<T> = StorageMap<550		Hasher = Blake2_128Concat,551		Key = CollectionId,552		Value = u32,553		QueryKind = ValueQuery,554	>;555556	/// List of collection admins557	#[pallet::storage]558	pub type IsAdmin<T: Config> = StorageNMap<559		Key = (560			Key<Blake2_128Concat, CollectionId>,561			Key<Blake2_128Concat, T::CrossAccountId>,562		),563		Value = bool,564		QueryKind = ValueQuery,565	>;566567	/// Allowlisted collection users568	#[pallet::storage]569	pub type Allowlist<T: Config> = StorageNMap<570		Key = (571			Key<Blake2_128Concat, CollectionId>,572			Key<Blake2_128Concat, T::CrossAccountId>,573		),574		Value = bool,575		QueryKind = ValueQuery,576	>;577578	/// Not used by code, exists only to provide some types to metadata579	#[pallet::storage]580	pub type DummyStorageValue<T: Config> = StorageValue<581		Value = (582			CollectionStats,583			CollectionId,584			TokenId,585			TokenChild,586			PhantomType<(587				TokenData<T::CrossAccountId>,588				RpcCollection<T::AccountId>,589				// RMRK590				RmrkCollectionInfo<T::AccountId>,591				RmrkInstanceInfo<T::AccountId>,592				RmrkResourceInfo,593				RmrkPropertyInfo,594				RmrkBaseInfo<T::AccountId>,595				RmrkPartType,596				RmrkBoundedTheme,597				RmrkNftChild,598			)>,599		),600		QueryKind = OptionQuery,601	>;602603	#[pallet::hooks]604	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {605		fn on_runtime_upgrade() -> Weight {606			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {607				use up_data_structs::{CollectionVersion1, CollectionVersion2};608				<CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {609					let mut props = Vec::new();610					if !v.offchain_schema.is_empty() {611						props.push(Property {612							key: b"_old_offchainSchema".to_vec().try_into().unwrap(),613							value: v614								.offchain_schema615								.clone()616								.into_inner()617								.try_into()618								.expect("offchain schema too big"),619						});620					}621					if !v.variable_on_chain_schema.is_empty() {622						props.push(Property {623							key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),624							value: v625								.variable_on_chain_schema626								.clone()627								.into_inner()628								.try_into()629								.expect("offchain schema too big"),630						});631					}632					if !v.const_on_chain_schema.is_empty() {633						props.push(Property {634							key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),635							value: v636								.const_on_chain_schema637								.clone()638								.into_inner()639								.try_into()640								.expect("offchain schema too big"),641						});642					}643					props.push(Property {644						key: b"_old_schemaVersion".to_vec().try_into().unwrap(),645						value: match v.schema_version {646							SchemaVersion::ImageURL => b"ImageUrl".as_slice(),647							SchemaVersion::Unique => b"Unique".as_slice(),648						}649						.to_vec()650						.try_into()651						.unwrap(),652					});653					Self::set_scoped_collection_properties(654						id,655						PropertyScope::None,656						props.into_iter(),657					)658					.expect("existing data larger than properties");659					let mut new = CollectionVersion2::from(v.clone());660					new.permissions.access = Some(v.access);661					new.permissions.mint_mode = Some(v.mint_mode);662					Some(new)663				});664			}665666			0667		}668	}669}670671impl<T: Config> Pallet<T> {672	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens673	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {674		ensure!(675			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,676			<Error<T>>::AddressIsZero677		);678		Ok(())679	}680	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {681		<IsAdmin<T>>::iter_prefix((collection,))682			.map(|(a, _)| a)683			.collect()684	}685	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {686		<Allowlist<T>>::iter_prefix((collection,))687			.map(|(a, _)| a)688			.collect()689	}690	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {691		<Allowlist<T>>::get((collection, user))692	}693	pub fn collection_stats() -> CollectionStats {694		let created = <CreatedCollectionCount<T>>::get();695		let destroyed = <DestroyedCollectionCount<T>>::get();696		CollectionStats {697			created: created.0,698			destroyed: destroyed.0,699			alive: created.0 - destroyed.0,700		}701	}702703	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {704		let collection = <CollectionById<T>>::get(collection);705		if collection.is_none() {706			return None;707		}708709		let collection = collection.unwrap();710		let limits = collection.limits;711		let effective_limits = CollectionLimits {712			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),713			sponsored_data_size: Some(limits.sponsored_data_size()),714			sponsored_data_rate_limit: Some(715				limits716					.sponsored_data_rate_limit717					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),718			),719			token_limit: Some(limits.token_limit()),720			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(721				match collection.mode {722					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,723					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,724					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,725				},726			)),727			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),728			owner_can_transfer: Some(limits.owner_can_transfer()),729			owner_can_destroy: Some(limits.owner_can_destroy()),730			transfers_enabled: Some(limits.transfers_enabled()),731		};732733		Some(effective_limits)734	}735736	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {737		let Collection {738			name,739			description,740			owner,741			mode,742			token_prefix,743			sponsorship,744			limits,745			permissions,746			external_collection,747		} = <CollectionById<T>>::get(collection)?;748749		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)750			.into_iter()751			.map(|(key, permission)| PropertyKeyPermission { key, permission })752			.collect();753754		let properties = <CollectionProperties<T>>::get(collection)755			.into_iter()756			.map(|(key, value)| Property { key, value })757			.collect();758759		let permissions = CollectionPermissions {760			access: Some(permissions.access()),761			mint_mode: Some(permissions.mint_mode()),762			nesting: Some(permissions.nesting().clone()),763		};764765		Some(RpcCollection {766			name: name.into_inner(),767			description: description.into_inner(),768			owner,769			mode,770			token_prefix: token_prefix.into_inner(),771			sponsorship,772			limits,773			permissions,774			token_property_permissions,775			properties,776			read_only: external_collection,777		})778	}779}780781macro_rules! limit_default {782	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{783		$(784			if let Some($new) = $new.$field {785				let $old = $old.$field($($arg)?);786				let _ = $new;787				let _ = $old;788				$check789			} else {790				$new.$field = $old.$field791			}792		)*793	}};794}795macro_rules! limit_default_clone {796	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{797		$(798			if let Some($new) = $new.$field.clone() {799				let $old = $old.$field($($arg)?);800				let _ = $new;801				let _ = $old;802				$check803			} else {804				$new.$field = $old.$field.clone()805			}806		)*807	}};808}809810impl<T: Config> Pallet<T> {811	pub fn init_collection(812		owner: T::CrossAccountId,813		data: CreateCollectionData<T::AccountId>,814		is_external: bool,815	) -> Result<CollectionId, DispatchError> {816		{817			ensure!(818				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,819				Error::<T>::CollectionTokenPrefixLimitExceeded820			);821		}822823		let created_count = <CreatedCollectionCount<T>>::get()824			.0825			.checked_add(1)826			.ok_or(ArithmeticError::Overflow)?;827		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;828		let id = CollectionId(created_count);829830		// bound Total number of collections831		ensure!(832			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,833			<Error<T>>::TotalCollectionsLimitExceeded834		);835836		// =========837838		let collection = Collection {839			owner: owner.as_sub().clone(),840			name: data.name,841			mode: data.mode.clone(),842			description: data.description,843			token_prefix: data.token_prefix,844			sponsorship: data845				.pending_sponsor846				.map(SponsorshipState::Unconfirmed)847				.unwrap_or_default(),848			limits: data849				.limits850				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))851				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,852			permissions: data853				.permissions854				.map(|permissions| {855					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)856				})857				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,858			external_collection: is_external,859		};860861		let mut collection_properties = up_data_structs::CollectionProperties::get();862		collection_properties863			.try_set_from_iter(data.properties.into_iter())864			.map_err(<Error<T>>::from)?;865866		CollectionProperties::<T>::insert(id, collection_properties);867868		let mut token_props_permissions = PropertiesPermissionMap::new();869		token_props_permissions870			.try_set_from_iter(data.token_property_permissions.into_iter())871			.map_err(<Error<T>>::from)?;872873		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);874875		// Take a (non-refundable) deposit of collection creation876		{877			let mut imbalance =878				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();879			imbalance.subsume(880				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(881					&T::TreasuryAccountId::get(),882					T::CollectionCreationPrice::get(),883				),884			);885			<T as Config>::Currency::settle(886				&owner.as_sub(),887				imbalance,888				WithdrawReasons::TRANSFER,889				ExistenceRequirement::KeepAlive,890			)891			.map_err(|_| Error::<T>::NotSufficientFounds)?;892		}893894		<CreatedCollectionCount<T>>::put(created_count);895		<Pallet<T>>::deposit_event(Event::CollectionCreated(896			id,897			data.mode.id(),898			owner.as_sub().clone(),899		));900		<PalletEvm<T>>::deposit_log(901			erc::CollectionHelpersEvents::CollectionCreated {902				owner: *owner.as_eth(),903				collection_id: eth::collection_id_to_address(id),904			}905			.to_log(T::ContractAddress::get()),906		);907		<CollectionById<T>>::insert(id, collection);908		Ok(id)909	}910911	pub fn destroy_collection(912		collection: CollectionHandle<T>,913		sender: &T::CrossAccountId,914	) -> DispatchResult {915		ensure!(916			collection.limits.owner_can_destroy(),917			<Error<T>>::NoPermission,918		);919		collection.check_is_owner(sender)?;920921		let destroyed_collections = <DestroyedCollectionCount<T>>::get()922			.0923			.checked_add(1)924			.ok_or(ArithmeticError::Overflow)?;925926		// =========927928		<DestroyedCollectionCount<T>>::put(destroyed_collections);929		<CollectionById<T>>::remove(collection.id);930		<AdminAmount<T>>::remove(collection.id);931		<IsAdmin<T>>::remove_prefix((collection.id,), None);932		<Allowlist<T>>::remove_prefix((collection.id,), None);933		<CollectionProperties<T>>::remove(collection.id);934935		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));936		Ok(())937	}938939	pub fn set_collection_property(940		collection: &CollectionHandle<T>,941		sender: &T::CrossAccountId,942		property: Property,943	) -> DispatchResult {944		collection.check_is_owner_or_admin(sender)?;945946		CollectionProperties::<T>::try_mutate(collection.id, |properties| {947			let property = property.clone();948			properties.try_set(property.key, property.value)949		})950		.map_err(<Error<T>>::from)?;951952		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));953954		Ok(())955	}956957	pub fn set_scoped_collection_property(958		collection_id: CollectionId,959		scope: PropertyScope,960		property: Property,961	) -> DispatchResult {962		CollectionProperties::<T>::try_mutate(collection_id, |properties| {963			properties.try_scoped_set(scope, property.key, property.value)964		})965		.map_err(<Error<T>>::from)?;966967		Ok(())968	}969970	pub fn set_scoped_collection_properties(971		collection_id: CollectionId,972		scope: PropertyScope,973		properties: impl Iterator<Item = Property>,974	) -> DispatchResult {975		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {976			stored_properties.try_scoped_set_from_iter(scope, properties)977		})978		.map_err(<Error<T>>::from)?;979980		Ok(())981	}982983	#[transactional]984	pub fn set_collection_properties(985		collection: &CollectionHandle<T>,986		sender: &T::CrossAccountId,987		properties: Vec<Property>,988	) -> DispatchResult {989		for property in properties {990			Self::set_collection_property(collection, sender, property)?;991		}992993		Ok(())994	}995996	pub fn delete_collection_property(997		collection: &CollectionHandle<T>,998		sender: &T::CrossAccountId,999		property_key: PropertyKey,1000	) -> DispatchResult {1001		collection.check_is_owner_or_admin(sender)?;10021003		CollectionProperties::<T>::try_mutate(collection.id, |properties| {1004			properties.remove(&property_key)1005		})1006		.map_err(<Error<T>>::from)?;10071008		Self::deposit_event(Event::CollectionPropertyDeleted(1009			collection.id,1010			property_key,1011		));10121013		Ok(())1014	}10151016	#[transactional]1017	pub fn delete_collection_properties(1018		collection: &CollectionHandle<T>,1019		sender: &T::CrossAccountId,1020		property_keys: Vec<PropertyKey>,1021	) -> DispatchResult {1022		for key in property_keys {1023			Self::delete_collection_property(collection, sender, key)?;1024		}10251026		Ok(())1027	}10281029	// For migrations1030	pub fn set_property_permission_unchecked(1031		collection: CollectionId,1032		property_permission: PropertyKeyPermission,1033	) -> DispatchResult {1034		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1035			permissions.try_set(property_permission.key, property_permission.permission)1036		})1037		.map_err(<Error<T>>::from)?;1038		Ok(())1039	}10401041	pub fn set_property_permission(1042		collection: &CollectionHandle<T>,1043		sender: &T::CrossAccountId,1044		property_permission: PropertyKeyPermission,1045	) -> DispatchResult {1046		collection.check_is_owner_or_admin(sender)?;10471048		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1049		let current_permission = all_permissions.get(&property_permission.key);1050		if matches![1051			current_permission,1052			Some(PropertyPermission { mutable: false, .. })1053		] {1054			return Err(<Error<T>>::NoPermission.into());1055		}10561057		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1058			let property_permission = property_permission.clone();1059			permissions.try_set(property_permission.key, property_permission.permission)1060		})1061		.map_err(<Error<T>>::from)?;10621063		Self::deposit_event(Event::PropertyPermissionSet(1064			collection.id,1065			property_permission.key,1066		));10671068		Ok(())1069	}10701071	#[transactional]1072	pub fn set_token_property_permissions(1073		collection: &CollectionHandle<T>,1074		sender: &T::CrossAccountId,1075		property_permissions: Vec<PropertyKeyPermission>,1076	) -> DispatchResult {1077		for prop_pemission in property_permissions {1078			Self::set_property_permission(collection, sender, prop_pemission)?;1079		}10801081		Ok(())1082	}10831084	pub fn get_collection_property(1085		collection_id: CollectionId,1086		key: &PropertyKey,1087	) -> Option<PropertyValue> {1088		Self::collection_properties(collection_id).get(key).cloned()1089	}10901091	pub fn bytes_keys_to_property_keys(1092		keys: Vec<Vec<u8>>,1093	) -> Result<Vec<PropertyKey>, DispatchError> {1094		keys.into_iter()1095			.map(|key| -> Result<PropertyKey, DispatchError> {1096				key.try_into()1097					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1098			})1099			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1100	}11011102	pub fn filter_collection_properties(1103		collection_id: CollectionId,1104		keys: Option<Vec<PropertyKey>>,1105	) -> Result<Vec<Property>, DispatchError> {1106		let properties = Self::collection_properties(collection_id);11071108		let properties = keys1109			.map(|keys| {1110				keys.into_iter()1111					.filter_map(|key| {1112						properties.get(&key).map(|value| Property {1113							key,1114							value: value.clone(),1115						})1116					})1117					.collect()1118			})1119			.unwrap_or_else(|| {1120				properties1121					.into_iter()1122					.map(|(key, value)| Property { key, value })1123					.collect()1124			});11251126		Ok(properties)1127	}11281129	pub fn filter_property_permissions(1130		collection_id: CollectionId,1131		keys: Option<Vec<PropertyKey>>,1132	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1133		let permissions = Self::property_permissions(collection_id);11341135		let key_permissions = keys1136			.map(|keys| {1137				keys.into_iter()1138					.filter_map(|key| {1139						permissions1140							.get(&key)1141							.map(|permission| PropertyKeyPermission {1142								key,1143								permission: permission.clone(),1144							})1145					})1146					.collect()1147			})1148			.unwrap_or_else(|| {1149				permissions1150					.into_iter()1151					.map(|(key, permission)| PropertyKeyPermission { key, permission })1152					.collect()1153			});11541155		Ok(key_permissions)1156	}11571158	pub fn toggle_allowlist(1159		collection: &CollectionHandle<T>,1160		sender: &T::CrossAccountId,1161		user: &T::CrossAccountId,1162		allowed: bool,1163	) -> DispatchResult {1164		collection.check_is_owner_or_admin(sender)?;11651166		// =========11671168		if allowed {1169			<Allowlist<T>>::insert((collection.id, user), true);1170		} else {1171			<Allowlist<T>>::remove((collection.id, user));1172		}11731174		Ok(())1175	}11761177	pub fn toggle_admin(1178		collection: &CollectionHandle<T>,1179		sender: &T::CrossAccountId,1180		user: &T::CrossAccountId,1181		admin: bool,1182	) -> DispatchResult {1183		collection.check_is_owner(sender)?;11841185		let was_admin = <IsAdmin<T>>::get((collection.id, user));1186		if was_admin == admin {1187			return Ok(());1188		}1189		let amount = <AdminAmount<T>>::get(collection.id);11901191		if admin {1192			let amount = amount1193				.checked_add(1)1194				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1195			ensure!(1196				amount <= Self::collection_admins_limit(),1197				<Error<T>>::CollectionAdminCountExceeded,1198			);11991200			// =========12011202			<AdminAmount<T>>::insert(collection.id, amount);1203			<IsAdmin<T>>::insert((collection.id, user), true);1204		} else {1205			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1206			<IsAdmin<T>>::remove((collection.id, user));1207		}12081209		Ok(())1210	}12111212	pub fn clamp_limits(1213		mode: CollectionMode,1214		old_limit: &CollectionLimits,1215		mut new_limit: CollectionLimits,1216	) -> Result<CollectionLimits, DispatchError> {1217		let limits = old_limit;1218		limit_default!(old_limit, new_limit,1219			account_token_ownership_limit => ensure!(1220				new_limit <= MAX_TOKEN_OWNERSHIP,1221				<Error<T>>::CollectionLimitBoundsExceeded,1222			),1223			sponsored_data_size => ensure!(1224				new_limit <= CUSTOM_DATA_LIMIT,1225				<Error<T>>::CollectionLimitBoundsExceeded,1226			),12271228			sponsored_data_rate_limit => {},1229			token_limit => ensure!(1230				old_limit >= new_limit && new_limit > 0,1231				<Error<T>>::CollectionTokenLimitExceeded1232			),12331234			sponsor_transfer_timeout(match mode {1235				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1236				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1237				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1238			}) => ensure!(1239				new_limit <= MAX_SPONSOR_TIMEOUT,1240				<Error<T>>::CollectionLimitBoundsExceeded,1241			),1242			sponsor_approve_timeout => {},1243			owner_can_transfer => ensure!(1244				!limits.owner_can_transfer_instaled() ||1245				old_limit || !new_limit,1246				<Error<T>>::OwnerPermissionsCantBeReverted,1247			),1248			owner_can_destroy => ensure!(1249				old_limit || !new_limit,1250				<Error<T>>::OwnerPermissionsCantBeReverted,1251			),1252			transfers_enabled => {},1253		);1254		Ok(new_limit)1255	}12561257	pub fn clamp_permissions(1258		_mode: CollectionMode,1259		old_limit: &CollectionPermissions,1260		mut new_limit: CollectionPermissions,1261	) -> Result<CollectionPermissions, DispatchError> {1262		limit_default_clone!(old_limit, new_limit,1263			access => {},1264			mint_mode => {},1265			nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1266		);1267		Ok(new_limit)1268	}1269}12701271#[macro_export]1272macro_rules! unsupported {1273	() => {1274		Err(<Error<T>>::UnsupportedOperation.into())1275	};1276}12771278/// Worst cases1279pub trait CommonWeightInfo<CrossAccountId> {1280	fn create_item() -> Weight;1281	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1282	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1283	fn burn_item() -> Weight;1284	fn set_collection_properties(amount: u32) -> Weight;1285	fn delete_collection_properties(amount: u32) -> Weight;1286	fn set_token_properties(amount: u32) -> Weight;1287	fn delete_token_properties(amount: u32) -> Weight;1288	fn set_token_property_permissions(amount: u32) -> Weight;1289	fn transfer() -> Weight;1290	fn approve() -> Weight;1291	fn transfer_from() -> Weight;1292	fn burn_from() -> Weight;12931294	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1295	/// whole users's balance1296	///1297	/// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead1298	fn burn_recursively_self_raw() -> Weight;1299	/// Cost of iterating over `amount` children while burning, without counting child burning itself1300	///1301	/// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead1302	fn burn_recursively_breadth_raw(amount: u32) -> Weight;13031304	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1305		Self::burn_recursively_self_raw()1306			.saturating_mul(max_selfs.max(1) as u64)1307			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1308	}1309}13101311pub trait RefungibleExtensionsWeightInfo {1312	fn repartition() -> Weight;1313}13141315pub trait CommonCollectionOperations<T: Config> {1316	fn create_item(1317		&self,1318		sender: T::CrossAccountId,1319		to: T::CrossAccountId,1320		data: CreateItemData,1321		nesting_budget: &dyn Budget,1322	) -> DispatchResultWithPostInfo;1323	fn create_multiple_items(1324		&self,1325		sender: T::CrossAccountId,1326		to: T::CrossAccountId,1327		data: Vec<CreateItemData>,1328		nesting_budget: &dyn Budget,1329	) -> DispatchResultWithPostInfo;1330	fn create_multiple_items_ex(1331		&self,1332		sender: T::CrossAccountId,1333		data: CreateItemExData<T::CrossAccountId>,1334		nesting_budget: &dyn Budget,1335	) -> DispatchResultWithPostInfo;1336	fn burn_item(1337		&self,1338		sender: T::CrossAccountId,1339		token: TokenId,1340		amount: u128,1341	) -> DispatchResultWithPostInfo;1342	fn burn_item_recursively(1343		&self,1344		sender: T::CrossAccountId,1345		token: TokenId,1346		self_budget: &dyn Budget,1347		breadth_budget: &dyn Budget,1348	) -> DispatchResultWithPostInfo;1349	fn set_collection_properties(1350		&self,1351		sender: T::CrossAccountId,1352		properties: Vec<Property>,1353	) -> DispatchResultWithPostInfo;1354	fn delete_collection_properties(1355		&self,1356		sender: &T::CrossAccountId,1357		property_keys: Vec<PropertyKey>,1358	) -> DispatchResultWithPostInfo;1359	fn set_token_properties(1360		&self,1361		sender: T::CrossAccountId,1362		token_id: TokenId,1363		property: Vec<Property>,1364		nesting_budget: &dyn Budget,1365	) -> DispatchResultWithPostInfo;1366	fn delete_token_properties(1367		&self,1368		sender: T::CrossAccountId,1369		token_id: TokenId,1370		property_keys: Vec<PropertyKey>,1371		nesting_budget: &dyn Budget,1372	) -> DispatchResultWithPostInfo;1373	fn set_token_property_permissions(1374		&self,1375		sender: &T::CrossAccountId,1376		property_permissions: Vec<PropertyKeyPermission>,1377	) -> DispatchResultWithPostInfo;1378	fn transfer(1379		&self,1380		sender: T::CrossAccountId,1381		to: T::CrossAccountId,1382		token: TokenId,1383		amount: u128,1384		nesting_budget: &dyn Budget,1385	) -> DispatchResultWithPostInfo;1386	fn approve(1387		&self,1388		sender: T::CrossAccountId,1389		spender: T::CrossAccountId,1390		token: TokenId,1391		amount: u128,1392	) -> DispatchResultWithPostInfo;1393	fn transfer_from(1394		&self,1395		sender: T::CrossAccountId,1396		from: T::CrossAccountId,1397		to: T::CrossAccountId,1398		token: TokenId,1399		amount: u128,1400		nesting_budget: &dyn Budget,1401	) -> DispatchResultWithPostInfo;1402	fn burn_from(1403		&self,1404		sender: T::CrossAccountId,1405		from: T::CrossAccountId,1406		token: TokenId,1407		amount: u128,1408		nesting_budget: &dyn Budget,1409	) -> DispatchResultWithPostInfo;14101411	fn check_nesting(1412		&self,1413		sender: T::CrossAccountId,1414		from: (CollectionId, TokenId),1415		under: TokenId,1416		nesting_budget: &dyn Budget,1417	) -> DispatchResult;14181419	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));14201421	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));14221423	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1424	fn collection_tokens(&self) -> Vec<TokenId>;1425	fn token_exists(&self, token: TokenId) -> bool;1426	fn last_token_id(&self) -> TokenId;14271428	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1429	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1430	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1431	/// Amount of unique collection tokens1432	fn total_supply(&self) -> u32;1433	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1434	fn account_balance(&self, account: T::CrossAccountId) -> u32;1435	/// Amount of specific token account have (Applicable to fungible/refungible)1436	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1437	/// Amount of token pieces1438	fn total_pieces(&self, token: TokenId) -> Option<u128>;1439	fn allowance(1440		&self,1441		sender: T::CrossAccountId,1442		spender: T::CrossAccountId,1443		token: TokenId,1444	) -> u128;1445	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1446}14471448pub trait RefungibleExtensions<T>1449where1450	T: Config,1451{1452	fn repartition(1453		&self,1454		owner: &T::CrossAccountId,1455		token: TokenId,1456		amount: u128,1457	) -> DispatchResultWithPostInfo;1458}14591460// Flexible enough for implementing CommonCollectionOperations1461pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1462	let post_info = PostDispatchInfo {1463		actual_weight: Some(weight),1464		pays_fee: Pays::Yes,1465	};1466	match res {1467		Ok(()) => Ok(post_info),1468		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1469	}1470}14711472impl<T: Config> From<PropertiesError> for Error<T> {1473	fn from(error: PropertiesError) -> Self {1474		match error {1475			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1476			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1477			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1478			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1479			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1480		}1481	}1482}
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -92,7 +92,7 @@
 	#[pallet::storage]
 	pub type TokensMinted<T: Config> =
 		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
-	
+
 	/// Amount of tokens burnt in a collection.
 	#[pallet::storage]
 	pub type TokensBurnt<T: Config> =
@@ -258,7 +258,7 @@
 		// TODO: ERC721 transfer event
 		Ok(())
 	}
-	
+
 	pub fn burn(
 		collection: &RefungibleHandle<T>,
 		owner: &T::CrossAccountId,
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -163,9 +163,9 @@
 		CollectionLimitSet(CollectionId),
 
 		/// Collection permissions were set
-		/// 
+		///
 		/// # Arguments
-		/// 
+		///
 		/// * collection_id: Globally unique collection identifier.
 		CollectionPermissionSet(CollectionId),
 	}
@@ -219,7 +219,7 @@
 		/// Collection id (controlled?2), token id (controlled?2)
 		#[deprecated]
 		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
-		/// Last sponsoring of token property setting // todo:doc rephrase this and the following 
+		/// Last sponsoring of token property setting // todo:doc rephrase this and the following
 		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
 
 		/// Last sponsoring of NFT approval in a collection
@@ -294,7 +294,7 @@
 		/// * Anyone.
 		///
 		/// # Arguments
-		/// 
+		///
 		/// * data: explicit create-collection data.
 		#[weight = <SelfWeightOf<T>>::create_collection()]
 		#[transactional]
@@ -495,7 +495,7 @@
 		}
 
 		/// Set (invite) a new collection sponsor. If successful, confirmation from the sponsor-to-be will be pending.
-		/// 
+		///
 		/// # Permissions
 		///
 		/// * Collection Owner
@@ -526,7 +526,7 @@
 		}
 
 		/// Confirm own sponsorship of a collection.
-		/// 
+		///
 		/// # Permissions
 		///
 		/// * The sponsor to-be
@@ -699,7 +699,7 @@
 		/// # Arguments
 		///
 		/// * collection_id.
-		/// 
+		///
 		/// * token_id.
 		///
 		/// * properties: a vector of key-value pairs stored as the token's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.
@@ -778,7 +778,7 @@
 		}
 
 		/// Create multiple items inside a collection with explicitly specified initial parameters.
-		/// 
+		///
 		/// # Permissions
 		///
 		/// * Collection Owner
@@ -792,7 +792,7 @@
 		///
 		/// * collection_id: ID of the collection.
 		///
-		/// * data: explicit item creation data. 
+		/// * data: explicit item creation data.
 		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]
 		#[transactional]
 		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {
@@ -939,7 +939,7 @@
 		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.
 		///
 		/// # Permissions
-		/// 
+		///
 		/// * Collection Owner
 		/// * Collection Admin
 		/// * Current NFT owner
@@ -968,7 +968,7 @@
 		/// Set specific limits of a collection. Empty, or None fields mean chain default.
 		///.
 		/// # Permissions
-		/// 
+		///
 		/// * Collection Owner
 		/// * Collection Admin
 		///
@@ -1002,7 +1002,7 @@
 		/// Set specific permissions of a collection. Empty, or None fields mean chain default.
 		///
 		/// # Permissions
-		/// 
+		///
 		/// * Collection Owner
 		/// * Collection Admin
 		///
@@ -1036,15 +1036,15 @@
 		/// Re-partition a refungible token, while owning all of its parts.
 		///
 		/// # Permissions
-		/// 
+		///
 		/// * Token Owner (must own every part)
 		///
 		/// # Arguments
 		///
 		/// * collection_id.
-		/// 
-		/// * token: the ID of the RFT. 
 		///
+		/// * token: the ID of the RFT.
+		///
 		/// * amount: The new number of parts into which the token shall be partitioned.
 		#[weight = T::RefungibleExtensionsWeightInfo::repartition()]
 		#[transactional]