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

difftreelog

source

pallets/unique/src/lib.rs40.0 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		/// DEPRECATED - use create_collection_ex. 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 [`create_collection_ex`](Call::create_collection_ex) instead.302		///303		/// # Permissions304		///305		/// * Anyone - becomes the owner of the new collection.306		///307		/// # Arguments308		///309		/// * `collection_name`: UTF-16 string with collection name (limit 64 characters),310		/// will be stored as zero-terminated.311		/// * `collection_description`: UTF-16 string with collection description (limit 256 characters),312		/// will be stored as zero-terminated.313		/// * `token_prefix`: UTF-8 string with token prefix.314		/// * `mode`: [`CollectionMode`] and type dependent data.315		// returns collection ID316		#[weight = <SelfWeightOf<T>>::create_collection()]317		#[transactional]318		#[deprecated]319		pub fn create_collection(320			origin,321			collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,322			collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,323			token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,324			mode: CollectionMode325		) -> DispatchResult  {326			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {327				name: collection_name,328				description: collection_description,329				token_prefix,330				mode,331				..Default::default()332			};333			Self::create_collection_ex(origin, data)334		}335336		/// Create a collection with explicit parameters.337		/// Prefer it to the deprecated [`create_collection`](Call::create_collection) method.338		///339		/// # Permissions340		///341		/// * Anyone - becomes the owner of the new collection.342		///343		/// # Arguments344		///345		/// * `data`: Explicit data of a collection used for its creation.346		#[weight = <SelfWeightOf<T>>::create_collection()]347		#[transactional]348		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {349			let sender = ensure_signed(origin)?;350351			// =========352353			T::CollectionDispatch::create(T::CrossAccountId::from_sub(sender), data)?;354355			Ok(())356		}357358		/// Destroy a collection if no tokens exist within.359		///360		/// # Permissions361		///362		/// * Collection owner363		///364		/// # Arguments365		///366		/// * `collection_id`: Collection to destroy.367		#[weight = <SelfWeightOf<T>>::destroy_collection()]368		#[transactional]369		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {370			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);371			let collection = <CollectionHandle<T>>::try_get(collection_id)?;372			collection.check_is_internal()?;373374			// =========375376			T::CollectionDispatch::destroy(sender, collection)?;377378			<NftTransferBasket<T>>::remove_prefix(collection_id, None);379			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);380			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);381382			<NftApproveBasket<T>>::remove_prefix(collection_id, None);383			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);384			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);385386			Ok(())387		}388389		/// Add an address to allow list.390		///391		/// # Permissions392		///393		/// * Collection owner394		/// * Collection admin395		///396		/// # Arguments397		///398		/// * `collection_id`: ID of the modified collection.399		/// * `address`: ID of the address to be added to the allowlist.400		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]401		#[transactional]402		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{403404			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);405			let collection = <CollectionHandle<T>>::try_get(collection_id)?;406			collection.check_is_internal()?;407408			<PalletCommon<T>>::toggle_allowlist(409				&collection,410				&sender,411				&address,412				true,413			)?;414415			Self::deposit_event(Event::<T>::AllowListAddressAdded(416				collection_id,417				address418			));419420			Ok(())421		}422423		/// Remove an address from allow list.424		///425		/// # Permissions426		///427		/// * Collection owner428		/// * Collection admin429		///430		/// # Arguments431		///432		/// * `collection_id`: ID of the modified collection.433		/// * `address`: ID of the address to be removed from the allowlist.434		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]435		#[transactional]436		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{437438			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);439			let collection = <CollectionHandle<T>>::try_get(collection_id)?;440			collection.check_is_internal()?;441442			<PalletCommon<T>>::toggle_allowlist(443				&collection,444				&sender,445				&address,446				false,447			)?;448449			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(450				collection_id,451				address452			));453454			Ok(())455		}456457		/// Change the owner of the collection.458		///459		/// # Permissions460		///461		/// * Collection owner462		///463		/// # Arguments464		///465		/// * `collection_id`: ID of the modified collection.466		/// * `new_owner`: ID of the account that will become the owner.467		#[weight = <SelfWeightOf<T>>::change_collection_owner()]468		#[transactional]469		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {470471			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);472473			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;474			target_collection.check_is_internal()?;475			target_collection.check_is_owner(&sender)?;476477			target_collection.owner = new_owner.clone();478			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(479				collection_id,480				new_owner481			));482483			target_collection.save()484		}485486		/// Add an admin to a collection.487		///488		/// NFT Collection can be controlled by multiple admin addresses489		/// (some which can also be servers, for example). Admins can issue490		/// and burn NFTs, as well as add and remove other admins,491		/// but cannot change NFT or Collection ownership.492		///493		/// # Permissions494		///495		/// * Collection owner496		/// * Collection admin497		///498		/// # Arguments499		///500		/// * `collection_id`: ID of the Collection to add an admin for.501		/// * `new_admin`: Address of new admin to add.502		#[weight = <SelfWeightOf<T>>::add_collection_admin()]503		#[transactional]504		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin: T::CrossAccountId) -> DispatchResult {505			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);506			let collection = <CollectionHandle<T>>::try_get(collection_id)?;507			collection.check_is_internal()?;508509			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(510				collection_id,511				new_admin.clone()512			));513514			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin, true)515		}516517		/// Remove admin of a collection.518		///519		/// An admin address can remove itself. List of admins may become empty,520		/// in which case only Collection Owner will be able to add an Admin.521		///522		/// # Permissions523		///524		/// * Collection owner525		/// * Collection admin526		///527		/// # Arguments528		///529		/// * `collection_id`: ID of the collection to remove the admin for.530		/// * `account_id`: Address of the admin to remove.531		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]532		#[transactional]533		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {534			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);535			let collection = <CollectionHandle<T>>::try_get(collection_id)?;536			collection.check_is_internal()?;537538			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(539				collection_id,540				account_id.clone()541			));542543			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)544		}545546		/// Set (invite) a new collection sponsor.547		/// If successful, confirmation from the sponsor-to-be will be pending.548		///549		/// # Permissions550		///551		/// * Collection owner552		/// * Collection admin553		///554		/// # Arguments555		///556		/// * `collection_id`: ID of the modified collection.557		/// * `new_sponsor`: ID of the account of the sponsor-to-be.558		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]559		#[transactional]560		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {561			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);562563			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;564			target_collection.check_is_owner_or_admin(&sender)?;565			target_collection.check_is_internal()?;566567			target_collection.set_sponsor(new_sponsor.clone())?;568569			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(570				collection_id,571				new_sponsor572			));573574			target_collection.save()575		}576577		/// Confirm own sponsorship of a collection, becoming the sponsor.578		/// An invitation must be pending, see [`set_collection_sponsor`](Call::set_collection_sponsor).579		///580		/// Sponsor can pay the fees of a transaction instead of the sender,581		/// but only within specified limits.582		///583		/// # Permissions584		///585		/// * Sponsor-to-be586		///587		/// # Arguments588		///589		/// * `collection_id`: ID of the collection with the pending sponsor.590		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]591		#[transactional]592		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {593			let sender = ensure_signed(origin)?;594595			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;596			target_collection.check_is_internal()?;597			ensure!(598				target_collection.confirm_sponsorship(&sender)?,599				Error::<T>::ConfirmUnsetSponsorFail600			);601602			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(603				collection_id,604				sender605			));606607			target_collection.save()608		}609610		/// Remove a sponsor from a collection, making everyone pay for their own transactions.611		///612		/// # Permissions613		///614		/// * Collection owner615		///616		/// # Arguments617		///618		/// * `collection_id`: ID of the collection with the sponsor to remove.619		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]620		#[transactional]621		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {622			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);623624			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;625			target_collection.check_is_internal()?;626			target_collection.check_is_owner(&sender)?;627628			target_collection.sponsorship = SponsorshipState::Disabled;629630			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(631				collection_id632			));633			target_collection.save()634		}635636		/// Mint an item within a collection.637		///638		/// A collection must exist first, see [`create_collection_ex`](Call::create_collection_ex).639		///640		/// # Permissions641		///642		/// * Collection owner643		/// * Collection admin644		/// * Anyone if645		///     * Allow List is enabled, and646		///     * Address is added to allow list, and647		///     * MintPermission is enabled (see [`set_collection_permissions`](Call::set_collection_permissions))648		///649		/// # Arguments650		///651		/// * `collection_id`: ID of the collection to which an item would belong.652		/// * `owner`: Address of the initial owner of the item.653		/// * `data`: Token data describing the item to store on chain.654		#[weight = T::CommonWeightInfo::create_item()]655		#[transactional]656		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {657			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);658			let budget = budget::Value::new(NESTING_BUDGET);659660			dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))661		}662663		/// Create multiple items within a collection.664		///665		/// A collection must exist first, see [`create_collection_ex`](Call::create_collection_ex).666		///667		/// # Permissions668		///669		/// * Collection owner670		/// * Collection admin671		/// * Anyone if672		///     * Allow List is enabled, and673		///     * Address is added to the allow list, and674		///     * MintPermission is enabled (see [`set_collection_permissions`](Call::set_collection_permissions))675		///676		/// # Arguments677		///678		/// * `collection_id`: ID of the collection to which the tokens would belong.679		/// * `owner`: Address of the initial owner of the tokens.680		/// * `items_data`: Vector of data describing each item to be created.681		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]682		#[transactional]683		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {684			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);685			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);686			let budget = budget::Value::new(NESTING_BUDGET);687688			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))689		}690691		/// Add or change collection properties.692		///693		/// # Permissions694		///695		/// * Collection owner696		/// * Collection admin697		///698		/// # Arguments699		///700		/// * `collection_id`: ID of the modified collection.701		/// * `properties`: Vector of key-value pairs stored as the collection's metadata.702		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.703		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]704		#[transactional]705		pub fn set_collection_properties(706			origin,707			collection_id: CollectionId,708			properties: Vec<Property>709		) -> DispatchResultWithPostInfo {710			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);711712			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);713714			dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))715		}716717		/// Delete specified collection properties.718		///719		/// # Permissions720		///721		/// * Collection Owner722		/// * Collection Admin723		///724		/// # Arguments725		///726		/// * `collection_id`: ID of the modified collection.727		/// * `property_keys`: Vector of keys of the properties to be deleted.728		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.729		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]730		#[transactional]731		pub fn delete_collection_properties(732			origin,733			collection_id: CollectionId,734			property_keys: Vec<PropertyKey>,735		) -> DispatchResultWithPostInfo {736			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);737738			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);739740			dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))741		}742743		/// Add or change token properties according to collection's permissions.744		/// Currently properties only work with NFTs.745		///746		/// # Permissions747		///748		/// * Depends on collection's token property permissions and specified property mutability:749		/// 	* Collection owner750		/// 	* Collection admin751		/// 	* Token owner752		///753		/// See [`set_token_property_permissions`](Call::set_token_property_permissions).754		///755		/// # Arguments756		///757		/// * `collection_id: ID of the collection to which the token belongs.758		/// * `token_id`: ID of the modified token.759		/// * `properties`: Vector of key-value pairs stored as the token's metadata.760		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.761		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]762		#[transactional]763		pub fn set_token_properties(764			origin,765			collection_id: CollectionId,766			token_id: TokenId,767			properties: Vec<Property>768		) -> DispatchResultWithPostInfo {769			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);770771			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);772			let budget = budget::Value::new(NESTING_BUDGET);773774			dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))775		}776777		/// Delete specified token properties. Currently properties only work with NFTs.778		///779		/// # Permissions780		///781		/// * Depends on collection's token property permissions and specified property mutability:782		/// 	* Collection owner783		/// 	* Collection admin784		/// 	* Token owner785		///786		/// # Arguments787		///788		/// * `collection_id`: ID of the collection to which the token belongs.789		/// * `token_id`: ID of the modified token.790		/// * `property_keys`: Vector of keys of the properties to be deleted.791		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.792		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]793		#[transactional]794		pub fn delete_token_properties(795			origin,796			collection_id: CollectionId,797			token_id: TokenId,798			property_keys: Vec<PropertyKey>799		) -> DispatchResultWithPostInfo {800			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);801802			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);803			let budget = budget::Value::new(NESTING_BUDGET);804805			dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))806		}807808		/// Add or change token property permissions of a collection.809		///810		/// Without a permission for a particular key, a property with that key811		/// cannot be created in a token.812		///813		/// # Permissions814		///815		/// * Collection owner816		/// * Collection admin817		///818		/// # Arguments819		///820		/// * `collection_id`: ID of the modified collection.821		/// * `property_permissions`: Vector of permissions for property keys.822		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.823		#[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]824		#[transactional]825		pub fn set_token_property_permissions(826			origin,827			collection_id: CollectionId,828			property_permissions: Vec<PropertyKeyPermission>,829		) -> DispatchResultWithPostInfo {830			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);831832			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);833834			dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))835		}836837		/// Create multiple items within a collection with explicitly specified initial parameters.838		///839		/// # Permissions840		///841		/// * Collection owner842		/// * Collection admin843		/// * Anyone if844		///     * Allow List is enabled, and845		///     * Address is added to allow list, and846		///     * MintPermission is enabled (see [`set_collection_permissions`](Call::set_collection_permissions))847		///848		/// # Arguments849		///850		/// * `collection_id`: ID of the collection to which the tokens would belong.851		/// * `data`: Explicit item creation data.852		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]853		#[transactional]854		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {855			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);856			let budget = budget::Value::new(NESTING_BUDGET);857858			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))859		}860861		/// Completely allow or disallow transfers for a particular collection.862		///863		/// # Permissions864		///865		/// * Collection owner866		///867		/// # Arguments868		///869		/// * `collection_id`: ID of the collection.870		/// * `value`: New value of the flag, are transfers allowed?871		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]872		#[transactional]873		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {874			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);875			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;876			target_collection.check_is_internal()?;877			target_collection.check_is_owner(&sender)?;878879			// =========880881			target_collection.limits.transfers_enabled = Some(value);882			target_collection.save()883		}884885		/// Destroy an item.886		///887		/// # Permissions888		///889		/// * Collection owner890		/// * Collection admin891		/// * Current item owner892		///893		/// # Arguments894		///895		/// * `collection_id`: ID of the collection to which the item belongs.896		/// * `item_id`: ID of item to burn.897		/// * `value`: Number of parts of the item to destroy.898		/// 	* Non-Fungible Mode: There is always 1 NFT.899		///     * Fungible Mode: The desired number of parts to burn.900		///     * Re-Fungible Mode: The desired number of parts to burn.901		#[weight = T::CommonWeightInfo::burn_item()]902		#[transactional]903		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {904			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);905906			let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;907			if value == 1 {908				<NftTransferBasket<T>>::remove(collection_id, item_id);909				<NftApproveBasket<T>>::remove(collection_id, item_id);910			}911			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?912			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());913			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));914			Ok(post_info)915		}916917		/// Destroy a token on behalf of the owner as a non-owner account.918		/// See also: [`approve`](Call::approve).919		///920		/// After this method executes, one approval is removed from the total so that921		/// the approved address will not be able to transfer this item again from this owner.922		///923		/// # Permissions924		///925		/// * Collection owner926		/// * Collection admin927		/// * Current token owner928		/// * Address approved by current item owner929		///930		/// # Arguments931		///932		/// * `from`: The owner of the burning item.933		/// * `collection_id`: ID of the collection to which the item belongs.934		/// * `item_id`: ID of item to burn.935		/// * `value`: Number of parts to burn.936		/// 	* Non-Fungible Mode: There is always 1 NFT.937		///     * Fungible Mode: The desired number of parts to burn.938		///     * Re-Fungible Mode: The desired number of parts to burn.939		#[weight = T::CommonWeightInfo::burn_from()]940		#[transactional]941		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {942			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);943			let budget = budget::Value::new(NESTING_BUDGET);944945			dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))946		}947948		/// Change ownership of the token.949		///950		/// # Permissions951		///952		/// * Collection owner953		/// * Collection admin954		/// * Current token owner955		///956		/// # Arguments957		///958		/// * `recipient`: Address of token recipient.959		/// * `collection_id`: ID of the collection the item belongs to.960		/// * `item_id`: ID of the item.961		///     * Non-Fungible Mode: Required.962		///     * Fungible Mode: Ignored.963		///     * Re-Fungible Mode: Required.964		///965		/// * `value`: Amount to transfer.966		/// 	* Non-Fungible Mode: There is always 1 NFT.967		///     * Fungible Mode: The desired number of parts to transfer.968		///     * Re-Fungible Mode: The desired number of parts to transfer.969		#[weight = T::CommonWeightInfo::transfer()]970		#[transactional]971		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {972			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);973			let budget = budget::Value::new(NESTING_BUDGET);974975			dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))976		}977978		/// Allow a non-permissioned address to transfer or burn an item.979		///980		/// # Permissions981		///982		/// * Collection owner983		/// * Collection admin984		/// * Current item owner985		///986		/// # Arguments987		///988		/// * `spender`: Account to be approved to make specific transactions on non-owned tokens.989		/// * `collection_id`: ID of the collection the item belongs to.990		/// * `item_id`: ID of the item transactions on which are now approved.991		/// * `amount`: Number of approved transactions overwriting the current number,992		/// e.g. set to `0` to remove approval.993		#[weight = T::CommonWeightInfo::approve()]994		#[transactional]995		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {996			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);997998			dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))999		}10001001		/// Change ownership of an item on behalf of the owner as a non-owner account.1002		/// See the [`approve`](Call::approve) method for additional information.1003		///1004		/// After this method executes, one approval is removed from the total so that1005		/// the approved address will not be able to transfer this item again from this owner.1006		///1007		/// # Permissions1008		///1009		/// * Collection owner1010		/// * Collection admin1011		/// * Current item owner1012		/// * Address approved by current item owner1013		///1014		/// # Arguments1015		///1016		/// * `from`: Address that currently owns the token.1017		/// * `recipient`: Address of the new token-owner-to-be.1018		/// * `collection_id`: ID of the collection the item.1019		/// * `item_id`: ID of the item to be transferred.1020		/// * `value`: Amount of parts to transfer.1021		/// 	* Non-Fungible Mode: There is always 1 NFT.1022		///     * Fungible Mode: The desired number of parts to transfer.1023		///     * Re-Fungible Mode: The desired number of parts to transfer.1024		#[weight = T::CommonWeightInfo::transfer_from()]1025		#[transactional]1026		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {1027			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1028			let budget = budget::Value::new(NESTING_BUDGET);10291030			dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))1031		}10321033		/// Set specific limits of a collection. Empty, or None fields mean chain default.1034		///1035		/// # Permissions1036		///1037		/// * Collection owner1038		/// * Collection admin1039		///1040		/// # Arguments1041		///1042		/// * `collection_id`: ID of the modified collection.1043		/// * `new_limit`: New limits of the collection. They will overwrite the current ones.1044		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1045		#[transactional]1046		pub fn set_collection_limits(1047			origin,1048			collection_id: CollectionId,1049			new_limit: CollectionLimits,1050		) -> DispatchResult {1051			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1052			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1053			target_collection.check_is_internal()?;1054			target_collection.check_is_owner_or_admin(&sender)?;1055			let old_limit = &target_collection.limits;10561057			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10581059			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1060				collection_id1061			));10621063			target_collection.save()1064		}10651066		/// Set specific permissions of a collection. Empty, or None fields mean chain default.1067		///1068		/// # Permissions1069		///1070		/// * Collection owner1071		/// * Collection admin1072		///1073		/// # Arguments1074		///1075		/// * `collection_id`: ID of the modified collection.1076		/// * `new_permission`: New permissions of the collection. They will overwrite the current ones.1077		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1078		#[transactional]1079		pub fn set_collection_permissions(1080			origin,1081			collection_id: CollectionId,1082			new_permission: CollectionPermissions,1083		) -> DispatchResult {1084			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1085			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1086			target_collection.check_is_internal()?;1087			target_collection.check_is_owner_or_admin(&sender)?;1088			let old_limit = &target_collection.permissions;10891090			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;10911092			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(1093				collection_id1094			));10951096			target_collection.save()1097		}10981099		/// Re-partition a refungible token, while owning all of its parts.1100		///1101		/// # Permissions1102		///1103		/// * Token owner (must own every part)1104		///1105		/// # Arguments1106		///1107		/// * `collection_id`: ID of the collection the RFT belongs to.1108		/// * `token_id`: ID of the RFT.1109		/// * `amount`: New number of parts into which the token shall be partitioned.1110		#[weight = T::RefungibleExtensionsWeightInfo::repartition()]1111		#[transactional]1112		pub fn repartition(1113			origin,1114			collection_id: CollectionId,1115			token_id: TokenId,1116			amount: u128,1117		) -> DispatchResultWithPostInfo {1118			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1119			dispatch_tx::<T, _>(collection_id, |d| {1120				if let Some(refungible_extensions) = d.refungible_extensions() {1121					refungible_extensions.repartition(&sender, token_id, amount)1122				} else {1123					fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1124				}1125			})1126		}1127	}1128}