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

difftreelog

source

pallets/unique/src/lib.rs43.7 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//! # Unique Pallet18//!19//! A pallet governing Unique transactions.20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The Unique pallet's purpose is to be the primary interface between28//! external users and the inner structure of the Unique chains.29//!30//! It also contains an implementation of [`CollectionHelpers`][`eth`],31//! an Ethereum contract dealing with collection operations.32//!33//! ## Interface34//!35//! ### Dispatchables36//!37//! - `create_collection` - Create a collection of tokens. **Deprecated**, use `create_collection_ex`.38//! - `create_collection_ex` - Create a collection of tokens with explicit parameters.39//! - `destroy_collection` - Destroy a collection if no tokens exist within.40//! - `add_to_allow_list` - Add an address to allow list.41//! - `remove_from_allow_list` - Remove an address from allow list.42//! - `change_collection_owner` - Change the owner of the collection.43//! - `add_collection_admin` - Add an admin to a collection.44//! - `remove_collection_admin` - Remove admin of a collection.45//! - `set_collection_sponsor` - Invite a new collection sponsor.46//! - `confirm_sponsorship` - Confirm own sponsorship of a collection, becoming the sponsor.47//! - `remove_collection_sponsor` - Remove a sponsor from a collection.48//! - `create_item` - Create an item within a collection.49//! - `create_multiple_items` - Create multiple items within a collection.50//! - `set_collection_properties` - Add or change collection properties.51//! - `delete_collection_properties` - Delete specified collection properties.52//! - `set_token_properties` - Add or change token properties.53//! - `delete_token_properties` - Delete token properties.54//! - `set_token_property_permissions` - Add or change token property permissions of a collection.55//! - `create_multiple_items_ex` - Create multiple items within a collection with explicitly specified initial parameters.56//! - `set_transfers_enabled_flag` - Completely allow or disallow transfers for a particular collection.57//! - `burn_item` - Destroy an item.58//! - `burn_from` - Destroy an item on behalf of the owner as a non-owner account.59//! - `transfer` - Change ownership of the token.60//! - `transfer_from` - Change ownership of the token on behalf of the owner as a non-owner account.61//! - `approve` - Allow a non-permissioned address to transfer or burn an item.62//! - `set_collection_limits` - Set specific limits of a collection.63//! - `set_collection_permissions` - Set specific permissions of a collection.64//! - `repartition` - Re-partition a refungible token, while owning all of its parts.6566#![recursion_limit = "1024"]67#![cfg_attr(not(feature = "std"), no_std)]68#![allow(69	clippy::too_many_arguments,70	clippy::unnecessary_mut_passed,71	clippy::unused_unit72)]7374extern crate alloc;7576use frame_support::{77	decl_module, decl_storage, decl_error, decl_event,78	dispatch::DispatchResult,79	ensure, fail,80	weights::{Weight},81	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},82	BoundedVec,83};84use scale_info::TypeInfo;85use frame_system::{self as system, ensure_signed};86use sp_std::{vec, vec::Vec};87use up_data_structs::{88	MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,89	MAX_PROPERTIES_PER_ITEM, MAX_PROPERTY_KEY_LENGTH, MAX_PROPERTY_VALUE_LENGTH,90	MAX_COLLECTION_PROPERTIES_SIZE, COLLECTION_ADMINS_LIMIT, MAX_TOKEN_PROPERTIES_SIZE,91	CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId,92	SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,93	PropertyKeyPermission,94};95use pallet_evm::account::CrossAccountId;96use pallet_common::{97	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,98	dispatch::CollectionDispatch, RefungibleExtensionsWeightInfo,99};100pub mod eth;101102#[cfg(feature = "runtime-benchmarks")]103pub mod benchmarking;104pub mod weights;105use weights::WeightInfo;106107/// A maximum number of levels of depth in the token nesting tree.108pub const NESTING_BUDGET: u32 = 5;109110decl_error! {111	/// Errors for the common Unique transactions.112	pub enum Error for Module<T: Config> {113		/// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].114		CollectionDecimalPointLimitExceeded,115		/// This address is not set as sponsor, use setCollectionSponsor first.116		ConfirmUnsetSponsorFail,117		/// Length of items properties must be greater than 0.118		EmptyArgument,119		/// Repertition is only supported by refungible collection.120		RepartitionCalledOnNonRefungibleCollection,121	}122}123124/// Configuration trait of this pallet.125pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {126	/// Overarching event type.127	type RuntimeEvent: From<Event<Self>> + Into<<Self as frame_system::Config>::RuntimeEvent>;128129	/// Weight information for extrinsics in this pallet.130	type WeightInfo: WeightInfo;131132	/// Weight information for common pallet operations.133	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;134135	/// Weight info information for extra refungible pallet operations.136	type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;137}138139decl_event! {140	pub enum Event<T>141	where142		<T as frame_system::Config>::AccountId,143		<T as pallet_evm::Config>::CrossAccountId,144	{145		/// Collection sponsor was removed146		///147		/// # Arguments148		/// * collection_id: ID of the affected collection.149		CollectionSponsorRemoved(CollectionId),150151		/// Collection admin was added152		///153		/// # Arguments154		/// * collection_id: ID of the affected collection.155		/// * admin: Admin address.156		CollectionAdminAdded(CollectionId, CrossAccountId),157158		/// Collection owned was changed159		///160		/// # Arguments161		/// * collection_id: ID of the affected collection.162		/// * owner: New owner address.163		CollectionOwnedChanged(CollectionId, AccountId),164165		/// Collection sponsor was set166		///167		/// # Arguments168		/// * collection_id: ID of the affected collection.169		/// * owner: New sponsor address.170		CollectionSponsorSet(CollectionId, AccountId),171172		/// New sponsor was confirm173		///174		/// # Arguments175		/// * collection_id: ID of the affected collection.176		/// * sponsor: New sponsor address.177		SponsorshipConfirmed(CollectionId, AccountId),178179		/// Collection admin was removed180		///181		/// # Arguments182		/// * collection_id: ID of the affected collection.183		/// * admin: Removed admin address.184		CollectionAdminRemoved(CollectionId, CrossAccountId),185186		/// Address was removed from the allow list187		///188		/// # Arguments189		/// * collection_id: ID of the affected collection.190		/// * user: Address of the removed account.191		AllowListAddressRemoved(CollectionId, CrossAccountId),192193		/// Address was added to the allow list194		///195		/// # Arguments196		/// * collection_id: ID of the affected collection.197		/// * user: Address of the added account.198		AllowListAddressAdded(CollectionId, CrossAccountId),199200		/// Collection limits were set201		///202		/// # Arguments203		/// * collection_id: ID of the affected collection.204		CollectionLimitSet(CollectionId),205206		/// Collection permissions were set207		///208		/// # Arguments209		/// * collection_id: ID of the affected collection.210		CollectionPermissionSet(CollectionId),211	}212}213214type SelfWeightOf<T> = <T as Config>::WeightInfo;215216// # Used definitions217//218// ## User control levels219//220// chain-controlled - key is uncontrolled by user221//                    i.e autoincrementing index222//                    can use non-cryptographic hash223// real - key is controlled by user224//        but it is hard to generate enough colliding values, i.e owner of signed txs225//        can use non-cryptographic hash226// controlled - key is completly controlled by users227//              i.e maps with mutable keys228//              should use cryptographic hash229//230// ## User control level downgrade reasons231//232// ?1 - chain-controlled -> controlled233//      collections/tokens can be destroyed, resulting in massive holes234// ?2 - chain-controlled -> controlled235//      same as ?1, but can be only added, resulting in easier exploitation236// ?3 - real -> controlled237//      no confirmation required, so addresses can be easily generated238decl_storage! {239	trait Store for Module<T: Config> as Unique {240241		//#region Private members242		/// Used for migrations243		ChainVersion: u64;244		//#endregion245246		//#region Tokens transfer sponosoring rate limit baskets247		/// (Collection id (controlled?2), who created (real))248		/// TODO: Off chain worker should remove from this map when collection gets removed249		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;250		/// Collection id (controlled?2), token id (controlled?2)251		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;252		/// Collection id (controlled?2), owning user (real)253		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;254		/// Collection id (controlled?2), token id (controlled?2)255		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;256		//#endregion257258		/// Variable metadata sponsoring259		/// Collection id (controlled?2), token id (controlled?2)260		#[deprecated]261		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;262		/// Last sponsoring of token property setting // todo:doc rephrase this and the following263		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;264265		/// Last sponsoring of NFT approval in a collection266		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;267		/// Last sponsoring of fungible tokens approval in a collection268		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;269		/// Last sponsoring of RFT approval in a collection270		pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;271	}272}273274decl_module! {275	/// Type alias to Pallet, to be used by construct_runtime.276	pub struct Module<T: Config> for enum Call277	where278		origin: T::RuntimeOrigin279	{280		type Error = Error<T>;281282		#[doc = "A maximum number of levels of depth in the token nesting tree."]283		const NESTING_BUDGET: u32 = NESTING_BUDGET;284285		#[doc = "Maximal length of a collection name."]286		const MAX_COLLECTION_NAME_LENGTH: u32 = MAX_COLLECTION_NAME_LENGTH;287288		#[doc = "Maximal length of a collection description."]289		const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = MAX_COLLECTION_DESCRIPTION_LENGTH;290291		#[doc = "Maximal length of a token prefix."]292		const MAX_TOKEN_PREFIX_LENGTH: u32 = MAX_TOKEN_PREFIX_LENGTH;293294		#[doc = "Maximum admins per collection."]295		const COLLECTION_ADMINS_LIMIT: u32 = COLLECTION_ADMINS_LIMIT;296297		#[doc = "Maximal length of a property key."]298		const MAX_PROPERTY_KEY_LENGTH: u32 = MAX_PROPERTY_KEY_LENGTH;299300		#[doc = "Maximal length of a property value."]301		const MAX_PROPERTY_VALUE_LENGTH: u32 = MAX_PROPERTY_VALUE_LENGTH;302303		#[doc = "A maximum number of token properties."]304		const MAX_PROPERTIES_PER_ITEM: u32 = MAX_PROPERTIES_PER_ITEM;305306		#[doc = "Maximum size for all collection properties."]307		const MAX_COLLECTION_PROPERTIES_SIZE: u32 = MAX_COLLECTION_PROPERTIES_SIZE;308309		#[doc = "Maximum size of all token properties."]310		const MAX_TOKEN_PROPERTIES_SIZE: u32 = MAX_TOKEN_PROPERTIES_SIZE;311312		#[doc = "Default NFT collection limit."]313		const NFT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::NFT);314315		#[doc = "Default RFT collection limit."]316		const RFT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::ReFungible);317318		#[doc = "Default FT collection limit."]319		const FT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::Fungible(0));320321322		pub fn deposit_event() = default;323324		fn on_initialize(_now: T::BlockNumber) -> Weight {325			Weight::zero()326		}327328		fn on_runtime_upgrade() -> Weight {329			Weight::zero()330		}331332		/// Create a collection of tokens.333		///334		/// Each Token may have multiple properties encoded as an array of bytes335		/// of certain length. The initial owner of the collection is set336		/// to the address that signed the transaction and can be changed later.337		///338		/// Prefer the more advanced [`create_collection_ex`][`Pallet::create_collection_ex`] instead.339		///340		/// # Permissions341		///342		/// * Anyone - becomes the owner of the new collection.343		///344		/// # Arguments345		///346		/// * `collection_name`: Wide-character string with collection name347		/// (limit [`MAX_COLLECTION_NAME_LENGTH`]).348		/// * `collection_description`: Wide-character string with collection description349		/// (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).350		/// * `token_prefix`: Byte string containing the token prefix to mark a collection351		/// to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).352		/// * `mode`: Type of items stored in the collection and type dependent data.353		// returns collection ID354		#[weight = <SelfWeightOf<T>>::create_collection()]355		#[deprecated(note = "`create_collection_ex` is more up-to-date and advanced, prefer it instead")]356		pub fn create_collection(357			origin,358			collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,359			collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,360			token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,361			mode: CollectionMode362		) -> DispatchResult {363			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {364				name: collection_name,365				description: collection_description,366				token_prefix,367				mode,368				..Default::default()369			};370			Self::create_collection_ex(origin, data)371		}372373		/// Create a collection with explicit parameters.374		///375		/// Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.376		///377		/// # Permissions378		///379		/// * Anyone - becomes the owner of the new collection.380		///381		/// # Arguments382		///383		/// * `data`: Explicit data of a collection used for its creation.384		#[weight = <SelfWeightOf<T>>::create_collection()]385		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {386			let sender = ensure_signed(origin)?;387388			// =========389			let sender = T::CrossAccountId::from_sub(sender);390			let _id = T::CollectionDispatch::create(sender.clone(), sender, data, Default::default())?;391392			Ok(())393		}394395		/// Destroy a collection if no tokens exist within.396		///397		/// # Permissions398		///399		/// * Collection owner400		///401		/// # Arguments402		///403		/// * `collection_id`: Collection to destroy.404		#[weight = <SelfWeightOf<T>>::destroy_collection()]405		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {406			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);407408			Self::destroy_collection_internal(sender, collection_id)409		}410411		/// Add an address to allow list.412		///413		/// # Permissions414		///415		/// * Collection owner416		/// * Collection admin417		///418		/// # Arguments419		///420		/// * `collection_id`: ID of the modified collection.421		/// * `address`: ID of the address to be added to the allowlist.422		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]423		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{424425			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);426			let collection = <CollectionHandle<T>>::try_get(collection_id)?;427			collection.check_is_internal()?;428429			<PalletCommon<T>>::toggle_allowlist(430				&collection,431				&sender,432				&address,433				true,434			)?;435436			Self::deposit_event(Event::<T>::AllowListAddressAdded(437				collection_id,438				address439			));440441			Ok(())442		}443444		/// Remove an address from allow list.445		///446		/// # Permissions447		///448		/// * Collection owner449		/// * Collection admin450		///451		/// # Arguments452		///453		/// * `collection_id`: ID of the modified collection.454		/// * `address`: ID of the address to be removed from the allowlist.455		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]456		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{457458			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);459			let collection = <CollectionHandle<T>>::try_get(collection_id)?;460			collection.check_is_internal()?;461462			<PalletCommon<T>>::toggle_allowlist(463				&collection,464				&sender,465				&address,466				false,467			)?;468469			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(470				collection_id,471				address472			));473474			Ok(())475		}476477		/// Change the owner of the collection.478		///479		/// # Permissions480		///481		/// * Collection owner482		///483		/// # Arguments484		///485		/// * `collection_id`: ID of the modified collection.486		/// * `new_owner`: ID of the account that will become the owner.487		#[weight = <SelfWeightOf<T>>::change_collection_owner()]488		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {489490			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);491492			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;493			target_collection.check_is_internal()?;494			target_collection.check_is_owner(&sender)?;495496			target_collection.owner = new_owner.clone();497			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(498				collection_id,499				new_owner500			));501502			target_collection.save()503		}504505		/// Add an admin to a collection.506		///507		/// NFT Collection can be controlled by multiple admin addresses508		/// (some which can also be servers, for example). Admins can issue509		/// and burn NFTs, as well as add and remove other admins,510		/// but cannot change NFT or Collection ownership.511		///512		/// # Permissions513		///514		/// * Collection owner515		/// * Collection admin516		///517		/// # Arguments518		///519		/// * `collection_id`: ID of the Collection to add an admin for.520		/// * `new_admin`: Address of new admin to add.521		#[weight = <SelfWeightOf<T>>::add_collection_admin()]522		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {523			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);524			let collection = <CollectionHandle<T>>::try_get(collection_id)?;525			collection.check_is_internal()?;526527			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(528				collection_id,529				new_admin_id.clone()530			));531532			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)533		}534535		/// Remove admin of a collection.536		///537		/// An admin address can remove itself. List of admins may become empty,538		/// in which case only Collection Owner will be able to add an Admin.539		///540		/// # Permissions541		///542		/// * Collection owner543		/// * Collection admin544		///545		/// # Arguments546		///547		/// * `collection_id`: ID of the collection to remove the admin for.548		/// * `account_id`: Address of the admin to remove.549		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]550		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {551			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);552			let collection = <CollectionHandle<T>>::try_get(collection_id)?;553			collection.check_is_internal()?;554555			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(556				collection_id,557				account_id.clone()558			));559560			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)561		}562563		/// Set (invite) a new collection sponsor.564		///565		/// If successful, confirmation from the sponsor-to-be will be pending.566		///567		/// # Permissions568		///569		/// * Collection owner570		/// * Collection admin571		///572		/// # Arguments573		///574		/// * `collection_id`: ID of the modified collection.575		/// * `new_sponsor`: ID of the account of the sponsor-to-be.576		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]577		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {578			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);579580			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;581			target_collection.check_is_owner_or_admin(&sender)?;582			target_collection.check_is_internal()?;583584			target_collection.set_sponsor(new_sponsor.clone())?;585586			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(587				collection_id,588				new_sponsor589			));590591			target_collection.save()592		}593594		/// Confirm own sponsorship of a collection, becoming the sponsor.595		///596		/// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].597		/// Sponsor can pay the fees of a transaction instead of the sender,598		/// but only within specified limits.599		///600		/// # Permissions601		///602		/// * Sponsor-to-be603		///604		/// # Arguments605		///606		/// * `collection_id`: ID of the collection with the pending sponsor.607		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]608		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {609			let sender = ensure_signed(origin)?;610611			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;612			target_collection.check_is_internal()?;613			ensure!(614				target_collection.confirm_sponsorship(&sender)?,615				Error::<T>::ConfirmUnsetSponsorFail616			);617618			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(619				collection_id,620				sender621			));622623			target_collection.save()624		}625626		/// Remove a collection's a sponsor, making everyone pay for their own transactions.627		///628		/// # Permissions629		///630		/// * Collection owner631		///632		/// # Arguments633		///634		/// * `collection_id`: ID of the collection with the sponsor to remove.635		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]636		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {637			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);638639			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;640			target_collection.check_is_internal()?;641			target_collection.check_is_owner(&sender)?;642643			target_collection.sponsorship = SponsorshipState::Disabled;644645			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(646				collection_id647			));648			target_collection.save()649		}650651		/// Mint an item within a collection.652		///653		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].654		///655		/// # Permissions656		///657		/// * Collection owner658		/// * Collection admin659		/// * Anyone if660		///     * Allow List is enabled, and661		///     * Address is added to allow list, and662		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])663		///664		/// # Arguments665		///666		/// * `collection_id`: ID of the collection to which an item would belong.667		/// * `owner`: Address of the initial owner of the item.668		/// * `data`: Token data describing the item to store on chain.669		#[weight = T::CommonWeightInfo::create_item()]670		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {671			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);672			let budget = budget::Value::new(NESTING_BUDGET);673674			dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))675		}676677		/// Create multiple items within a collection.678		///679		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].680		///681		/// # Permissions682		///683		/// * Collection owner684		/// * Collection admin685		/// * Anyone if686		///     * Allow List is enabled, and687		///     * Address is added to the allow list, and688		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])689		///690		/// # Arguments691		///692		/// * `collection_id`: ID of the collection to which the tokens would belong.693		/// * `owner`: Address of the initial owner of the tokens.694		/// * `items_data`: Vector of data describing each item to be created.695		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]696		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {697			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);698			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);699			let budget = budget::Value::new(NESTING_BUDGET);700701			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))702		}703704		/// Add or change collection properties.705		///706		/// # Permissions707		///708		/// * Collection owner709		/// * Collection admin710		///711		/// # Arguments712		///713		/// * `collection_id`: ID of the modified collection.714		/// * `properties`: Vector of key-value pairs stored as the collection's metadata.715		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.716		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]717		pub fn set_collection_properties(718			origin,719			collection_id: CollectionId,720			properties: Vec<Property>721		) -> DispatchResultWithPostInfo {722			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);723724			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);725726			dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))727		}728729		/// Delete specified collection properties.730		///731		/// # Permissions732		///733		/// * Collection Owner734		/// * Collection Admin735		///736		/// # Arguments737		///738		/// * `collection_id`: ID of the modified collection.739		/// * `property_keys`: Vector of keys of the properties to be deleted.740		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.741		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]742		pub fn delete_collection_properties(743			origin,744			collection_id: CollectionId,745			property_keys: Vec<PropertyKey>,746		) -> DispatchResultWithPostInfo {747			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);748749			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);750751			dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))752		}753754		/// Add or change token properties according to collection's permissions.755		/// Currently properties only work with NFTs.756		///757		/// # Permissions758		///759		/// * Depends on collection's token property permissions and specified property mutability:760		/// 	* Collection owner761		/// 	* Collection admin762		/// 	* Token owner763		///764		/// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].765		///766		/// # Arguments767		///768		/// * `collection_id: ID of the collection to which the token belongs.769		/// * `token_id`: ID of the modified token.770		/// * `properties`: Vector of key-value pairs stored as the token's metadata.771		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.772		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]773		pub fn set_token_properties(774			origin,775			collection_id: CollectionId,776			token_id: TokenId,777			properties: Vec<Property>778		) -> DispatchResultWithPostInfo {779			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);780781			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);782			let budget = budget::Value::new(NESTING_BUDGET);783784			dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))785		}786787		/// Delete specified token properties. Currently properties only work with NFTs.788		///789		/// # Permissions790		///791		/// * Depends on collection's token property permissions and specified property mutability:792		/// 	* Collection owner793		/// 	* Collection admin794		/// 	* Token owner795		///796		/// # Arguments797		///798		/// * `collection_id`: ID of the collection to which the token belongs.799		/// * `token_id`: ID of the modified token.800		/// * `property_keys`: Vector of keys of the properties to be deleted.801		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.802		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]803		pub fn delete_token_properties(804			origin,805			collection_id: CollectionId,806			token_id: TokenId,807			property_keys: Vec<PropertyKey>808		) -> DispatchResultWithPostInfo {809			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);810811			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);812			let budget = budget::Value::new(NESTING_BUDGET);813814			dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))815		}816817		/// Add or change token property permissions of a collection.818		///819		/// Without a permission for a particular key, a property with that key820		/// cannot be created in a token.821		///822		/// # Permissions823		///824		/// * Collection owner825		/// * Collection admin826		///827		/// # Arguments828		///829		/// * `collection_id`: ID of the modified collection.830		/// * `property_permissions`: Vector of permissions for property keys.831		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.832		#[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]833		pub fn set_token_property_permissions(834			origin,835			collection_id: CollectionId,836			property_permissions: Vec<PropertyKeyPermission>,837		) -> DispatchResultWithPostInfo {838			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);839840			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);841842			dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))843		}844845		/// Create multiple items within a collection with explicitly specified initial parameters.846		///847		/// # Permissions848		///849		/// * Collection owner850		/// * Collection admin851		/// * Anyone if852		///     * Allow List is enabled, and853		///     * Address is added to allow list, and854		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])855		///856		/// # Arguments857		///858		/// * `collection_id`: ID of the collection to which the tokens would belong.859		/// * `data`: Explicit item creation data.860		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]861		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {862			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);863			let budget = budget::Value::new(NESTING_BUDGET);864865			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))866		}867868		/// Completely allow or disallow transfers for a particular collection.869		///870		/// # Permissions871		///872		/// * Collection owner873		///874		/// # Arguments875		///876		/// * `collection_id`: ID of the collection.877		/// * `value`: New value of the flag, are transfers allowed?878		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]879		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {880			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);881			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;882			target_collection.check_is_internal()?;883			target_collection.check_is_owner(&sender)?;884885			// =========886887			target_collection.limits.transfers_enabled = Some(value);888			target_collection.save()889		}890891		/// Destroy an item.892		///893		/// # Permissions894		///895		/// * Collection owner896		/// * Collection admin897		/// * Current item owner898		///899		/// # Arguments900		///901		/// * `collection_id`: ID of the collection to which the item belongs.902		/// * `item_id`: ID of item to burn.903		/// * `value`: Number of pieces of the item to destroy.904		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.905		///     * Fungible Mode: The desired number of pieces to burn.906		///     * Re-Fungible Mode: The desired number of pieces to burn.907		#[weight = T::CommonWeightInfo::burn_item()]908		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {909			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);910911			let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;912			if value == 1 {913				<NftTransferBasket<T>>::remove(collection_id, item_id);914				<NftApproveBasket<T>>::remove(collection_id, item_id);915			}916			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?917			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());918			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));919			Ok(post_info)920		}921922		/// Destroy a token on behalf of the owner as a non-owner account.923		///924		/// See also: [`approve`][`Pallet::approve`].925		///926		/// After this method executes, one approval is removed from the total so that927		/// the approved address will not be able to transfer this item again from this owner.928		///929		/// # Permissions930		///931		/// * Collection owner932		/// * Collection admin933		/// * Current token owner934		/// * Address approved by current item owner935		///936		/// # Arguments937		///938		/// * `from`: The owner of the burning item.939		/// * `collection_id`: ID of the collection to which the item belongs.940		/// * `item_id`: ID of item to burn.941		/// * `value`: Number of pieces to burn.942		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.943		///     * Fungible Mode: The desired number of pieces to burn.944		///     * Re-Fungible Mode: The desired number of pieces to burn.945		#[weight = T::CommonWeightInfo::burn_from()]946		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {947			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);948			let budget = budget::Value::new(NESTING_BUDGET);949950			dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))951		}952953		/// Change ownership of the token.954		///955		/// # Permissions956		///957		/// * Collection owner958		/// * Collection admin959		/// * Current token owner960		///961		/// # Arguments962		///963		/// * `recipient`: Address of token recipient.964		/// * `collection_id`: ID of the collection the item belongs to.965		/// * `item_id`: ID of the item.966		///     * Non-Fungible Mode: Required.967		///     * Fungible Mode: Ignored.968		///     * Re-Fungible Mode: Required.969		///970		/// * `value`: Amount to transfer.971		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.972		///     * Fungible Mode: The desired number of pieces to transfer.973		///     * Re-Fungible Mode: The desired number of pieces to transfer.974		#[weight = T::CommonWeightInfo::transfer()]975		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {976			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);977			let budget = budget::Value::new(NESTING_BUDGET);978979			dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))980		}981982		/// Allow a non-permissioned address to transfer or burn an item.983		///984		/// # Permissions985		///986		/// * Collection owner987		/// * Collection admin988		/// * Current item owner989		///990		/// # Arguments991		///992		/// * `spender`: Account to be approved to make specific transactions on non-owned tokens.993		/// * `collection_id`: ID of the collection the item belongs to.994		/// * `item_id`: ID of the item transactions on which are now approved.995		/// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).996		/// Set to 0 to revoke the approval.997		#[weight = T::CommonWeightInfo::approve()]998		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {999			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10001001			dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))1002		}10031004		/// Change ownership of an item on behalf of the owner as a non-owner account.1005		///1006		/// See the [`approve`][`Pallet::approve`] method for additional information.1007		///1008		/// After this method executes, one approval is removed from the total so that1009		/// the approved address will not be able to transfer this item again from this owner.1010		///1011		/// # Permissions1012		///1013		/// * Collection owner1014		/// * Collection admin1015		/// * Current item owner1016		/// * Address approved by current item owner1017		///1018		/// # Arguments1019		///1020		/// * `from`: Address that currently owns the token.1021		/// * `recipient`: Address of the new token-owner-to-be.1022		/// * `collection_id`: ID of the collection the item.1023		/// * `item_id`: ID of the item to be transferred.1024		/// * `value`: Amount to transfer.1025		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1026		///     * Fungible Mode: The desired number of pieces to transfer.1027		///     * Re-Fungible Mode: The desired number of pieces to transfer.1028		#[weight = T::CommonWeightInfo::transfer_from()]1029		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {1030			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1031			let budget = budget::Value::new(NESTING_BUDGET);10321033			dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))1034		}10351036		/// Set specific limits of a collection. Empty, or None fields mean chain default.1037		///1038		/// # Permissions1039		///1040		/// * Collection owner1041		/// * Collection admin1042		///1043		/// # Arguments1044		///1045		/// * `collection_id`: ID of the modified collection.1046		/// * `new_limit`: New limits of the collection. Fields that are not set (None)1047		/// will not overwrite the old ones.1048		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1049		pub fn set_collection_limits(1050			origin,1051			collection_id: CollectionId,1052			new_limit: CollectionLimits,1053		) -> DispatchResult {1054			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1055			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1056			target_collection.check_is_internal()?;1057			target_collection.check_is_owner_or_admin(&sender)?;1058			let old_limit = &target_collection.limits;10591060			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10611062			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1063				collection_id1064			));10651066			target_collection.save()1067		}10681069		/// Set specific permissions of a collection. Empty, or None fields mean chain default.1070		///1071		/// # Permissions1072		///1073		/// * Collection owner1074		/// * Collection admin1075		///1076		/// # Arguments1077		///1078		/// * `collection_id`: ID of the modified collection.1079		/// * `new_permission`: New permissions of the collection. Fields that are not set (None)1080		/// will not overwrite the old ones.1081		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1082		pub fn set_collection_permissions(1083			origin,1084			collection_id: CollectionId,1085			new_permission: CollectionPermissions,1086		) -> DispatchResult {1087			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1088			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1089			target_collection.check_is_internal()?;1090			target_collection.check_is_owner_or_admin(&sender)?;1091			let old_limit = &target_collection.permissions;10921093			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;10941095			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(1096				collection_id1097			));10981099			target_collection.save()1100		}11011102		/// Re-partition a refungible token, while owning all of its parts/pieces.1103		///1104		/// # Permissions1105		///1106		/// * Token owner (must own every part)1107		///1108		/// # Arguments1109		///1110		/// * `collection_id`: ID of the collection the RFT belongs to.1111		/// * `token_id`: ID of the RFT.1112		/// * `amount`: New number of parts/pieces into which the token shall be partitioned.1113		#[weight = T::RefungibleExtensionsWeightInfo::repartition()]1114		pub fn repartition(1115			origin,1116			collection_id: CollectionId,1117			token_id: TokenId,1118			amount: u128,1119		) -> DispatchResultWithPostInfo {1120			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1121			dispatch_tx::<T, _>(collection_id, |d| {1122				if let Some(refungible_extensions) = d.refungible_extensions() {1123					refungible_extensions.repartition(&sender, token_id, amount)1124				} else {1125					fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1126				}1127			})1128		}1129	}1130}11311132impl<T: Config> Pallet<T> {1133	/// Force set `sponsor` for `collection`.1134	///1135	/// Differs from [`set_collection_sponsor`][`Pallet::set_collection_sponsor`] in that confirmation1136	/// from the `sponsor` is not required.1137	///1138	/// # Arguments1139	///1140	/// * `sponsor`: ID of the account of the sponsor-to-be.1141	/// * `collection_id`: ID of the modified collection.1142	pub fn force_set_sponsor(sponsor: T::AccountId, collection_id: CollectionId) -> DispatchResult {1143		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1144		target_collection.check_is_internal()?;1145		target_collection.set_sponsor(sponsor.clone())?;11461147		Self::deposit_event(Event::<T>::CollectionSponsorSet(1148			collection_id,1149			sponsor.clone(),1150		));11511152		ensure!(1153			target_collection.confirm_sponsorship(&sponsor)?,1154			Error::<T>::ConfirmUnsetSponsorFail1155		);11561157		Self::deposit_event(Event::<T>::SponsorshipConfirmed(collection_id, sponsor));11581159		target_collection.save()1160	}11611162	/// Force remove `sponsor` for `collection`.1163	///1164	/// Differs from `remove_sponsor` in that1165	/// it doesn't require consent from the `owner` of the collection.1166	///1167	/// # Arguments1168	///1169	/// * `collection_id`: ID of the modified collection.1170	pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult {1171		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1172		target_collection.check_is_internal()?;1173		target_collection.sponsorship = SponsorshipState::Disabled;11741175		Self::deposit_event(Event::<T>::CollectionSponsorRemoved(collection_id));11761177		target_collection.save()1178	}11791180	#[inline(always)]1181	pub(crate) fn destroy_collection_internal(1182		sender: T::CrossAccountId,1183		collection_id: CollectionId,1184	) -> DispatchResult {1185		let collection = <CollectionHandle<T>>::try_get(collection_id)?;1186		collection.check_is_internal()?;11871188		T::CollectionDispatch::destroy(sender, collection)?;11891190		// TODO: basket cleanup should be moved elsewhere1191		// Maybe runtime dispatch.rs should perform it?11921193		let _ = <NftTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1194		let _ = <FungibleTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1195		let _ = <ReFungibleTransferBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);11961197		let _ = <NftApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1198		let _ = <FungibleApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1199		let _ = <RefungibleApproveBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);12001201		Ok(())1202	}1203}