git.delta.rocks / unique-network / refs/commits / 05c81ca4ae1f

difftreelog

source

pallets/nonfungible/src/lib.rs39.6 KiBsourcehistory
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//! # Nonfungible Pallet18//!19//! The Nonfungible pallet provides functionality for handling nonfungible collections and tokens.20//!21//! - [`Config`]22//! - [`NonfungibleHandle`]23//! - [`Pallet`]24//! - [`CommonWeights`](common::CommonWeights)25//!26//! ## Overview27//!28//! The Nonfungible pallet provides functions for:29//!30//! - NFT collection creation and removal31//! - Minting and burning of NFT tokens32//! - Retrieving account balances33//! - Transfering NFT tokens34//! - Setting and checking allowance for NFT tokens35//! - Setting properties and permissions for NFT collections and tokens36//! - Nesting and unnesting tokens37//!38//! ### Terminology39//!40//! - **NFT token:** Non fungible token.41//!42//! - **NFT Collection:** A collection of NFT tokens. All NFT tokens are part of a collection.43//!   Each collection can define it's own properties, properties for it's tokens and set of permissions.44//!45//! - **Balance:** Number of NFT tokens owned by an account46//!47//! - **Allowance:** NFT tokens owned by one account that another account is allowed to make operations on48//!49//! - **Burning:** The process of “deleting” a token from a collection and from50//!   an account balance of the owner.51//!52//! - **Nesting:** Setting up parent-child relationship between tokens. Nested tokens are inhereting53//!   owner from their parent. There could be multiple levels of nesting. Token couldn't be nested in54//!   it's child token i.e. parent-child relationship graph shouldn't have cycles.55//!56//! - **Properties:** Key-Values pairs. Token properties are attached to a token. Collection properties are57//!   attached to a collection. Set of permissions could be defined for each property.58//!59//! ### Implementations60//!61//! The Nonfungible pallet provides implementations for the following traits. If these traits provide62//! the functionality that you need, then you can avoid coupling with the Nonfungible pallet.63//!64//! - [`CommonWeightInfo`](pallet_common::CommonWeightInfo): Functions for retrieval of transaction weight65//! - [`CommonCollectionOperations`](pallet_common::CommonCollectionOperations): Functions for dealing66//!   with collections67//!68//! ## Interface69//!70//! ### Dispatchable Functions71//!72//! - `init_collection` - Create NFT collection. NFT collection can be configured to allow or deny access for73//!   some accounts.74//! - `destroy_collection` - Destroy exising NFT collection. There should be no tokens in the collection.75//! - `burn` - Burn NFT token owned by account.76//! - `transfer` - Transfer NFT token. Transfers should be enabled for NFT collection.77//!   Nests the NFT token if it is sent to another token.78//! - `create_item` - Mint NFT token in collection. Sender should have permission to mint tokens.79//! - `set_allowance` - Set allowance for another account.80//! - `set_token_property` - Set token property value.81//! - `delete_token_property` - Remove property from the token.82//! - `set_collection_properties` - Set collection properties.83//! - `delete_collection_properties` - Remove properties from the collection.84//! - `set_property_permission` - Set collection property permission.85//! - `set_token_property_permissions` - Set token property permissions.86//!87//! ## Assumptions88//!89//! * To perform operations on tokens sender should be in collection's allow list if collection access mode is `AllowList`.9091#![cfg_attr(not(feature = "std"), no_std)]9293use core::ops::Deref;9495use erc::ERC721Events;96use evm_coder::ToLog;97use frame_support::{98	dispatch::{Pays, PostDispatchInfo},99	ensure, fail,100	pallet_prelude::*,101	storage::with_transaction,102	transactional, BoundedVec,103};104pub use pallet::*;105use pallet_common::{106	eth::collection_id_to_address, helpers::add_weight_to_post_info,107	weights::WeightInfo as CommonWeightInfo, CollectionHandle, Error as CommonError,108	Event as CommonEvent, Pallet as PalletCommon, SelfWeightOf as PalletCommonWeightOf,109};110use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};111use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};112use pallet_structure::Pallet as PalletStructure;113use parity_scale_codec::{Decode, Encode, MaxEncodedLen};114use scale_info::TypeInfo;115use sp_core::{Get, H160};116use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};117use sp_std::{collections::btree_map::BTreeMap, vec, vec::Vec};118use up_data_structs::{119	budget::Budget, mapping::TokenAddressMapping, AccessMode, AuxPropertyValue, CollectionId,120	CreateNftExData, CustomDataLimit, PropertiesPermissionMap, Property, PropertyKey,121	PropertyKeyPermission, PropertyScope, PropertyValue, TokenChild, TokenId,122	TokenProperties as TokenPropertiesT,123};124use weights::WeightInfo;125#[cfg(feature = "runtime-benchmarks")]126pub mod benchmarking;127pub mod common;128pub mod erc;129pub mod weights;130131pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::Config>::CrossAccountId>;132pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;133134/// Token data, stored independently from other data used to describe it135/// for the convenience of database access. Notably contains the owner account address.136#[struct_versioning::versioned(version = 2, upper)]137#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]138pub struct ItemData<CrossAccountId> {139	#[version(..2)]140	pub const_data: BoundedVec<u8, CustomDataLimit>,141142	#[version(..2)]143	pub variable_data: BoundedVec<u8, CustomDataLimit>,144145	pub owner: CrossAccountId,146}147148#[frame_support::pallet]149pub mod pallet {150	use frame_support::{151		pallet_prelude::*, storage::Key, traits::StorageVersion, Blake2_128Concat, Twox64Concat,152	};153	use up_data_structs::{CollectionId, TokenId};154155	use super::{weights::WeightInfo, *};156157	#[pallet::error]158	pub enum Error<T> {159		/// Not Nonfungible item data used to mint in Nonfungible collection.160		NotNonfungibleDataUsedToMintFungibleCollectionToken,161		/// Used amount > 1 with NFT162		NonfungibleItemsHaveNoAmount,163		/// Unable to burn NFT with children164		CantBurnNftWithChildren,165	}166167	#[pallet::config]168	pub trait Config:169		frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config170	{171		type WeightInfo: WeightInfo;172	}173174	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);175176	#[pallet::pallet]177	#[pallet::storage_version(STORAGE_VERSION)]178	pub struct Pallet<T>(_);179180	/// Total amount of minted tokens in a collection.181	#[pallet::storage]182	pub type TokensMinted<T: Config> =183		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;184185	/// Amount of burnt tokens in a collection.186	#[pallet::storage]187	pub type TokensBurnt<T: Config> =188		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;189190	/// Token data, used to partially describe a token.191	#[pallet::storage]192	pub type TokenData<T: Config> = StorageNMap<193		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),194		Value = ItemData<T::CrossAccountId>,195		QueryKind = OptionQuery,196	>;197198	/// Map of key-value pairs, describing the metadata of a token.199	#[pallet::storage]200	#[pallet::getter(fn token_properties)]201	pub type TokenProperties<T: Config> = StorageNMap<202		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),203		Value = TokenPropertiesT,204		QueryKind = OptionQuery,205	>;206207	/// Custom data of a token that is serialized to bytes,208	/// primarily reserved for on-chain operations,209	/// normally obscured from the external users.210	///211	/// Auxiliary properties are slightly different from212	/// usual [`TokenProperties`] due to an unlimited number213	/// and separately stored and written-to key-value pairs.214	///215	/// Currently unused.216	#[pallet::storage]217	#[pallet::getter(fn token_aux_property)]218	pub type TokenAuxProperties<T: Config> = StorageNMap<219		Key = (220			Key<Twox64Concat, CollectionId>,221			Key<Twox64Concat, TokenId>,222			Key<Twox64Concat, PropertyScope>,223			Key<Twox64Concat, PropertyKey>,224		),225		Value = AuxPropertyValue,226		QueryKind = OptionQuery,227	>;228229	/// Used to enumerate tokens owned by account.230	#[pallet::storage]231	pub type Owned<T: Config> = StorageNMap<232		Key = (233			Key<Twox64Concat, CollectionId>,234			Key<Blake2_128Concat, T::CrossAccountId>,235			Key<Twox64Concat, TokenId>,236		),237		Value = bool,238		QueryKind = ValueQuery,239	>;240241	/// Used to enumerate token's children.242	#[pallet::storage]243	#[pallet::getter(fn token_children)]244	pub type TokenChildren<T: Config> = StorageNMap<245		Key = (246			Key<Twox64Concat, CollectionId>,247			Key<Twox64Concat, TokenId>,248			Key<Twox64Concat, (CollectionId, TokenId)>,249		),250		Value = bool,251		QueryKind = ValueQuery,252	>;253254	/// Amount of tokens owned by an account in a collection.255	#[pallet::storage]256	pub type AccountBalance<T: Config> = StorageNMap<257		Key = (258			Key<Twox64Concat, CollectionId>,259			Key<Blake2_128Concat, T::CrossAccountId>,260		),261		Value = u32,262		QueryKind = ValueQuery,263	>;264265	/// Allowance set by a token owner for another user to perform one of certain transactions on a token.266	#[pallet::storage]267	pub type Allowance<T: Config> = StorageNMap<268		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),269		Value = T::CrossAccountId,270		QueryKind = OptionQuery,271	>;272273	/// Operator set by a wallet owner that could perform certain transactions on all tokens in the wallet.274	#[pallet::storage]275	pub type CollectionAllowance<T: Config> = StorageNMap<276		Key = (277			Key<Twox64Concat, CollectionId>,278			Key<Blake2_128Concat, T::CrossAccountId>,279			Key<Blake2_128Concat, T::CrossAccountId>,280		),281		Value = bool,282		QueryKind = ValueQuery,283	>;284285	#[pallet::genesis_config]286	pub struct GenesisConfig<T>(PhantomData<T>);287288	impl<T: Config> Default for GenesisConfig<T> {289		fn default() -> Self {290			Self(Default::default())291		}292	}293294	#[pallet::genesis_build]295	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {296		fn build(&self) {297			StorageVersion::new(1).put::<Pallet<T>>();298		}299	}300}301302pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);303impl<T: Config> NonfungibleHandle<T> {304	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {305		Self(inner)306	}307	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {308		self.0309	}310	pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {311		&mut self.0312	}313}314315impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {316	fn recorder(&self) -> &SubstrateRecorder<T> {317		self.0.recorder()318	}319	fn into_recorder(self) -> SubstrateRecorder<T> {320		self.0.into_recorder()321	}322}323impl<T: Config> Deref for NonfungibleHandle<T> {324	type Target = pallet_common::CollectionHandle<T>;325326	fn deref(&self) -> &Self::Target {327		&self.0328	}329}330331impl<T: Config> Pallet<T> {332	/// Get number of NFT tokens in collection.333	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {334		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)335	}336337	/// Check that NFT token exists.338	///339	/// - `token`: Token ID.340	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {341		<TokenData<T>>::contains_key((collection.id, token))342	}343344	/// Add or edit auxiliary data for the property.345	///346	/// - `f`: function that adds or edits auxiliary data.347	pub fn try_mutate_token_aux_property<R, E>(348		collection_id: CollectionId,349		token_id: TokenId,350		scope: PropertyScope,351		key: PropertyKey,352		f: impl FnOnce(&mut Option<AuxPropertyValue>) -> Result<R, E>,353	) -> Result<R, E> {354		<TokenAuxProperties<T>>::try_mutate((collection_id, token_id, scope, key), f)355	}356357	/// Remove auxiliary data for the property.358	pub fn remove_token_aux_property(359		collection_id: CollectionId,360		token_id: TokenId,361		scope: PropertyScope,362		key: PropertyKey,363	) {364		<TokenAuxProperties<T>>::remove((collection_id, token_id, scope, key));365	}366367	/// Get all auxiliary data in a given scope.368	///369	/// Returns iterator over Property Key - Data pairs.370	pub fn iterate_token_aux_properties(371		collection_id: CollectionId,372		token_id: TokenId,373		scope: PropertyScope,374	) -> impl Iterator<Item = (PropertyKey, AuxPropertyValue)> {375		<TokenAuxProperties<T>>::iter_prefix((collection_id, token_id, scope))376	}377378	/// Get ID of the last minted token379	pub fn current_token_id(collection_id: CollectionId) -> TokenId {380		TokenId(<TokensMinted<T>>::get(collection_id))381	}382}383384// unchecked calls skips any permission checks385impl<T: Config> Pallet<T> {386	/// Destroy NFT collection387	///388	/// `destroy_collection` will throw error if collection contains any tokens.389	/// Only owner can destroy collection.390	pub fn destroy_collection(391		collection: NonfungibleHandle<T>,392		sender: &T::CrossAccountId,393	) -> DispatchResult {394		let id = collection.id;395396		if Self::collection_has_tokens(id) {397			return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());398		}399400		// =========401402		PalletCommon::destroy_collection(collection.0, sender)?;403404		let _ = <TokenData<T>>::clear_prefix((id,), u32::MAX, None);405		let _ = <TokenChildren<T>>::clear_prefix((id,), u32::MAX, None);406		let _ = <Owned<T>>::clear_prefix((id,), u32::MAX, None);407		<TokensMinted<T>>::remove(id);408		<TokensBurnt<T>>::remove(id);409		let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);410		let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);411		let _ = <CollectionAllowance<T>>::clear_prefix((id,), u32::MAX, None);412		Ok(())413	}414415	/// Burn NFT token416	///417	/// `burn` removes `token` from the `collection`, from it's owner and from the parent token418	/// if the token is nested.419	/// Only the owner can `burn` the token. The `token` shouldn't have any nested tokens.420	/// Also removes all corresponding properties and auxiliary properties.421	///422	/// - `token`: Token that should be burned423	/// - `collection`: Collection that contains the token424	pub fn burn(425		collection: &NonfungibleHandle<T>,426		sender: &T::CrossAccountId,427		token: TokenId,428	) -> DispatchResult {429		let token_data =430			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;431		ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);432433		if collection.permissions.access() == AccessMode::AllowList {434			collection.check_allowlist(sender)?;435		}436437		if Self::token_has_children(collection.id, token) {438			return Err(<Error<T>>::CantBurnNftWithChildren.into());439		}440441		let burnt = <TokensBurnt<T>>::get(collection.id)442			.checked_add(1)443			.ok_or(ArithmeticError::Overflow)?;444445		let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))446			.checked_sub(1)447			.ok_or(ArithmeticError::Overflow)?;448449		// =========450451		if balance == 0 {452			<AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));453		} else {454			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);455		}456457		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);458459		<Owned<T>>::remove((collection.id, &token_data.owner, token));460		<TokensBurnt<T>>::insert(collection.id, burnt);461		<TokenData<T>>::remove((collection.id, token));462		<TokenProperties<T>>::remove((collection.id, token));463		let _ = <TokenAuxProperties<T>>::clear_prefix((collection.id, token), u32::MAX, None);464		let old_spender = <Allowance<T>>::take((collection.id, token));465466		if let Some(old_spender) = old_spender {467			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(468				collection.id,469				token,470				token_data.owner.clone(),471				old_spender,472				0,473			));474		}475476		<PalletEvm<T>>::deposit_log(477			ERC721Events::Transfer {478				from: *token_data.owner.as_eth(),479				to: H160::default(),480				token_id: token.into(),481			}482			.to_log(collection_id_to_address(collection.id)),483		);484		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(485			collection.id,486			token,487			token_data.owner,488			1,489		));490		Ok(())491	}492493	/// A batch operation to add, edit or remove properties for a token.494	///495	/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.496	///497	/// All affected properties should have `mutable` permission498	/// to be **deleted** or to be **set more than once**,499	/// and the sender should have permission to edit those properties.500	///501	/// This function fires an event for each property change.502	/// In case of an error, all the changes (including the events) will be reverted503	/// since the function is transactional.504	#[transactional]505	fn modify_token_properties(506		collection: &NonfungibleHandle<T>,507		sender: &T::CrossAccountId,508		token_id: TokenId,509		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,510		nesting_budget: &dyn Budget,511	) -> DispatchResult {512		let mut property_writer =513			pallet_common::ExistingTokenPropertyWriter::new(collection, sender);514515		property_writer.write_token_properties(516			sender,517			token_id,518			properties_updates,519			nesting_budget,520			erc::ERC721TokenEvent::TokenChanged {521				token_id: token_id.into(),522			}523			.to_log(T::ContractAddress::get()),524		)525	}526527	pub fn next_token_id(collection: &NonfungibleHandle<T>) -> Result<TokenId, DispatchError> {528		let next_token_id = <TokensMinted<T>>::get(collection.id)529			.checked_add(1)530			.ok_or(<CommonError<T>>::CollectionTokenLimitExceeded)?;531532		ensure!(533			collection.limits.token_limit() >= next_token_id,534			<CommonError<T>>::CollectionTokenLimitExceeded535		);536537		Ok(TokenId(next_token_id))538	}539540	/// Batch operation to add or edit properties for the token541	///542	/// Same as [`modify_token_properties`] but doesn't allow to remove properties543	///544	/// [`modify_token_properties`]: struct.Pallet.html#method.modify_token_properties545	pub fn set_token_properties(546		collection: &NonfungibleHandle<T>,547		sender: &T::CrossAccountId,548		token_id: TokenId,549		properties: impl Iterator<Item = Property>,550		nesting_budget: &dyn Budget,551	) -> DispatchResult {552		Self::modify_token_properties(553			collection,554			sender,555			token_id,556			properties.map(|p| (p.key, Some(p.value))),557			nesting_budget,558		)559	}560561	/// Add or edit single property for the token562	///563	/// Calls [`set_token_properties`] internally564	///565	/// [`set_token_properties`]: struct.Pallet.html#method.set_token_properties566	pub fn set_token_property(567		collection: &NonfungibleHandle<T>,568		sender: &T::CrossAccountId,569		token_id: TokenId,570		property: Property,571		nesting_budget: &dyn Budget,572	) -> DispatchResult {573		Self::set_token_properties(574			collection,575			sender,576			token_id,577			[property].into_iter(),578			nesting_budget,579		)580	}581582	/// Batch operation to remove properties from the token583	///584	/// Same as [`modify_token_properties`] but doesn't allow to add or edit properties585	///586	/// [`modify_token_properties`]: struct.Pallet.html#method.modify_token_properties587	pub fn delete_token_properties(588		collection: &NonfungibleHandle<T>,589		sender: &T::CrossAccountId,590		token_id: TokenId,591		property_keys: impl Iterator<Item = PropertyKey>,592		nesting_budget: &dyn Budget,593	) -> DispatchResult {594		Self::modify_token_properties(595			collection,596			sender,597			token_id,598			property_keys.into_iter().map(|key| (key, None)),599			nesting_budget,600		)601	}602603	/// Remove single property from the token604	///605	/// Calls [`delete_token_properties`] internally606	///607	/// [`delete_token_properties`]: struct.Pallet.html#method.delete_token_properties608	pub fn delete_token_property(609		collection: &NonfungibleHandle<T>,610		sender: &T::CrossAccountId,611		token_id: TokenId,612		property_key: PropertyKey,613		nesting_budget: &dyn Budget,614	) -> DispatchResult {615		Self::delete_token_properties(616			collection,617			sender,618			token_id,619			[property_key].into_iter(),620			nesting_budget,621		)622	}623624	/// Add or edit properties for the collection625	pub fn set_collection_properties(626		collection: &NonfungibleHandle<T>,627		sender: &T::CrossAccountId,628		properties: Vec<Property>,629	) -> DispatchResult {630		<PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())631	}632633	/// Remove properties from the collection634	pub fn delete_collection_properties(635		collection: &CollectionHandle<T>,636		sender: &T::CrossAccountId,637		property_keys: Vec<PropertyKey>,638	) -> DispatchResult {639		<PalletCommon<T>>::delete_collection_properties(640			collection,641			sender,642			property_keys.into_iter(),643		)644	}645646	/// Set property permissions for the token.647	///648	/// Sender should be the owner or admin of token's collection.649	pub fn set_token_property_permissions(650		collection: &CollectionHandle<T>,651		sender: &T::CrossAccountId,652		property_permissions: Vec<PropertyKeyPermission>,653	) -> DispatchResult {654		<PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)655	}656657	/// Set property permissions for the token with scope.658	///659	/// Sender should be the owner or admin of token's collection.660	pub fn set_scoped_token_property_permissions(661		collection: &CollectionHandle<T>,662		sender: &T::CrossAccountId,663		scope: PropertyScope,664		property_permissions: Vec<PropertyKeyPermission>,665	) -> DispatchResult {666		<PalletCommon<T>>::set_scoped_token_property_permissions(667			collection,668			sender,669			scope,670			property_permissions,671		)672	}673674	pub fn token_property_permission(collection_id: CollectionId) -> PropertiesPermissionMap {675		<PalletCommon<T>>::property_permissions(collection_id)676	}677678	pub fn check_token_immediate_ownership(679		collection: &NonfungibleHandle<T>,680		token: TokenId,681		possible_owner: &T::CrossAccountId,682	) -> DispatchResult {683		let token_data =684			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;685		ensure!(686			&token_data.owner == possible_owner,687			<CommonError<T>>::NoPermission688		);689		Ok(())690	}691692	/// Transfer NFT token from one account to another.693	///694	/// `from` account stops being the owner and `to` account becomes the owner of the token.695	/// If `to` is token than `to` becomes owner of the token and the token become nested.696	/// Unnests token from previous parent if it was nested before.697	/// Removes allowance for the token if there was any.698	/// Throws if transfers aren't allowed for collection or if receiver reached token ownership limit.699	///700	/// - `nesting_budget`: Limit for token nesting depth701	pub fn transfer(702		collection: &NonfungibleHandle<T>,703		from: &T::CrossAccountId,704		to: &T::CrossAccountId,705		token: TokenId,706		nesting_budget: &dyn Budget,707	) -> DispatchResultWithPostInfo {708		let depositor = from;709		Self::transfer_internal(collection, depositor, from, to, token, nesting_budget)710	}711712	/// Transfers an NFT from the `from` account to the `to` account.713	/// The `depositor` is the account who deposits the NFT.714	/// For instance, the nesting rules will be checked against the `depositor`'s permissions.715	pub fn transfer_internal(716		collection: &NonfungibleHandle<T>,717		depositor: &T::CrossAccountId,718		from: &T::CrossAccountId,719		to: &T::CrossAccountId,720		token: TokenId,721		nesting_budget: &dyn Budget,722	) -> DispatchResultWithPostInfo {723		ensure!(724			collection.limits.transfers_enabled(),725			<CommonError<T>>::TransferNotAllowed726		);727728		let mut actual_weight = <SelfWeightOf<T>>::transfer_raw();729		let token_data =730			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;731		ensure!(&token_data.owner == from, <CommonError<T>>::NoPermission);732733		if collection.permissions.access() == AccessMode::AllowList {734			collection.check_allowlist(from)?;735			collection.check_allowlist(to)?;736			actual_weight += <PalletCommonWeightOf<T>>::check_accesslist() * 2;737		}738		<PalletCommon<T>>::ensure_correct_receiver(to)?;739740		let balance_from = <AccountBalance<T>>::get((collection.id, from))741			.checked_sub(1)742			.ok_or(<CommonError<T>>::TokenValueTooLow)?;743		let balance_to = if from != to {744			let balance_to = <AccountBalance<T>>::get((collection.id, to))745				.checked_add(1)746				.ok_or(ArithmeticError::Overflow)?;747748			ensure!(749				balance_to < collection.limits.account_token_ownership_limit(),750				<CommonError<T>>::AccountTokenLimitExceeded,751			);752753			Some(balance_to)754		} else {755			None756		};757758		<PalletStructure<T>>::nest_if_sent_to_token(759			depositor,760			to,761			collection.id,762			token,763			nesting_budget,764		)?;765766		// =========767768		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);769770		<TokenData<T>>::insert((collection.id, token), ItemData { owner: to.clone() });771772		if let Some(balance_to) = balance_to {773			// from != to774			if balance_from == 0 {775				<AccountBalance<T>>::remove((collection.id, from));776			} else {777				<AccountBalance<T>>::insert((collection.id, from), balance_from);778			}779			<AccountBalance<T>>::insert((collection.id, to), balance_to);780			<Owned<T>>::remove((collection.id, from, token));781			<Owned<T>>::insert((collection.id, to, token), true);782		}783		Self::set_allowance_unchecked(collection, from, token, None, true);784785		<PalletEvm<T>>::deposit_log(786			ERC721Events::Transfer {787				from: *from.as_eth(),788				to: *to.as_eth(),789				token_id: token.into(),790			}791			.to_log(collection_id_to_address(collection.id)),792		);793		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(794			collection.id,795			token,796			from.clone(),797			to.clone(),798			1,799		));800801		Ok(PostDispatchInfo {802			actual_weight: Some(actual_weight),803			pays_fee: Pays::Yes,804		})805	}806807	/// Batch operation to mint multiple NFT tokens.808	///809	/// The sender should be the owner/admin of the collection or collection should be configured810	/// to allow public minting.811	/// Throws if amount of tokens reached it's limit for the collection or if caller reached812	/// token ownership limit.813	///814	/// - `data`: Contains list of token properties and users who will become the owners of the815	///   corresponging tokens.816	/// - `nesting_budget`: Limit for token nesting depth817	pub fn create_multiple_items(818		collection: &NonfungibleHandle<T>,819		sender: &T::CrossAccountId,820		data: Vec<CreateItemData<T>>,821		nesting_budget: &dyn Budget,822	) -> DispatchResult {823		if !collection.is_owner_or_admin(sender) {824			ensure!(825				collection.permissions.mint_mode(),826				<CommonError<T>>::PublicMintingNotAllowed827			);828			collection.check_allowlist(sender)?;829830			for item in data.iter() {831				collection.check_allowlist(&item.owner)?;832			}833		}834835		for data in data.iter() {836			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;837		}838839		let first_token = <TokensMinted<T>>::get(collection.id);840		let tokens_minted = first_token841			.checked_add(data.len() as u32)842			.ok_or(ArithmeticError::Overflow)?;843		ensure!(844			tokens_minted <= collection.limits.token_limit(),845			<CommonError<T>>::CollectionTokenLimitExceeded846		);847848		let mut balances = BTreeMap::new();849		for data in &data {850			let balance = balances851				.entry(&data.owner)852				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));853			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;854855			ensure!(856				*balance <= collection.limits.account_token_ownership_limit(),857				<CommonError<T>>::AccountTokenLimitExceeded,858			);859		}860861		for (i, data) in data.iter().enumerate() {862			let token = TokenId(first_token + i as u32 + 1);863864			<PalletStructure<T>>::check_nesting(865				sender,866				&data.owner,867				collection.id,868				token,869				nesting_budget,870			)?;871		}872873		// =========874875		let mut property_writer = pallet_common::NewTokenPropertyWriter::new(collection, sender);876877		with_transaction(|| {878			for (i, data) in data.iter().enumerate() {879				let token = first_token + i as u32 + 1;880881				<TokenData<T>>::insert(882					(collection.id, token),883					ItemData {884						// const_data: data.const_data.clone(),885						owner: data.owner.clone(),886					},887				);888889				let token = TokenId(token);890891				<PalletStructure<T>>::nest_if_sent_to_token_unchecked(892					&data.owner,893					collection.id,894					token,895				);896897				if let Err(e) = property_writer.write_token_properties(898					sender.conv_eq(&data.owner),899					token,900					data.properties.clone().into_iter(),901					erc::ERC721TokenEvent::TokenChanged {902						token_id: token.into(),903					}904					.to_log(T::ContractAddress::get()),905				) {906					return TransactionOutcome::Rollback(Err(e));907				}908			}909			TransactionOutcome::Commit(Ok(()))910		})?;911912		<TokensMinted<T>>::insert(collection.id, tokens_minted);913		for (account, balance) in balances {914			<AccountBalance<T>>::insert((collection.id, account), balance);915		}916		for (i, data) in data.into_iter().enumerate() {917			let token = first_token + i as u32 + 1;918			<Owned<T>>::insert((collection.id, &data.owner, token), true);919920			<PalletEvm<T>>::deposit_log(921				ERC721Events::Transfer {922					from: H160::default(),923					to: *data.owner.as_eth(),924					token_id: token.into(),925				}926				.to_log(collection_id_to_address(collection.id)),927			);928			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(929				collection.id,930				TokenId(token),931				data.owner.clone(),932				1,933			));934		}935		Ok(())936	}937938	pub fn set_allowance_unchecked(939		collection: &NonfungibleHandle<T>,940		sender: &T::CrossAccountId,941		token: TokenId,942		spender: Option<&T::CrossAccountId>,943		assume_implicit_eth: bool,944	) {945		if let Some(spender) = spender {946			let old_spender = <Allowance<T>>::get((collection.id, token));947			<Allowance<T>>::insert((collection.id, token), spender);948			// In ERC721 there is only one possible approved user of token, so we set949			// approved user to spender950			<PalletEvm<T>>::deposit_log(951				ERC721Events::Approval {952					owner: *sender.as_eth(),953					approved: *spender.as_eth(),954					token_id: token.into(),955				}956				.to_log(collection_id_to_address(collection.id)),957			);958			// In Unique chain, any token can have any amount of approved users, so we need to959			// set allowance of old owner to 0, and allowance of new owner to 1960			if old_spender.as_ref() != Some(spender) {961				if let Some(old_owner) = old_spender {962					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(963						collection.id,964						token,965						sender.clone(),966						old_owner,967						0,968					));969				}970				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(971					collection.id,972					token,973					sender.clone(),974					spender.clone(),975					1,976				));977			}978		} else {979			let old_spender = <Allowance<T>>::take((collection.id, token));980			if !assume_implicit_eth {981				// In ERC721 there is only one possible approved user of token, so we set982				// approved user to zero address983				<PalletEvm<T>>::deposit_log(984					ERC721Events::Approval {985						owner: *sender.as_eth(),986						approved: H160::default(),987						token_id: token.into(),988					}989					.to_log(collection_id_to_address(collection.id)),990				);991			}992			// In Unique chain, any token can have any amount of approved users, so we need to993			// set allowance of old owner to 0994			if let Some(old_spender) = old_spender {995				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(996					collection.id,997					token,998					sender.clone(),999					old_spender,1000					0,1001				));1002			}1003		}1004	}10051006	pub fn get_allowance(1007		collection: &NonfungibleHandle<T>,1008		token_id: TokenId,1009	) -> Result<Option<T::CrossAccountId>, DispatchError> {1010		ensure!(1011			<TokenData<T>>::get((collection.id, token_id)).is_some(),1012			<CommonError<T>>::TokenNotFound1013		);1014		Ok(<Allowance<T>>::get((collection.id, token_id)))1015	}10161017	/// Set allowance for the spender to `transfer` or `burn` sender's token.1018	///1019	/// - `token`: Token the spender is allowed to `transfer` or `burn`.1020	pub fn set_allowance(1021		collection: &NonfungibleHandle<T>,1022		sender: &T::CrossAccountId,1023		token: TokenId,1024		spender: Option<&T::CrossAccountId>,1025	) -> DispatchResult {1026		if collection.permissions.access() == AccessMode::AllowList {1027			collection.check_allowlist(sender)?;1028			if let Some(spender) = spender {1029				collection.check_allowlist(spender)?;1030			}1031		}10321033		if let Some(spender) = spender {1034			<PalletCommon<T>>::ensure_correct_receiver(spender)?;1035		}10361037		let token_data =1038			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;1039		if &token_data.owner != sender {1040			ensure!(1041				collection.ignores_owned_amount(sender),1042				<CommonError<T>>::CantApproveMoreThanOwned1043			);1044		}10451046		// =========10471048		Self::set_allowance_unchecked(collection, sender, token, spender, false);1049		Ok(())1050	}10511052	/// Set allowance for the spender to `transfer` or `burn` sender's token from eth mirror.1053	///1054	/// - `from`: Address of sender's eth mirror.1055	/// - `to`: Adress of spender.1056	/// - `token`: Token the spender is allowed to `transfer` or `burn`.1057	pub fn set_allowance_from(1058		collection: &NonfungibleHandle<T>,1059		sender: &T::CrossAccountId,1060		from: &T::CrossAccountId,1061		token: TokenId,1062		to: Option<&T::CrossAccountId>,1063	) -> DispatchResult {1064		if collection.permissions.access() == AccessMode::AllowList {1065			collection.check_allowlist(sender)?;1066			collection.check_allowlist(from)?;1067			if let Some(to) = to {1068				collection.check_allowlist(to)?;1069			}1070		}10711072		if let Some(to) = to {1073			<PalletCommon<T>>::ensure_correct_receiver(to)?;1074		}10751076		ensure!(1077			sender.conv_eq(from),1078			<CommonError<T>>::AddressIsNotEthMirror1079		);10801081		let token_data =1082			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;1083		if token_data.owner != *from {1084			ensure!(1085				collection.limits.owner_can_transfer()1086					&& (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from)),1087				<CommonError<T>>::CantApproveMoreThanOwned1088			);1089		}10901091		// =========10921093		Self::set_allowance_unchecked(collection, from, token, to, false);1094		Ok(())1095	}10961097	/// Checks allowance for the spender to use the token.1098	fn check_allowed(1099		collection: &NonfungibleHandle<T>,1100		spender: &T::CrossAccountId,1101		from: &T::CrossAccountId,1102		token: TokenId,1103		nesting_budget: &dyn Budget,1104	) -> DispatchResult {1105		if spender.conv_eq(from) {1106			return Ok(());1107		}1108		if collection.permissions.access() == AccessMode::AllowList {1109			// `from`, `to` checked in [`transfer`]1110			collection.check_allowlist(spender)?;1111		}11121113		if collection.ignores_token_restrictions(spender) {1114			return Ok(());1115		}11161117		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1118			ensure!(1119				<PalletStructure<T>>::check_indirectly_owned(1120					spender.clone(),1121					source.0,1122					source.1,1123					None,1124					nesting_budget1125				)?,1126				<CommonError<T>>::ApprovedValueTooLow,1127			);1128			return Ok(());1129		}1130		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {1131			return Ok(());1132		}1133		if <CollectionAllowance<T>>::get((collection.id, from, spender)) {1134			return Ok(());1135		}11361137		Err(<CommonError<T>>::ApprovedValueTooLow.into())1138	}11391140	/// Transfer NFT token from one account to another.1141	///1142	/// Same as the [`transfer`] but spender doesn't needs to be the owner of the token.1143	/// The owner should set allowance for the spender to transfer token.1144	///1145	/// [`transfer`]: struct.Pallet.html#method.transfer1146	pub fn transfer_from(1147		collection: &NonfungibleHandle<T>,1148		spender: &T::CrossAccountId,1149		from: &T::CrossAccountId,1150		to: &T::CrossAccountId,1151		token: TokenId,1152		nesting_budget: &dyn Budget,1153	) -> DispatchResultWithPostInfo {1154		Self::check_allowed(collection, spender, from, token, nesting_budget)?;11551156		// =========11571158		// Allowance is reset in [`transfer`]1159		let mut result =1160			Self::transfer_internal(collection, spender, from, to, token, nesting_budget);1161		add_weight_to_post_info(&mut result, <SelfWeightOf<T>>::check_allowed_raw());1162		result1163	}11641165	/// Burn NFT token for `from` account.1166	///1167	/// Same as the [`burn`] but spender doesn't need to be an owner of the token. The owner should1168	/// set allowance for the spender to burn token.1169	///1170	/// [`burn`]: struct.Pallet.html#method.burn1171	pub fn burn_from(1172		collection: &NonfungibleHandle<T>,1173		spender: &T::CrossAccountId,1174		from: &T::CrossAccountId,1175		token: TokenId,1176		nesting_budget: &dyn Budget,1177	) -> DispatchResult {1178		Self::check_allowed(collection, spender, from, token, nesting_budget)?;11791180		// =========11811182		Self::burn(collection, from, token)1183	}11841185	/// Check that `from` token could be nested in `under` token.1186	///1187	pub fn check_nesting(1188		handle: &NonfungibleHandle<T>,1189		sender: &T::CrossAccountId,1190		from: (CollectionId, TokenId),1191		under: TokenId,1192		nesting_budget: &dyn Budget,1193	) -> DispatchResult {1194		let nesting = handle.permissions.nesting();11951196		#[cfg(not(feature = "runtime-benchmarks"))]1197		let permissive = false;1198		#[cfg(feature = "runtime-benchmarks")]1199		let permissive = nesting.permissive;12001201		if permissive {1202			ensure!(1203				<TokenData<T>>::contains_key((handle.id, under)),1204				<CommonError<T>>::TokenNotFound1205			);1206		} else if nesting.token_owner1207			&& <PalletStructure<T>>::check_indirectly_owned(1208				sender.clone(),1209				handle.id,1210				under,1211				Some(from),1212				nesting_budget,1213			)? {1214			// Pass, token existence and ouroboros checks are done in `check_indirectly_owned`1215		} else if nesting.collection_admin && handle.is_owner_or_admin(sender) {1216			// token existence and ouroboros checks are done in `get_checked_topmost_owner`1217			let _ = <PalletStructure<T>>::get_checked_topmost_owner(1218				handle.id,1219				under,1220				Some(from),1221				nesting_budget,1222			)?1223			.ok_or(<CommonError<T>>::TokenNotFound)?;1224		} else {1225			fail!(<CommonError<T>>::UserIsNotAllowedToNest);1226		}12271228		if let Some(whitelist) = &nesting.restricted {1229			ensure!(1230				whitelist.contains(&from.0),1231				<CommonError<T>>::SourceCollectionIsNotAllowedToNest1232			);1233		}1234		Ok(())1235	}12361237	fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {1238		if to_nest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1239			<TokenChildren<T>>::insert((under.0, under.1, to_nest), true);1240		}1241	}12421243	fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {1244		if to_unnest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1245			<TokenChildren<T>>::remove((under.0, under.1, to_unnest));1246		}1247	}12481249	fn collection_has_tokens(collection_id: CollectionId) -> bool {1250		<TokenData<T>>::iter_prefix((collection_id,))1251			.next()1252			.is_some()1253	}12541255	fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {1256		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1257			.next()1258			.is_some()1259	}12601261	pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {1262		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1263			.map(|((child_collection_id, child_id), _)| TokenChild {1264				collection: child_collection_id,1265				token: child_id,1266			})1267			.collect()1268	}12691270	/// Mint single NFT token.1271	///1272	/// Delegated to [`create_multiple_items`]1273	///1274	/// [`create_multiple_items`]: struct.Pallet.html#method.create_multiple_items1275	pub fn create_item(1276		collection: &NonfungibleHandle<T>,1277		sender: &T::CrossAccountId,1278		data: CreateItemData<T>,1279		nesting_budget: &dyn Budget,1280	) -> DispatchResult {1281		Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1282	}12831284	/// Sets or unsets the approval of a given operator.1285	///1286	/// The `operator` is allowed to transfer all token pieces of the `owner` on their behalf.1287	/// - `owner`: Token owner1288	/// - `operator`: Operator1289	/// - `approve`: Should operator status be granted or revoked?1290	pub fn set_allowance_for_all(1291		collection: &NonfungibleHandle<T>,1292		owner: &T::CrossAccountId,1293		operator: &T::CrossAccountId,1294		approve: bool,1295	) -> DispatchResult {1296		<PalletCommon<T>>::set_allowance_for_all(1297			collection,1298			owner,1299			operator,1300			approve,1301			|| <CollectionAllowance<T>>::insert((collection.id, owner, operator), approve),1302			ERC721Events::ApprovalForAll {1303				owner: *owner.as_eth(),1304				operator: *operator.as_eth(),1305				approved: approve,1306			}1307			.to_log(collection_id_to_address(collection.id)),1308		)1309	}13101311	/// Tells whether the given `owner` approves the `operator`.1312	pub fn allowance_for_all(1313		collection: &NonfungibleHandle<T>,1314		owner: &T::CrossAccountId,1315		operator: &T::CrossAccountId,1316	) -> bool {1317		<CollectionAllowance<T>>::get((collection.id, owner, operator))1318	}13191320	pub fn repair_item(collection: &NonfungibleHandle<T>, token: TokenId) -> DispatchResult {1321		<TokenProperties<T>>::mutate((collection.id, token), |properties| {1322			if let Some(properties) = properties {1323				properties.recompute_consumed_space();1324			}1325		});13261327		Ok(())1328	}1329}