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

difftreelog

source

pallets/unique/src/lib.rs40.4 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	transactional,82	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},83	BoundedVec,84};85use scale_info::TypeInfo;86use frame_system::{self as system, ensure_signed};87use sp_runtime::{sp_std::prelude::Vec};88use up_data_structs::{89	MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,90	CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId,91	SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,92	PropertyKeyPermission,93};94use pallet_evm::account::CrossAccountId;95use pallet_common::{96	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,97	dispatch::CollectionDispatch, RefungibleExtensionsWeightInfo,98};99pub mod eth;100101#[cfg(feature = "runtime-benchmarks")]102mod benchmarking;103pub mod weights;104use weights::WeightInfo;105106/// Maximum number of levels of depth in the token nesting tree.107pub const NESTING_BUDGET: u32 = 5;108109decl_error! {110	/// Errors for the common Unique transactions.111	pub enum Error for Module<T: Config> {112		/// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].113		CollectionDecimalPointLimitExceeded,114		/// This address is not set as sponsor, use setCollectionSponsor first.115		ConfirmUnsetSponsorFail,116		/// Length of items properties must be greater than 0.117		EmptyArgument,118		/// Repertition is only supported by refungible collection.119		RepartitionCalledOnNonRefungibleCollection,120	}121}122123/// Configuration trait of this pallet.124pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {125	/// Overarching event type.126	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;127128	/// Weight information for extrinsics in this pallet.129	type WeightInfo: WeightInfo;130131	/// Weight information for common pallet operations.132	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;133134	/// Weight info information for extra refungible pallet operations.135	type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;136}137138decl_event! {139	pub enum Event<T>140	where141		<T as frame_system::Config>::AccountId,142		<T as pallet_evm::account::Config>::CrossAccountId,143	{144		/// Collection sponsor was removed145		///146		/// # Arguments147		/// * collection_id: ID of the affected collection.148		CollectionSponsorRemoved(CollectionId),149150		/// Collection admin was added151		///152		/// # Arguments153		/// * collection_id: ID of the affected collection.154		/// * admin: Admin address.155		CollectionAdminAdded(CollectionId, CrossAccountId),156157		/// Collection owned was changed158		///159		/// # Arguments160		/// * collection_id: ID of the affected collection.161		/// * owner: New owner address.162		CollectionOwnedChanged(CollectionId, AccountId),163164		/// Collection sponsor was set165		///166		/// # Arguments167		/// * collection_id: ID of the affected collection.168		/// * owner: New sponsor address.169		CollectionSponsorSet(CollectionId, AccountId),170171		/// New sponsor was confirm172		///173		/// # Arguments174		/// * collection_id: ID of the affected collection.175		/// * sponsor: New sponsor address.176		SponsorshipConfirmed(CollectionId, AccountId),177178		/// Collection admin was removed179		///180		/// # Arguments181		/// * collection_id: ID of the affected collection.182		/// * admin: Removed admin address.183		CollectionAdminRemoved(CollectionId, CrossAccountId),184185		/// Address was removed from the allow list186		///187		/// # Arguments188		/// * collection_id: ID of the affected collection.189		/// * user: Address of the removed account.190		AllowListAddressRemoved(CollectionId, CrossAccountId),191192		/// Address was added to the allow list193		///194		/// # Arguments195		/// * collection_id: ID of the affected collection.196		/// * user: Address of the added account.197		AllowListAddressAdded(CollectionId, CrossAccountId),198199		/// Collection limits were set200		///201		/// # Arguments202		/// * collection_id: ID of the affected collection.203		CollectionLimitSet(CollectionId),204205		/// Collection permissions were set206		///207		/// # Arguments208		/// * collection_id: ID of the affected collection.209		CollectionPermissionSet(CollectionId),210	}211}212213type SelfWeightOf<T> = <T as Config>::WeightInfo;214215// # Used definitions216//217// ## User control levels218//219// chain-controlled - key is uncontrolled by user220//                    i.e autoincrementing index221//                    can use non-cryptographic hash222// real - key is controlled by user223//        but it is hard to generate enough colliding values, i.e owner of signed txs224//        can use non-cryptographic hash225// controlled - key is completly controlled by users226//              i.e maps with mutable keys227//              should use cryptographic hash228//229// ## User control level downgrade reasons230//231// ?1 - chain-controlled -> controlled232//      collections/tokens can be destroyed, resulting in massive holes233// ?2 - chain-controlled -> controlled234//      same as ?1, but can be only added, resulting in easier exploitation235// ?3 - real -> controlled236//      no confirmation required, so addresses can be easily generated237decl_storage! {238	trait Store for Module<T: Config> as Unique {239240		//#region Private members241		/// Used for migrations242		ChainVersion: u64;243		//#endregion244245		//#region Tokens transfer sponosoring rate limit baskets246		/// (Collection id (controlled?2), who created (real))247		/// TODO: Off chain worker should remove from this map when collection gets removed248		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;249		/// Collection id (controlled?2), token id (controlled?2)250		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;251		/// Collection id (controlled?2), owning user (real)252		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;253		/// Collection id (controlled?2), token id (controlled?2)254		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>;255		//#endregion256257		/// Variable metadata sponsoring258		/// Collection id (controlled?2), token id (controlled?2)259		#[deprecated]260		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;261		/// Last sponsoring of token property setting // todo:doc rephrase this and the following262		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;263264		/// Last sponsoring of NFT approval in a collection265		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;266		/// Last sponsoring of fungible tokens approval in a collection267		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;268		/// Last sponsoring of RFT approval in a collection269		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>;270	}271}272273decl_module! {274	/// Type alias to Pallet, to be used by construct_runtime.275	pub struct Module<T: Config> for enum Call276	where277		origin: T::Origin278	{279		type Error = Error<T>;280281		fn deposit_event() = default;282283		fn on_initialize(_now: T::BlockNumber) -> Weight {284			0285		}286287		fn on_runtime_upgrade() -> Weight {288			let limit = None;289290			<VariableMetaDataBasket<T>>::remove_all(limit);291292			0293		}294295		/// Create a collection of tokens.296		///297		/// Each Token may have multiple properties encoded as an array of bytes298		/// of certain length. The initial owner of the collection is set299		/// to the address that signed the transaction and can be changed later.300		///301		/// Prefer the more advanced [`create_collection_ex`][`Pallet::create_collection_ex`] instead.302		///303		/// # Permissions304		///305		/// * Anyone - becomes the owner of the new collection.306		///307		/// # Arguments308		///309		/// * `collection_name`: Wide-character string with collection name310		/// (limit [`MAX_COLLECTION_NAME_LENGTH`]).311		/// * `collection_description`: Wide-character string with collection description312		/// (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).313		/// * `token_prefix`: Byte string containing the token prefix to mark a collection314		/// to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).315		/// * `mode`: Type of items stored in the collection and type dependent data.316		// returns collection ID317		#[weight = <SelfWeightOf<T>>::create_collection()]318		#[transactional]319		#[deprecated(note = "`create_collection_ex` is more up-to-date and advanced, prefer it instead")]320		pub fn create_collection(321			origin,322			collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,323			collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,324			token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,325			mode: CollectionMode326		) -> DispatchResult {327			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {328				name: collection_name,329				description: collection_description,330				token_prefix,331				mode,332				..Default::default()333			};334			Self::create_collection_ex(origin, data)335		}336337		/// Create a collection with explicit parameters.338		///339		/// Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.340		///341		/// # Permissions342		///343		/// * Anyone - becomes the owner of the new collection.344		///345		/// # Arguments346		///347		/// * `data`: Explicit data of a collection used for its creation.348		#[weight = <SelfWeightOf<T>>::create_collection()]349		#[transactional]350		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {351			let sender = ensure_signed(origin)?;352353			// =========354355			T::CollectionDispatch::create(T::CrossAccountId::from_sub(sender), data)?;356357			Ok(())358		}359360		/// Destroy a collection if no tokens exist within.361		///362		/// # Permissions363		///364		/// * Collection owner365		///366		/// # Arguments367		///368		/// * `collection_id`: Collection to destroy.369		#[weight = <SelfWeightOf<T>>::destroy_collection()]370		#[transactional]371		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {372			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);373			let collection = <CollectionHandle<T>>::try_get(collection_id)?;374			collection.check_is_internal()?;375376			// =========377378			T::CollectionDispatch::destroy(sender, collection)?;379380			<NftTransferBasket<T>>::remove_prefix(collection_id, None);381			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);382			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);383384			<NftApproveBasket<T>>::remove_prefix(collection_id, None);385			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);386			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);387388			Ok(())389		}390391		/// Add an address to allow list.392		///393		/// # Permissions394		///395		/// * Collection owner396		/// * Collection admin397		///398		/// # Arguments399		///400		/// * `collection_id`: ID of the modified collection.401		/// * `address`: ID of the address to be added to the allowlist.402		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]403		#[transactional]404		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{405406			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);407			let collection = <CollectionHandle<T>>::try_get(collection_id)?;408			collection.check_is_internal()?;409410			<PalletCommon<T>>::toggle_allowlist(411				&collection,412				&sender,413				&address,414				true,415			)?;416417			Self::deposit_event(Event::<T>::AllowListAddressAdded(418				collection_id,419				address420			));421422			Ok(())423		}424425		/// Remove an address from allow list.426		///427		/// # Permissions428		///429		/// * Collection owner430		/// * Collection admin431		///432		/// # Arguments433		///434		/// * `collection_id`: ID of the modified collection.435		/// * `address`: ID of the address to be removed from the allowlist.436		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]437		#[transactional]438		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{439440			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);441			let collection = <CollectionHandle<T>>::try_get(collection_id)?;442			collection.check_is_internal()?;443444			<PalletCommon<T>>::toggle_allowlist(445				&collection,446				&sender,447				&address,448				false,449			)?;450451			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(452				collection_id,453				address454			));455456			Ok(())457		}458459		/// Change the owner of the collection.460		///461		/// # Permissions462		///463		/// * Collection owner464		///465		/// # Arguments466		///467		/// * `collection_id`: ID of the modified collection.468		/// * `new_owner`: ID of the account that will become the owner.469		#[weight = <SelfWeightOf<T>>::change_collection_owner()]470		#[transactional]471		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {472473			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);474475			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;476			target_collection.check_is_internal()?;477			target_collection.check_is_owner(&sender)?;478479			target_collection.owner = new_owner.clone();480			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(481				collection_id,482				new_owner483			));484485			target_collection.save()486		}487488		/// Add an admin to a collection.489		///490		/// NFT Collection can be controlled by multiple admin addresses491		/// (some which can also be servers, for example). Admins can issue492		/// and burn NFTs, as well as add and remove other admins,493		/// but cannot change NFT or Collection ownership.494		///495		/// # Permissions496		///497		/// * Collection owner498		/// * Collection admin499		///500		/// # Arguments501		///502		/// * `collection_id`: ID of the Collection to add an admin for.503		/// * `new_admin`: Address of new admin to add.504		#[weight = <SelfWeightOf<T>>::add_collection_admin()]505		#[transactional]506		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin: T::CrossAccountId) -> DispatchResult {507			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);508			let collection = <CollectionHandle<T>>::try_get(collection_id)?;509			collection.check_is_internal()?;510511			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(512				collection_id,513				new_admin.clone()514			));515516			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin, true)517		}518519		/// Remove admin of a collection.520		///521		/// An admin address can remove itself. List of admins may become empty,522		/// in which case only Collection Owner will be able to add an Admin.523		///524		/// # Permissions525		///526		/// * Collection owner527		/// * Collection admin528		///529		/// # Arguments530		///531		/// * `collection_id`: ID of the collection to remove the admin for.532		/// * `account_id`: Address of the admin to remove.533		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]534		#[transactional]535		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {536			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);537			let collection = <CollectionHandle<T>>::try_get(collection_id)?;538			collection.check_is_internal()?;539540			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(541				collection_id,542				account_id.clone()543			));544545			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)546		}547548		/// Set (invite) a new collection sponsor.549		///550		/// If successful, confirmation from the sponsor-to-be will be pending.551		///552		/// # Permissions553		///554		/// * Collection owner555		/// * Collection admin556		///557		/// # Arguments558		///559		/// * `collection_id`: ID of the modified collection.560		/// * `new_sponsor`: ID of the account of the sponsor-to-be.561		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]562		#[transactional]563		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {564			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);565566			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;567			target_collection.check_is_owner_or_admin(&sender)?;568			target_collection.check_is_internal()?;569570			target_collection.set_sponsor(new_sponsor.clone())?;571572			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(573				collection_id,574				new_sponsor575			));576577			target_collection.save()578		}579580		/// Confirm own sponsorship of a collection, becoming the sponsor.581		///582		/// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].583		/// Sponsor can pay the fees of a transaction instead of the sender,584		/// but only within specified limits.585		///586		/// # Permissions587		///588		/// * Sponsor-to-be589		///590		/// # Arguments591		///592		/// * `collection_id`: ID of the collection with the pending sponsor.593		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]594		#[transactional]595		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {596			let sender = ensure_signed(origin)?;597598			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;599			target_collection.check_is_internal()?;600			ensure!(601				target_collection.confirm_sponsorship(&sender)?,602				Error::<T>::ConfirmUnsetSponsorFail603			);604605			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(606				collection_id,607				sender608			));609610			target_collection.save()611		}612613		/// Remove a collection's a sponsor, making everyone pay for their own transactions.614		///615		/// # Permissions616		///617		/// * Collection owner618		///619		/// # Arguments620		///621		/// * `collection_id`: ID of the collection with the sponsor to remove.622		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]623		#[transactional]624		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {625			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);626627			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;628			target_collection.check_is_internal()?;629			target_collection.check_is_owner(&sender)?;630631			target_collection.sponsorship = SponsorshipState::Disabled;632633			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(634				collection_id635			));636			target_collection.save()637		}638639		/// Mint an item within a collection.640		///641		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].642		///643		/// # Permissions644		///645		/// * Collection owner646		/// * Collection admin647		/// * Anyone if648		///     * Allow List is enabled, and649		///     * Address is added to allow list, and650		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])651		///652		/// # Arguments653		///654		/// * `collection_id`: ID of the collection to which an item would belong.655		/// * `owner`: Address of the initial owner of the item.656		/// * `data`: Token data describing the item to store on chain.657		#[weight = T::CommonWeightInfo::create_item()]658		#[transactional]659		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {660			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);661			let budget = budget::Value::new(NESTING_BUDGET);662663			dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))664		}665666		/// Create multiple items within a collection.667		///668		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].669		///670		/// # Permissions671		///672		/// * Collection owner673		/// * Collection admin674		/// * Anyone if675		///     * Allow List is enabled, and676		///     * Address is added to the allow list, and677		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])678		///679		/// # Arguments680		///681		/// * `collection_id`: ID of the collection to which the tokens would belong.682		/// * `owner`: Address of the initial owner of the tokens.683		/// * `items_data`: Vector of data describing each item to be created.684		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]685		#[transactional]686		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {687			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);688			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);689			let budget = budget::Value::new(NESTING_BUDGET);690691			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))692		}693694		/// Add or change collection properties.695		///696		/// # Permissions697		///698		/// * Collection owner699		/// * Collection admin700		///701		/// # Arguments702		///703		/// * `collection_id`: ID of the modified collection.704		/// * `properties`: Vector of key-value pairs stored as the collection's metadata.705		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.706		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]707		#[transactional]708		pub fn set_collection_properties(709			origin,710			collection_id: CollectionId,711			properties: Vec<Property>712		) -> DispatchResultWithPostInfo {713			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);714715			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);716717			dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))718		}719720		/// Delete specified collection properties.721		///722		/// # Permissions723		///724		/// * Collection Owner725		/// * Collection Admin726		///727		/// # Arguments728		///729		/// * `collection_id`: ID of the modified collection.730		/// * `property_keys`: Vector of keys of the properties to be deleted.731		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.732		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]733		#[transactional]734		pub fn delete_collection_properties(735			origin,736			collection_id: CollectionId,737			property_keys: Vec<PropertyKey>,738		) -> DispatchResultWithPostInfo {739			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);740741			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);742743			dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))744		}745746		/// Add or change token properties according to collection's permissions.747		/// Currently properties only work with NFTs.748		///749		/// # Permissions750		///751		/// * Depends on collection's token property permissions and specified property mutability:752		/// 	* Collection owner753		/// 	* Collection admin754		/// 	* Token owner755		///756		/// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].757		///758		/// # Arguments759		///760		/// * `collection_id: ID of the collection to which the token belongs.761		/// * `token_id`: ID of the modified token.762		/// * `properties`: Vector of key-value pairs stored as the token's metadata.763		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.764		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]765		#[transactional]766		pub fn set_token_properties(767			origin,768			collection_id: CollectionId,769			token_id: TokenId,770			properties: Vec<Property>771		) -> DispatchResultWithPostInfo {772			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);773774			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);775			let budget = budget::Value::new(NESTING_BUDGET);776777			dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))778		}779780		/// Delete specified token properties. Currently properties only work with NFTs.781		///782		/// # Permissions783		///784		/// * Depends on collection's token property permissions and specified property mutability:785		/// 	* Collection owner786		/// 	* Collection admin787		/// 	* Token owner788		///789		/// # Arguments790		///791		/// * `collection_id`: ID of the collection to which the token belongs.792		/// * `token_id`: ID of the modified token.793		/// * `property_keys`: Vector of keys of the properties to be deleted.794		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.795		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]796		#[transactional]797		pub fn delete_token_properties(798			origin,799			collection_id: CollectionId,800			token_id: TokenId,801			property_keys: Vec<PropertyKey>802		) -> DispatchResultWithPostInfo {803			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);804805			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);806			let budget = budget::Value::new(NESTING_BUDGET);807808			dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))809		}810811		/// Add or change token property permissions of a collection.812		///813		/// Without a permission for a particular key, a property with that key814		/// cannot be created in a token.815		///816		/// # Permissions817		///818		/// * Collection owner819		/// * Collection admin820		///821		/// # Arguments822		///823		/// * `collection_id`: ID of the modified collection.824		/// * `property_permissions`: Vector of permissions for property keys.825		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.826		#[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]827		#[transactional]828		pub fn set_token_property_permissions(829			origin,830			collection_id: CollectionId,831			property_permissions: Vec<PropertyKeyPermission>,832		) -> DispatchResultWithPostInfo {833			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);834835			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);836837			dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))838		}839840		/// Create multiple items within a collection with explicitly specified initial parameters.841		///842		/// # Permissions843		///844		/// * Collection owner845		/// * Collection admin846		/// * Anyone if847		///     * Allow List is enabled, and848		///     * Address is added to allow list, and849		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])850		///851		/// # Arguments852		///853		/// * `collection_id`: ID of the collection to which the tokens would belong.854		/// * `data`: Explicit item creation data.855		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]856		#[transactional]857		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {858			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);859			let budget = budget::Value::new(NESTING_BUDGET);860861			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))862		}863864		/// Completely allow or disallow transfers for a particular collection.865		///866		/// # Permissions867		///868		/// * Collection owner869		///870		/// # Arguments871		///872		/// * `collection_id`: ID of the collection.873		/// * `value`: New value of the flag, are transfers allowed?874		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]875		#[transactional]876		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {877			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);878			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;879			target_collection.check_is_internal()?;880			target_collection.check_is_owner(&sender)?;881882			// =========883884			target_collection.limits.transfers_enabled = Some(value);885			target_collection.save()886		}887888		/// Destroy an item.889		///890		/// # Permissions891		///892		/// * Collection owner893		/// * Collection admin894		/// * Current item owner895		///896		/// # Arguments897		///898		/// * `collection_id`: ID of the collection to which the item belongs.899		/// * `item_id`: ID of item to burn.900		/// * `value`: Number of pieces of the item to destroy.901		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.902		///     * Fungible Mode: The desired number of pieces to burn.903		///     * Re-Fungible Mode: The desired number of pieces to burn.904		#[weight = T::CommonWeightInfo::burn_item()]905		#[transactional]906		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {907			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);908909			let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;910			if value == 1 {911				<NftTransferBasket<T>>::remove(collection_id, item_id);912				<NftApproveBasket<T>>::remove(collection_id, item_id);913			}914			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?915			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());916			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));917			Ok(post_info)918		}919920		/// Destroy a token on behalf of the owner as a non-owner account.921		///922		/// See also: [`approve`][`Pallet::approve`].923		///924		/// After this method executes, one approval is removed from the total so that925		/// the approved address will not be able to transfer this item again from this owner.926		///927		/// # Permissions928		///929		/// * Collection owner930		/// * Collection admin931		/// * Current token owner932		/// * Address approved by current item owner933		///934		/// # Arguments935		///936		/// * `from`: The owner of the burning item.937		/// * `collection_id`: ID of the collection to which the item belongs.938		/// * `item_id`: ID of item to burn.939		/// * `value`: Number of pieces to burn.940		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.941		///     * Fungible Mode: The desired number of pieces to burn.942		///     * Re-Fungible Mode: The desired number of pieces to burn.943		#[weight = T::CommonWeightInfo::burn_from()]944		#[transactional]945		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {946			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);947			let budget = budget::Value::new(NESTING_BUDGET);948949			dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))950		}951952		/// Change ownership of the token.953		///954		/// # Permissions955		///956		/// * Collection owner957		/// * Collection admin958		/// * Current token owner959		///960		/// # Arguments961		///962		/// * `recipient`: Address of token recipient.963		/// * `collection_id`: ID of the collection the item belongs to.964		/// * `item_id`: ID of the item.965		///     * Non-Fungible Mode: Required.966		///     * Fungible Mode: Ignored.967		///     * Re-Fungible Mode: Required.968		///969		/// * `value`: Amount to transfer.970		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.971		///     * Fungible Mode: The desired number of pieces to transfer.972		///     * Re-Fungible Mode: The desired number of pieces to transfer.973		#[weight = T::CommonWeightInfo::transfer()]974		#[transactional]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		#[transactional]999		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {1000			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10011002			dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))1003		}10041005		/// Change ownership of an item on behalf of the owner as a non-owner account.1006		///1007		/// See the [`approve`][`Pallet::approve`] method for additional information.1008		///1009		/// After this method executes, one approval is removed from the total so that1010		/// the approved address will not be able to transfer this item again from this owner.1011		///1012		/// # Permissions1013		///1014		/// * Collection owner1015		/// * Collection admin1016		/// * Current item owner1017		/// * Address approved by current item owner1018		///1019		/// # Arguments1020		///1021		/// * `from`: Address that currently owns the token.1022		/// * `recipient`: Address of the new token-owner-to-be.1023		/// * `collection_id`: ID of the collection the item.1024		/// * `item_id`: ID of the item to be transferred.1025		/// * `value`: Amount to transfer.1026		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1027		///     * Fungible Mode: The desired number of pieces to transfer.1028		///     * Re-Fungible Mode: The desired number of pieces to transfer.1029		#[weight = T::CommonWeightInfo::transfer_from()]1030		#[transactional]1031		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {1032			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1033			let budget = budget::Value::new(NESTING_BUDGET);10341035			dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))1036		}10371038		/// Set specific limits of a collection. Empty, or None fields mean chain default.1039		///1040		/// # Permissions1041		///1042		/// * Collection owner1043		/// * Collection admin1044		///1045		/// # Arguments1046		///1047		/// * `collection_id`: ID of the modified collection.1048		/// * `new_limit`: New limits of the collection. Fields that are not set (None)1049		/// will not overwrite the old ones.1050		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1051		#[transactional]1052		pub fn set_collection_limits(1053			origin,1054			collection_id: CollectionId,1055			new_limit: CollectionLimits,1056		) -> DispatchResult {1057			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1058			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1059			target_collection.check_is_internal()?;1060			target_collection.check_is_owner_or_admin(&sender)?;1061			let old_limit = &target_collection.limits;10621063			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10641065			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1066				collection_id1067			));10681069			target_collection.save()1070		}10711072		/// Set specific permissions of a collection. Empty, or None fields mean chain default.1073		///1074		/// # Permissions1075		///1076		/// * Collection owner1077		/// * Collection admin1078		///1079		/// # Arguments1080		///1081		/// * `collection_id`: ID of the modified collection.1082		/// * `new_permission`: New permissions of the collection. Fields that are not set (None)1083		/// will not overwrite the old ones.1084		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1085		#[transactional]1086		pub fn set_collection_permissions(1087			origin,1088			collection_id: CollectionId,1089			new_permission: CollectionPermissions,1090		) -> DispatchResult {1091			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1092			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1093			target_collection.check_is_internal()?;1094			target_collection.check_is_owner_or_admin(&sender)?;1095			let old_limit = &target_collection.permissions;10961097			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;10981099			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(1100				collection_id1101			));11021103			target_collection.save()1104		}11051106		/// Re-partition a refungible token, while owning all of its parts/pieces.1107		///1108		/// # Permissions1109		///1110		/// * Token owner (must own every part)1111		///1112		/// # Arguments1113		///1114		/// * `collection_id`: ID of the collection the RFT belongs to.1115		/// * `token_id`: ID of the RFT.1116		/// * `amount`: New number of parts/pieces into which the token shall be partitioned.1117		#[weight = T::RefungibleExtensionsWeightInfo::repartition()]1118		#[transactional]1119		pub fn repartition(1120			origin,1121			collection_id: CollectionId,1122			token_id: TokenId,1123			amount: u128,1124		) -> DispatchResultWithPostInfo {1125			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1126			dispatch_tx::<T, _>(collection_id, |d| {1127				if let Some(refungible_extensions) = d.refungible_extensions() {1128					refungible_extensions.repartition(&sender, token_id, amount)1129				} else {1130					fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1131				}1132			})1133		}1134	}1135}