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

difftreelog

doc: adjust documentation style

Farhad Hakimov2022-07-11parent: #9b4da67.patch.diff
in: master

2 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 transferred.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		/// * mode - [CollectionMode] converted into u8.293		/// * account_id - Collection owner.294		CollectionCreated(CollectionId, u8, T::AccountId),295296		/// New collection was destroyed297		///298		/// # Arguments299		///300		/// * collection_id - Globally unique identifier of collection that has been destroyed.301		CollectionDestroyed(CollectionId),302303		/// New item was created.304		///305		/// # Arguments306		///307		/// * collection_id - ID of the collection where the item was created.308		/// * item_id - ID of the item. Unique within the collection.309		/// * recipient - Owner of the newly created item.310		/// * amount - The amount of tokens that were created (always 1 for NFT).311		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),312313		/// Collection item was burned.314		///315		/// # Arguments316		///317		/// * collection_id - Identifier of the collection to which the burned NFT belonged.318		/// * item_id - Identifier of burned NFT.319		/// * owner - Which user has destroyed their tokens.320		/// * amount - Amount of tokens that were destroyed (always 1 for NFT).321		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),322323		/// Item was transferred.324		///325		/// # Arguments326		///327		/// * collection_id - ID of the collection to which the item belongs.328		/// * item_id - ID of the item transferred.329		/// * sender - Original owner of the item.330		/// * recipient - New owner of the item.331		/// * amount - Amount of tokens that were transferred (always 1 for NFT).332		Transfer(333			CollectionId,334			TokenId,335			T::CrossAccountId,336			T::CrossAccountId,337			u128,338		),339340		/// Sponsoring allowance was approved.341		///342		/// # Arguments343		///344		/// * collection_id - todo:doc flesh out345		/// * item_id346		/// * sender347		/// * spender348		/// * amount349		Approved(350			CollectionId,351			TokenId,352			T::CrossAccountId,353			T::CrossAccountId,354			u128,355		),356357		/// Collection property was added or edited.358		///359		/// # Arguments360		///361		/// * collection_id - ID of the collection, whose property was just set.362		/// * property_key - Key of the property that was just set.363		CollectionPropertySet(CollectionId, PropertyKey),364365		/// Collection property was deleted.366		///367		/// # Arguments368		///369		/// * collection_id - ID of the collection, whose property was just deleted.370		/// * property_key - Key of the property that was just deleted.371		CollectionPropertyDeleted(CollectionId, PropertyKey),372373		/// Item property was added or edited.374		///375		/// # Arguments376		///377		/// * collection_id - ID of the collection, whose token's property was just set.378		/// * item_id - ID of the item, whose property was just set.379		/// * property_key - Key of the property that was just set.380		TokenPropertySet(CollectionId, TokenId, PropertyKey),381382		/// Item property was deleted.383		///384		/// # Arguments385		///386		/// * collection_id - ID of the collection, whose token's property was just deleted.387		/// * item_id - ID of the item, whose property was just deleted.388		/// * property_key - Key of the property that was just deleted.389		TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),390391		/// Token property permission was added or updated for a collection.392		///393		/// # Arguments394		///395		/// * collection_id - ID of the collection, whose permissions were just set/updated.396		/// * property_key - Key of the property of the set/updated permission.397		PropertyPermissionSet(CollectionId, PropertyKey),398	}399400	#[pallet::error]401	pub enum Error<T> {402		/// This collection does not exist.403		CollectionNotFound,404		/// Sender parameter and item owner must be equal.405		MustBeTokenOwner,406		/// No permission to perform action407		NoPermission,408		/// Destroying only empty collections is allowed409		CantDestroyNotEmptyCollection,410		/// Collection is not in mint mode.411		PublicMintingNotAllowed,412		/// Address is not in allow list.413		AddressNotInAllowlist,414415		/// Collection name can not be longer than 63 char.416		CollectionNameLimitExceeded,417		/// Collection description can not be longer than 255 char.418		CollectionDescriptionLimitExceeded,419		/// Token prefix can not be longer than 15 char.420		CollectionTokenPrefixLimitExceeded,421		/// Total collections bound exceeded.422		TotalCollectionsLimitExceeded,423		/// Exceeded max admin count424		CollectionAdminCountExceeded,425		/// Collection limit bounds per collection exceeded426		CollectionLimitBoundsExceeded,427		/// Tried to enable permissions which are only permitted to be disabled428		OwnerPermissionsCantBeReverted,429		/// Collection settings not allowing items transferring430		TransferNotAllowed,431		/// Account token limit exceeded per collection432		AccountTokenLimitExceeded,433		/// Collection token limit exceeded434		CollectionTokenLimitExceeded,435		/// Metadata flag frozen436		MetadataFlagFrozen,437438		/// Item does not exist439		TokenNotFound,440		/// Item is balance not enough441		TokenValueTooLow,442		/// Requested value is more than the approved443		ApprovedValueTooLow,444		/// Tried to approve more than owned445		CantApproveMoreThanOwned,446447		/// Can't transfer tokens to ethereum zero address448		AddressIsZero,449		/// Target collection doesn't support this operation450		UnsupportedOperation,451452		/// Insufficient funds to perform an action453		NotSufficientFounds,454455		/// User does not satisfy the nesting rule456		UserIsNotAllowedToNest,457		/// Only tokens from specific collections may nest tokens under this one458		SourceCollectionIsNotAllowedToNest,459460		/// Tried to store more data than allowed in collection field461		CollectionFieldSizeExceeded,462463		/// Tried to store more property data than allowed464		NoSpaceForProperty,465466		/// Tried to store more property keys than allowed467		PropertyLimitReached,468469		/// Property key is too long470		PropertyKeyIsTooLong,471472		/// Only ASCII letters, digits, and symbols '_', '-', and '.' are allowed473		InvalidCharacterInPropertyKey,474475		/// Empty property keys are forbidden476		EmptyPropertyKey,477478		/// Tried to access an external collection with an internal API479		CollectionIsExternal,480481		/// Tried to access an internal collection with an external API482		CollectionIsInternal,483	}484485	/// The number of created collections. Essentially contains the last collection ID.486	#[pallet::storage]487	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;488489	/// The number of destroyed collections490	#[pallet::storage]491	pub type DestroyedCollectionCount<T> =492		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;493494	/// Collection info495	#[pallet::storage]496	pub type CollectionById<T> = StorageMap<497		Hasher = Blake2_128Concat,498		Key = CollectionId,499		Value = Collection<<T as frame_system::Config>::AccountId>,500		QueryKind = OptionQuery,501	>;502503	/// Collection properties504	#[pallet::storage]505	#[pallet::getter(fn collection_properties)]506	pub type CollectionProperties<T> = StorageMap<507		Hasher = Blake2_128Concat,508		Key = CollectionId,509		Value = Properties,510		QueryKind = ValueQuery,511		OnEmpty = up_data_structs::CollectionProperties,512	>;513514	/// Token permissions of a collection515	#[pallet::storage]516	#[pallet::getter(fn property_permissions)]517	pub type CollectionPropertyPermissions<T> = StorageMap<518		Hasher = Blake2_128Concat,519		Key = CollectionId,520		Value = PropertiesPermissionMap,521		QueryKind = ValueQuery,522	>;523524	/// Amount of collection admins525	#[pallet::storage]526	pub type AdminAmount<T> = StorageMap<527		Hasher = Blake2_128Concat,528		Key = CollectionId,529		Value = u32,530		QueryKind = ValueQuery,531	>;532533	/// List of collection admins534	#[pallet::storage]535	pub type IsAdmin<T: Config> = StorageNMap<536		Key = (537			Key<Blake2_128Concat, CollectionId>,538			Key<Blake2_128Concat, T::CrossAccountId>,539		),540		Value = bool,541		QueryKind = ValueQuery,542	>;543544	/// Allowlisted collection users545	#[pallet::storage]546	pub type Allowlist<T: Config> = StorageNMap<547		Key = (548			Key<Blake2_128Concat, CollectionId>,549			Key<Blake2_128Concat, T::CrossAccountId>,550		),551		Value = bool,552		QueryKind = ValueQuery,553	>;554555	/// Not used by code, exists only to provide some types to metadata556	#[pallet::storage]557	pub type DummyStorageValue<T: Config> = StorageValue<558		Value = (559			CollectionStats,560			CollectionId,561			TokenId,562			TokenChild,563			PhantomType<(564				TokenData<T::CrossAccountId>,565				RpcCollection<T::AccountId>,566				// RMRK567				RmrkCollectionInfo<T::AccountId>,568				RmrkInstanceInfo<T::AccountId>,569				RmrkResourceInfo,570				RmrkPropertyInfo,571				RmrkBaseInfo<T::AccountId>,572				RmrkPartType,573				RmrkBoundedTheme,574				RmrkNftChild,575			)>,576		),577		QueryKind = OptionQuery,578	>;579580	#[pallet::hooks]581	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {582		fn on_runtime_upgrade() -> Weight {583			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {584				use up_data_structs::{CollectionVersion1, CollectionVersion2};585				<CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {586					let mut props = Vec::new();587					if !v.offchain_schema.is_empty() {588						props.push(Property {589							key: b"_old_offchainSchema".to_vec().try_into().unwrap(),590							value: v591								.offchain_schema592								.clone()593								.into_inner()594								.try_into()595								.expect("offchain schema too big"),596						});597					}598					if !v.variable_on_chain_schema.is_empty() {599						props.push(Property {600							key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),601							value: v602								.variable_on_chain_schema603								.clone()604								.into_inner()605								.try_into()606								.expect("offchain schema too big"),607						});608					}609					if !v.const_on_chain_schema.is_empty() {610						props.push(Property {611							key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),612							value: v613								.const_on_chain_schema614								.clone()615								.into_inner()616								.try_into()617								.expect("offchain schema too big"),618						});619					}620					props.push(Property {621						key: b"_old_schemaVersion".to_vec().try_into().unwrap(),622						value: match v.schema_version {623							SchemaVersion::ImageURL => b"ImageUrl".as_slice(),624							SchemaVersion::Unique => b"Unique".as_slice(),625						}626						.to_vec()627						.try_into()628						.unwrap(),629					});630					Self::set_scoped_collection_properties(631						id,632						PropertyScope::None,633						props.into_iter(),634					)635					.expect("existing data larger than properties");636					let mut new = CollectionVersion2::from(v.clone());637					new.permissions.access = Some(v.access);638					new.permissions.mint_mode = Some(v.mint_mode);639					Some(new)640				});641			}642643			0644		}645	}646}647648impl<T: Config> Pallet<T> {649	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens650	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {651		ensure!(652			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,653			<Error<T>>::AddressIsZero654		);655		Ok(())656	}657	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {658		<IsAdmin<T>>::iter_prefix((collection,))659			.map(|(a, _)| a)660			.collect()661	}662	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {663		<Allowlist<T>>::iter_prefix((collection,))664			.map(|(a, _)| a)665			.collect()666	}667	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {668		<Allowlist<T>>::get((collection, user))669	}670	pub fn collection_stats() -> CollectionStats {671		let created = <CreatedCollectionCount<T>>::get();672		let destroyed = <DestroyedCollectionCount<T>>::get();673		CollectionStats {674			created: created.0,675			destroyed: destroyed.0,676			alive: created.0 - destroyed.0,677		}678	}679680	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {681		let collection = <CollectionById<T>>::get(collection);682		if collection.is_none() {683			return None;684		}685686		let collection = collection.unwrap();687		let limits = collection.limits;688		let effective_limits = CollectionLimits {689			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),690			sponsored_data_size: Some(limits.sponsored_data_size()),691			sponsored_data_rate_limit: Some(692				limits693					.sponsored_data_rate_limit694					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),695			),696			token_limit: Some(limits.token_limit()),697			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(698				match collection.mode {699					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,700					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,701					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,702				},703			)),704			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),705			owner_can_transfer: Some(limits.owner_can_transfer()),706			owner_can_destroy: Some(limits.owner_can_destroy()),707			transfers_enabled: Some(limits.transfers_enabled()),708		};709710		Some(effective_limits)711	}712713	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {714		let Collection {715			name,716			description,717			owner,718			mode,719			token_prefix,720			sponsorship,721			limits,722			permissions,723			external_collection,724		} = <CollectionById<T>>::get(collection)?;725726		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)727			.into_iter()728			.map(|(key, permission)| PropertyKeyPermission { key, permission })729			.collect();730731		let properties = <CollectionProperties<T>>::get(collection)732			.into_iter()733			.map(|(key, value)| Property { key, value })734			.collect();735736		let permissions = CollectionPermissions {737			access: Some(permissions.access()),738			mint_mode: Some(permissions.mint_mode()),739			nesting: Some(permissions.nesting().clone()),740		};741742		Some(RpcCollection {743			name: name.into_inner(),744			description: description.into_inner(),745			owner,746			mode,747			token_prefix: token_prefix.into_inner(),748			sponsorship,749			limits,750			permissions,751			token_property_permissions,752			properties,753			read_only: external_collection,754		})755	}756}757758macro_rules! limit_default {759	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{760		$(761			if let Some($new) = $new.$field {762				let $old = $old.$field($($arg)?);763				let _ = $new;764				let _ = $old;765				$check766			} else {767				$new.$field = $old.$field768			}769		)*770	}};771}772macro_rules! limit_default_clone {773	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{774		$(775			if let Some($new) = $new.$field.clone() {776				let $old = $old.$field($($arg)?);777				let _ = $new;778				let _ = $old;779				$check780			} else {781				$new.$field = $old.$field.clone()782			}783		)*784	}};785}786787impl<T: Config> Pallet<T> {788	pub fn init_collection(789		owner: T::CrossAccountId,790		data: CreateCollectionData<T::AccountId>,791		is_external: bool,792	) -> Result<CollectionId, DispatchError> {793		{794			ensure!(795				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,796				Error::<T>::CollectionTokenPrefixLimitExceeded797			);798		}799800		let created_count = <CreatedCollectionCount<T>>::get()801			.0802			.checked_add(1)803			.ok_or(ArithmeticError::Overflow)?;804		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;805		let id = CollectionId(created_count);806807		// bound Total number of collections808		ensure!(809			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,810			<Error<T>>::TotalCollectionsLimitExceeded811		);812813		// =========814815		let collection = Collection {816			owner: owner.as_sub().clone(),817			name: data.name,818			mode: data.mode.clone(),819			description: data.description,820			token_prefix: data.token_prefix,821			sponsorship: data822				.pending_sponsor823				.map(SponsorshipState::Unconfirmed)824				.unwrap_or_default(),825			limits: data826				.limits827				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))828				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,829			permissions: data830				.permissions831				.map(|permissions| {832					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)833				})834				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,835			external_collection: is_external,836		};837838		let mut collection_properties = up_data_structs::CollectionProperties::get();839		collection_properties840			.try_set_from_iter(data.properties.into_iter())841			.map_err(<Error<T>>::from)?;842843		CollectionProperties::<T>::insert(id, collection_properties);844845		let mut token_props_permissions = PropertiesPermissionMap::new();846		token_props_permissions847			.try_set_from_iter(data.token_property_permissions.into_iter())848			.map_err(<Error<T>>::from)?;849850		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);851852		// Take a (non-refundable) deposit of collection creation853		{854			let mut imbalance =855				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();856			imbalance.subsume(857				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(858					&T::TreasuryAccountId::get(),859					T::CollectionCreationPrice::get(),860				),861			);862			<T as Config>::Currency::settle(863				&owner.as_sub(),864				imbalance,865				WithdrawReasons::TRANSFER,866				ExistenceRequirement::KeepAlive,867			)868			.map_err(|_| Error::<T>::NotSufficientFounds)?;869		}870871		<CreatedCollectionCount<T>>::put(created_count);872		<Pallet<T>>::deposit_event(Event::CollectionCreated(873			id,874			data.mode.id(),875			owner.as_sub().clone(),876		));877		<PalletEvm<T>>::deposit_log(878			erc::CollectionHelpersEvents::CollectionCreated {879				owner: *owner.as_eth(),880				collection_id: eth::collection_id_to_address(id),881			}882			.to_log(T::ContractAddress::get()),883		);884		<CollectionById<T>>::insert(id, collection);885		Ok(id)886	}887888	pub fn destroy_collection(889		collection: CollectionHandle<T>,890		sender: &T::CrossAccountId,891	) -> DispatchResult {892		ensure!(893			collection.limits.owner_can_destroy(),894			<Error<T>>::NoPermission,895		);896		collection.check_is_owner(sender)?;897898		let destroyed_collections = <DestroyedCollectionCount<T>>::get()899			.0900			.checked_add(1)901			.ok_or(ArithmeticError::Overflow)?;902903		// =========904905		<DestroyedCollectionCount<T>>::put(destroyed_collections);906		<CollectionById<T>>::remove(collection.id);907		<AdminAmount<T>>::remove(collection.id);908		<IsAdmin<T>>::remove_prefix((collection.id,), None);909		<Allowlist<T>>::remove_prefix((collection.id,), None);910		<CollectionProperties<T>>::remove(collection.id);911912		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));913		Ok(())914	}915916	pub fn set_collection_property(917		collection: &CollectionHandle<T>,918		sender: &T::CrossAccountId,919		property: Property,920	) -> DispatchResult {921		collection.check_is_owner_or_admin(sender)?;922923		CollectionProperties::<T>::try_mutate(collection.id, |properties| {924			let property = property.clone();925			properties.try_set(property.key, property.value)926		})927		.map_err(<Error<T>>::from)?;928929		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));930931		Ok(())932	}933934	pub fn set_scoped_collection_property(935		collection_id: CollectionId,936		scope: PropertyScope,937		property: Property,938	) -> DispatchResult {939		CollectionProperties::<T>::try_mutate(collection_id, |properties| {940			properties.try_scoped_set(scope, property.key, property.value)941		})942		.map_err(<Error<T>>::from)?;943944		Ok(())945	}946947	pub fn set_scoped_collection_properties(948		collection_id: CollectionId,949		scope: PropertyScope,950		properties: impl Iterator<Item = Property>,951	) -> DispatchResult {952		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {953			stored_properties.try_scoped_set_from_iter(scope, properties)954		})955		.map_err(<Error<T>>::from)?;956957		Ok(())958	}959960	#[transactional]961	pub fn set_collection_properties(962		collection: &CollectionHandle<T>,963		sender: &T::CrossAccountId,964		properties: Vec<Property>,965	) -> DispatchResult {966		for property in properties {967			Self::set_collection_property(collection, sender, property)?;968		}969970		Ok(())971	}972973	pub fn delete_collection_property(974		collection: &CollectionHandle<T>,975		sender: &T::CrossAccountId,976		property_key: PropertyKey,977	) -> DispatchResult {978		collection.check_is_owner_or_admin(sender)?;979980		CollectionProperties::<T>::try_mutate(collection.id, |properties| {981			properties.remove(&property_key)982		})983		.map_err(<Error<T>>::from)?;984985		Self::deposit_event(Event::CollectionPropertyDeleted(986			collection.id,987			property_key,988		));989990		Ok(())991	}992993	#[transactional]994	pub fn delete_collection_properties(995		collection: &CollectionHandle<T>,996		sender: &T::CrossAccountId,997		property_keys: Vec<PropertyKey>,998	) -> DispatchResult {999		for key in property_keys {1000			Self::delete_collection_property(collection, sender, key)?;1001		}10021003		Ok(())1004	}10051006	// For migrations1007	pub fn set_property_permission_unchecked(1008		collection: CollectionId,1009		property_permission: PropertyKeyPermission,1010	) -> DispatchResult {1011		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1012			permissions.try_set(property_permission.key, property_permission.permission)1013		})1014		.map_err(<Error<T>>::from)?;1015		Ok(())1016	}10171018	pub fn set_property_permission(1019		collection: &CollectionHandle<T>,1020		sender: &T::CrossAccountId,1021		property_permission: PropertyKeyPermission,1022	) -> DispatchResult {1023		collection.check_is_owner_or_admin(sender)?;10241025		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1026		let current_permission = all_permissions.get(&property_permission.key);1027		if matches![1028			current_permission,1029			Some(PropertyPermission { mutable: false, .. })1030		] {1031			return Err(<Error<T>>::NoPermission.into());1032		}10331034		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1035			let property_permission = property_permission.clone();1036			permissions.try_set(property_permission.key, property_permission.permission)1037		})1038		.map_err(<Error<T>>::from)?;10391040		Self::deposit_event(Event::PropertyPermissionSet(1041			collection.id,1042			property_permission.key,1043		));10441045		Ok(())1046	}10471048	#[transactional]1049	pub fn set_token_property_permissions(1050		collection: &CollectionHandle<T>,1051		sender: &T::CrossAccountId,1052		property_permissions: Vec<PropertyKeyPermission>,1053	) -> DispatchResult {1054		for prop_pemission in property_permissions {1055			Self::set_property_permission(collection, sender, prop_pemission)?;1056		}10571058		Ok(())1059	}10601061	pub fn get_collection_property(1062		collection_id: CollectionId,1063		key: &PropertyKey,1064	) -> Option<PropertyValue> {1065		Self::collection_properties(collection_id).get(key).cloned()1066	}10671068	pub fn bytes_keys_to_property_keys(1069		keys: Vec<Vec<u8>>,1070	) -> Result<Vec<PropertyKey>, DispatchError> {1071		keys.into_iter()1072			.map(|key| -> Result<PropertyKey, DispatchError> {1073				key.try_into()1074					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1075			})1076			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1077	}10781079	pub fn filter_collection_properties(1080		collection_id: CollectionId,1081		keys: Option<Vec<PropertyKey>>,1082	) -> Result<Vec<Property>, DispatchError> {1083		let properties = Self::collection_properties(collection_id);10841085		let properties = keys1086			.map(|keys| {1087				keys.into_iter()1088					.filter_map(|key| {1089						properties.get(&key).map(|value| Property {1090							key,1091							value: value.clone(),1092						})1093					})1094					.collect()1095			})1096			.unwrap_or_else(|| {1097				properties1098					.into_iter()1099					.map(|(key, value)| Property { key, value })1100					.collect()1101			});11021103		Ok(properties)1104	}11051106	pub fn filter_property_permissions(1107		collection_id: CollectionId,1108		keys: Option<Vec<PropertyKey>>,1109	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1110		let permissions = Self::property_permissions(collection_id);11111112		let key_permissions = keys1113			.map(|keys| {1114				keys.into_iter()1115					.filter_map(|key| {1116						permissions1117							.get(&key)1118							.map(|permission| PropertyKeyPermission {1119								key,1120								permission: permission.clone(),1121							})1122					})1123					.collect()1124			})1125			.unwrap_or_else(|| {1126				permissions1127					.into_iter()1128					.map(|(key, permission)| PropertyKeyPermission { key, permission })1129					.collect()1130			});11311132		Ok(key_permissions)1133	}11341135	pub fn toggle_allowlist(1136		collection: &CollectionHandle<T>,1137		sender: &T::CrossAccountId,1138		user: &T::CrossAccountId,1139		allowed: bool,1140	) -> DispatchResult {1141		collection.check_is_owner_or_admin(sender)?;11421143		// =========11441145		if allowed {1146			<Allowlist<T>>::insert((collection.id, user), true);1147		} else {1148			<Allowlist<T>>::remove((collection.id, user));1149		}11501151		Ok(())1152	}11531154	pub fn toggle_admin(1155		collection: &CollectionHandle<T>,1156		sender: &T::CrossAccountId,1157		user: &T::CrossAccountId,1158		admin: bool,1159	) -> DispatchResult {1160		collection.check_is_owner(sender)?;11611162		let was_admin = <IsAdmin<T>>::get((collection.id, user));1163		if was_admin == admin {1164			return Ok(());1165		}1166		let amount = <AdminAmount<T>>::get(collection.id);11671168		if admin {1169			let amount = amount1170				.checked_add(1)1171				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1172			ensure!(1173				amount <= Self::collection_admins_limit(),1174				<Error<T>>::CollectionAdminCountExceeded,1175			);11761177			// =========11781179			<AdminAmount<T>>::insert(collection.id, amount);1180			<IsAdmin<T>>::insert((collection.id, user), true);1181		} else {1182			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1183			<IsAdmin<T>>::remove((collection.id, user));1184		}11851186		Ok(())1187	}11881189	pub fn clamp_limits(1190		mode: CollectionMode,1191		old_limit: &CollectionLimits,1192		mut new_limit: CollectionLimits,1193	) -> Result<CollectionLimits, DispatchError> {1194		let limits = old_limit;1195		limit_default!(old_limit, new_limit,1196			account_token_ownership_limit => ensure!(1197				new_limit <= MAX_TOKEN_OWNERSHIP,1198				<Error<T>>::CollectionLimitBoundsExceeded,1199			),1200			sponsored_data_size => ensure!(1201				new_limit <= CUSTOM_DATA_LIMIT,1202				<Error<T>>::CollectionLimitBoundsExceeded,1203			),12041205			sponsored_data_rate_limit => {},1206			token_limit => ensure!(1207				old_limit >= new_limit && new_limit > 0,1208				<Error<T>>::CollectionTokenLimitExceeded1209			),12101211			sponsor_transfer_timeout(match mode {1212				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1213				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1214				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1215			}) => ensure!(1216				new_limit <= MAX_SPONSOR_TIMEOUT,1217				<Error<T>>::CollectionLimitBoundsExceeded,1218			),1219			sponsor_approve_timeout => {},1220			owner_can_transfer => ensure!(1221				!limits.owner_can_transfer_instaled() ||1222				old_limit || !new_limit,1223				<Error<T>>::OwnerPermissionsCantBeReverted,1224			),1225			owner_can_destroy => ensure!(1226				old_limit || !new_limit,1227				<Error<T>>::OwnerPermissionsCantBeReverted,1228			),1229			transfers_enabled => {},1230		);1231		Ok(new_limit)1232	}12331234	pub fn clamp_permissions(1235		_mode: CollectionMode,1236		old_limit: &CollectionPermissions,1237		mut new_limit: CollectionPermissions,1238	) -> Result<CollectionPermissions, DispatchError> {1239		limit_default_clone!(old_limit, new_limit,1240			access => {},1241			mint_mode => {},1242			nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1243		);1244		Ok(new_limit)1245	}1246}12471248#[macro_export]1249macro_rules! unsupported {1250	() => {1251		Err(<Error<T>>::UnsupportedOperation.into())1252	};1253}12541255/// Worst cases1256pub trait CommonWeightInfo<CrossAccountId> {1257	fn create_item() -> Weight;1258	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1259	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1260	fn burn_item() -> Weight;1261	fn set_collection_properties(amount: u32) -> Weight;1262	fn delete_collection_properties(amount: u32) -> Weight;1263	fn set_token_properties(amount: u32) -> Weight;1264	fn delete_token_properties(amount: u32) -> Weight;1265	fn set_token_property_permissions(amount: u32) -> Weight;1266	fn transfer() -> Weight;1267	fn approve() -> Weight;1268	fn transfer_from() -> Weight;1269	fn burn_from() -> Weight;12701271	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1272	/// whole users's balance1273	///1274	/// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead1275	fn burn_recursively_self_raw() -> Weight;1276	/// Cost of iterating over `amount` children while burning, without counting child burning itself1277	///1278	/// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead1279	fn burn_recursively_breadth_raw(amount: u32) -> Weight;12801281	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1282		Self::burn_recursively_self_raw()1283			.saturating_mul(max_selfs.max(1) as u64)1284			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1285	}1286}12871288pub trait RefungibleExtensionsWeightInfo {1289	fn repartition() -> Weight;1290}12911292pub trait CommonCollectionOperations<T: Config> {1293	fn create_item(1294		&self,1295		sender: T::CrossAccountId,1296		to: T::CrossAccountId,1297		data: CreateItemData,1298		nesting_budget: &dyn Budget,1299	) -> DispatchResultWithPostInfo;1300	fn create_multiple_items(1301		&self,1302		sender: T::CrossAccountId,1303		to: T::CrossAccountId,1304		data: Vec<CreateItemData>,1305		nesting_budget: &dyn Budget,1306	) -> DispatchResultWithPostInfo;1307	fn create_multiple_items_ex(1308		&self,1309		sender: T::CrossAccountId,1310		data: CreateItemExData<T::CrossAccountId>,1311		nesting_budget: &dyn Budget,1312	) -> DispatchResultWithPostInfo;1313	fn burn_item(1314		&self,1315		sender: T::CrossAccountId,1316		token: TokenId,1317		amount: u128,1318	) -> DispatchResultWithPostInfo;1319	fn burn_item_recursively(1320		&self,1321		sender: T::CrossAccountId,1322		token: TokenId,1323		self_budget: &dyn Budget,1324		breadth_budget: &dyn Budget,1325	) -> DispatchResultWithPostInfo;1326	fn set_collection_properties(1327		&self,1328		sender: T::CrossAccountId,1329		properties: Vec<Property>,1330	) -> DispatchResultWithPostInfo;1331	fn delete_collection_properties(1332		&self,1333		sender: &T::CrossAccountId,1334		property_keys: Vec<PropertyKey>,1335	) -> DispatchResultWithPostInfo;1336	fn set_token_properties(1337		&self,1338		sender: T::CrossAccountId,1339		token_id: TokenId,1340		property: Vec<Property>,1341		nesting_budget: &dyn Budget,1342	) -> DispatchResultWithPostInfo;1343	fn delete_token_properties(1344		&self,1345		sender: T::CrossAccountId,1346		token_id: TokenId,1347		property_keys: Vec<PropertyKey>,1348		nesting_budget: &dyn Budget,1349	) -> DispatchResultWithPostInfo;1350	fn set_token_property_permissions(1351		&self,1352		sender: &T::CrossAccountId,1353		property_permissions: Vec<PropertyKeyPermission>,1354	) -> DispatchResultWithPostInfo;1355	fn transfer(1356		&self,1357		sender: T::CrossAccountId,1358		to: T::CrossAccountId,1359		token: TokenId,1360		amount: u128,1361		nesting_budget: &dyn Budget,1362	) -> DispatchResultWithPostInfo;1363	fn approve(1364		&self,1365		sender: T::CrossAccountId,1366		spender: T::CrossAccountId,1367		token: TokenId,1368		amount: u128,1369	) -> DispatchResultWithPostInfo;1370	fn transfer_from(1371		&self,1372		sender: T::CrossAccountId,1373		from: T::CrossAccountId,1374		to: T::CrossAccountId,1375		token: TokenId,1376		amount: u128,1377		nesting_budget: &dyn Budget,1378	) -> DispatchResultWithPostInfo;1379	fn burn_from(1380		&self,1381		sender: T::CrossAccountId,1382		from: T::CrossAccountId,1383		token: TokenId,1384		amount: u128,1385		nesting_budget: &dyn Budget,1386	) -> DispatchResultWithPostInfo;13871388	fn check_nesting(1389		&self,1390		sender: T::CrossAccountId,1391		from: (CollectionId, TokenId),1392		under: TokenId,1393		nesting_budget: &dyn Budget,1394	) -> DispatchResult;13951396	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13971398	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13991400	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1401	fn collection_tokens(&self) -> Vec<TokenId>;1402	fn token_exists(&self, token: TokenId) -> bool;1403	fn last_token_id(&self) -> TokenId;14041405	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1406	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1407	fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1408	/// Amount of unique collection tokens1409	fn total_supply(&self) -> u32;1410	/// Amount of different tokens account has (Applicable to nonfungible/refungible)1411	fn account_balance(&self, account: T::CrossAccountId) -> u32;1412	/// Amount of specific token account have (Applicable to fungible/refungible)1413	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1414	/// Amount of token pieces1415	fn total_pieces(&self, token: TokenId) -> Option<u128>;1416	fn allowance(1417		&self,1418		sender: T::CrossAccountId,1419		spender: T::CrossAccountId,1420		token: TokenId,1421	) -> u128;1422	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1423}14241425pub trait RefungibleExtensions<T>1426where1427	T: Config,1428{1429	fn repartition(1430		&self,1431		owner: &T::CrossAccountId,1432		token: TokenId,1433		amount: u128,1434	) -> DispatchResultWithPostInfo;1435}14361437// Flexible enough for implementing CommonCollectionOperations1438pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1439	let post_info = PostDispatchInfo {1440		actual_weight: Some(weight),1441		pays_fee: Pays::Yes,1442	};1443	match res {1444		Ok(()) => Ok(post_info),1445		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1446	}1447}14481449impl<T: Config> From<PropertiesError> for Error<T> {1450	fn from(error: PropertiesError) -> Self {1451		match error {1452			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1453			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1454			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1455			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1456			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1457		}1458	}1459}
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -261,12 +261,9 @@
 		/// # Arguments
 		///
 		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.
-		///
-		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.
-		///
-		/// * token_prefix: UTF-8 string with token prefix.
-		///
-		/// * mode: [CollectionMode] collection type and type dependent data.
+		/// * collection_description - UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.
+		/// * token_prefix - UTF-8 string with token prefix.
+		/// * mode - [CollectionMode] collection type and type dependent data.
 		// returns collection ID
 		#[weight = <SelfWeightOf<T>>::create_collection()]
 		#[transactional]
@@ -316,7 +313,7 @@
 		///
 		/// # Arguments
 		///
-		/// * collection_id: collection to destroy.
+		/// * collection_id - collection to destroy.
 		#[weight = <SelfWeightOf<T>>::destroy_collection()]
 		#[transactional]
 		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {
@@ -349,7 +346,6 @@
 		/// # Arguments
 		///
 		/// * collection_id.
-		///
 		/// * address.
 		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]
 		#[transactional]
@@ -384,7 +380,6 @@
 		/// # Arguments
 		///
 		/// * collection_id.
-		///
 		/// * address.
 		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]
 		#[transactional]
@@ -418,7 +413,6 @@
 		/// # Arguments
 		///
 		/// * collection_id.
-		///
 		/// * new_owner.
 		#[weight = <SelfWeightOf<T>>::change_collection_owner()]
 		#[transactional]
@@ -448,10 +442,9 @@
 		/// * Collection Admin
 		///
 		/// # Arguments
-		///
-		/// * collection_id: ID of the Collection to add admin for.
 		///
-		/// * new_admin: Address of new admin to add.
+		/// * collection_id - ID of the Collection to add admin for.
+		/// * new_admin - Address of new admin to add.
 		#[weight = <SelfWeightOf<T>>::add_collection_admin()]
 		#[transactional]
 		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin: T::CrossAccountId) -> DispatchResult {
@@ -476,9 +469,8 @@
 		///
 		/// # Arguments
 		///
-		/// * collection_id: ID of the Collection to remove admin for.
-		///
-		/// * account_id: Address of admin to remove.
+		/// * collection_id - ID of the Collection to remove admin for.
+		/// * account_id - Address of admin to remove.
 		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]
 		#[transactional]
 		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {
@@ -504,7 +496,6 @@
 		/// # Arguments
 		///
 		/// * collection_id.
-		///
 		/// * new_sponsor.
 		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]
 		#[transactional]
@@ -529,7 +520,7 @@
 		///
 		/// # Permissions
 		///
-		/// * The sponsor to-be
+		/// * Sponsor-to-be
 		///
 		/// # Arguments
 		///
@@ -593,11 +584,9 @@
 		///
 		/// # Arguments
 		///
-		/// * collection_id: ID of the collection.
-		///
-		/// * owner: Address, initial owner of the NFT.
-		///
-		/// * data: Token data to store on chain.
+		/// * collection_id - ID of the collection.
+		/// * owner - Address, initial owner of the NFT.
+		/// * data - Token data to store on chain.
 		#[weight = T::CommonWeightInfo::create_item()]
 		#[transactional]
 		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {
@@ -620,11 +609,9 @@
 		///
 		/// # Arguments
 		///
-		/// * collection_id: ID of the collection.
-		///
-		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].
-		///
-		/// * owner: Address, initial owner of the NFT.
+		/// * collection_id - ID of the collection.
+		/// * owner - Address, initial owner of the NFT.
+		/// * items_data - Array items properties. Each property is an array of bytes itself, see [`create_item`].
 		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]
 		#[transactional]
 		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {
@@ -645,8 +632,7 @@
 		/// # Arguments
 		///
 		/// * collection_id.
-		///
-		/// * properties: a vector of key-value pairs stored as the collection's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.
+		/// * properties - Vector of key-value pairs stored as the collection's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.
 		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]
 		#[transactional]
 		pub fn set_collection_properties(
@@ -671,8 +657,7 @@
 		/// # Arguments
 		///
 		/// * collection_id.
-		///
-		/// * property_keys: a vector of keys of the properties to be deleted.
+		/// * property_keys - Vector of keys of the properties to be deleted.
 		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]
 		#[transactional]
 		pub fn delete_collection_properties(
@@ -699,10 +684,8 @@
 		/// # 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.
+		/// * properties - Vector of key-value pairs stored as the token's metadata. Keys support Latin letters, `-`, `_`, and `.` as symbols.
 		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]
 		#[transactional]
 		pub fn set_token_properties(
@@ -731,10 +714,8 @@
 		/// # Arguments
 		///
 		/// * collection_id.
-		///
 		/// * token_id.
-		///
-		/// * property_keys: a vector of keys of the properties to be deleted.
+		/// * property_keys - Vector of keys of the properties to be deleted.
 		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]
 		#[transactional]
 		pub fn delete_token_properties(
@@ -761,8 +742,7 @@
 		/// # Arguments
 		///
 		/// * collection_id.
-		///
-		/// * property_permissions: a vector of permissions for property keys. Keys support Latin letters, '-', '_', and '.' as symbols.
+		/// * property_permissions - Vector of permissions for property keys. Keys support Latin letters, `-`, `_`, and `.` as symbols.
 		#[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]
 		#[transactional]
 		pub fn set_token_property_permissions(
@@ -789,10 +769,9 @@
 		///     * MintPermission is enabled (see SetMintPermission method)
 		///
 		/// # Arguments
-		///
-		/// * collection_id: ID of the collection.
 		///
-		/// * data: explicit item creation data.
+		/// * collection_id - ID of the collection.
+		/// * 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 {
@@ -810,9 +789,8 @@
 		///
 		/// # Arguments
 		///
-		/// * collection_id: ID of the collection.
-		///
-		/// * value: New flag value.
+		/// * collection_id - ID of the collection.
+		/// * value - New flag value.
 		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]
 		#[transactional]
 		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {
@@ -837,9 +815,8 @@
 		///
 		/// # Arguments
 		///
-		/// * collection_id: ID of the collection.
-		///
-		/// * item_id: ID of NFT to burn.
+		/// * collection_id - ID of the collection.
+		/// * item_id - ID of NFT to burn.
 		#[weight = T::CommonWeightInfo::burn_item()]
 		#[transactional]
 		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {
@@ -866,12 +843,10 @@
 		/// * Current NFT Owner.
 		///
 		/// # Arguments
-		///
-		/// * collection_id: ID of the collection.
 		///
-		/// * item_id: ID of NFT to burn.
-		///
-		/// * from: owner of item
+		/// * collection_id - ID of the collection.
+		/// * item_id - ID of NFT to burn.
+		/// * from - The owner of the item from whom it is taken away.
 		#[weight = T::CommonWeightInfo::burn_from()]
 		#[transactional]
 		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {
@@ -891,16 +866,16 @@
 		///
 		/// # Arguments
 		///
-		/// * recipient: Address of token recipient.
+		/// * recipient - Address of token recipient.
 		///
 		/// * collection_id.
 		///
-		/// * item_id: ID of the item
+		/// * item_id - ID of the item
 		///     * Non-Fungible Mode: Required.
 		///     * Fungible Mode: Ignored.
 		///     * Re-Fungible Mode: Required.
 		///
-		/// * value: Amount to transfer.
+		/// * value - Amount to transfer.
 		///     * Non-Fungible Mode: Ignored
 		///     * Fungible Mode: Must specify transferred amount
 		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)
@@ -923,11 +898,9 @@
 		///
 		/// # Arguments
 		///
-		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).
-		///
+		/// * approved - Address that is approved to transfer this NFT or zero (if needed to remove approval).
 		/// * collection_id.
-		///
-		/// * item_id: ID of the item.
+		/// * item_id - ID of the item.
 		#[weight = T::CommonWeightInfo::approve()]
 		#[transactional]
 		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {
@@ -947,15 +920,11 @@
 		///
 		/// # Arguments
 		///
-		/// * from: Address that owns token.
-		///
-		/// * recipient: Address of token recipient.
-		///
+		/// * from - Address that currently owns the token.
+		/// * recipient - Address of the new token-owner-to-be.
 		/// * collection_id.
-		///
-		/// * item_id: ID of the item.
-		///
-		/// * value: Amount to transfer.
+		/// * item_id - ID of the item to be transferred.
+		/// * value - Amount to transfer.
 		#[weight = T::CommonWeightInfo::transfer_from()]
 		#[transactional]
 		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {
@@ -966,7 +935,7 @@
 		}
 
 		/// Set specific limits of a collection. Empty, or None fields mean chain default.
-		///.
+		///
 		/// # Permissions
 		///
 		/// * Collection Owner
@@ -975,8 +944,7 @@
 		/// # Arguments
 		///
 		/// * collection_id.
-		///
-		/// * new_limit: The new limits of the collection. They will overwrite the current ones.
+		/// * new_limit - New limits of the collection. They will overwrite the current ones.
 		#[weight = <SelfWeightOf<T>>::set_collection_limits()]
 		#[transactional]
 		pub fn set_collection_limits(
@@ -1009,8 +977,7 @@
 		/// # Arguments
 		///
 		/// * collection_id.
-		///
-		/// * new_permission: The new permissions of the collection. They will overwrite the current ones.
+		/// * new_permission - New permissions of the collection. They will overwrite the current ones.
 		#[weight = <SelfWeightOf<T>>::set_collection_limits()]
 		#[transactional]
 		pub fn set_collection_permissions(
@@ -1042,16 +1009,14 @@
 		/// # Arguments
 		///
 		/// * collection_id.
-		///
-		/// * token: the ID of the RFT.
-		///
-		/// * amount: The new number of parts into which the token shall be partitioned.
+		/// * token_id - ID of the RFT.
+		/// * amount - New number of parts into which the token shall be partitioned.
 		#[weight = T::RefungibleExtensionsWeightInfo::repartition()]
 		#[transactional]
 		pub fn repartition(
 			origin,
 			collection_id: CollectionId,
-			token: TokenId,
+			token_id: TokenId,
 			amount: u128,
 		) -> DispatchResultWithPostInfo {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);