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

difftreelog

refactor(pallet-unique) switch to #[pallet] macro

Yaroslav Bolyukin2023-04-17parent: #2993020.patch.diff
in: master

3 files changed

modifiedpallets/unique/src/eth/mod.rsdiffbeforeafterboth
--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -27,7 +27,7 @@
 	dispatch::CollectionDispatch,
 	erc::{CollectionHelpersEvents, static_property::key},
 	eth::{map_eth_to_id, collection_id_to_address},
-	Pallet as PalletCommon,
+	Pallet as PalletCommon, CollectionHandle,
 };
 use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};
 use pallet_evm_coder_substrate::{
@@ -262,7 +262,7 @@
 		let collection =
 			pallet_common::eth::map_eth_to_id(&collection).ok_or("not a collection address")?;
 		let mut collection =
-			<crate::CollectionHandle<T>>::new(collection).ok_or("collection not found")?;
+			<CollectionHandle<T>>::new(collection).ok_or("collection not found")?;
 
 		if !matches!(
 			collection.mode,
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
before · pallets/unique/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # 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,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, ensure_root};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	CreateCollectionData, CreateItemExData, budget, Property, PropertyKey, 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")]102pub mod benchmarking;103pub mod weights;104use weights::WeightInfo;105106/// A 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		/// Length of items properties must be greater than 0.115		EmptyArgument,116		/// Repertition is only supported by refungible collection.117		RepartitionCalledOnNonRefungibleCollection,118	}119}120121/// Configuration trait of this pallet.122pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {123	/// Weight information for extrinsics in this pallet.124	type WeightInfo: WeightInfo;125126	/// Weight information for common pallet operations.127	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;128129	/// Weight info information for extra refungible pallet operations.130	type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;131}132133type SelfWeightOf<T> = <T as Config>::WeightInfo;134135// # Used definitions136//137// ## User control levels138//139// chain-controlled - key is uncontrolled by user140//                    i.e autoincrementing index141//                    can use non-cryptographic hash142// real - key is controlled by user143//        but it is hard to generate enough colliding values, i.e owner of signed txs144//        can use non-cryptographic hash145// controlled - key is completly controlled by users146//              i.e maps with mutable keys147//              should use cryptographic hash148//149// ## User control level downgrade reasons150//151// ?1 - chain-controlled -> controlled152//      collections/tokens can be destroyed, resulting in massive holes153// ?2 - chain-controlled -> controlled154//      same as ?1, but can be only added, resulting in easier exploitation155// ?3 - real -> controlled156//      no confirmation required, so addresses can be easily generated157decl_storage! {158	trait Store for Module<T: Config> as Unique {159160		//#region Private members161		/// Used for migrations162		ChainVersion: u64;163		//#endregion164165		//#region Tokens transfer sponosoring rate limit baskets166		/// (Collection id (controlled?2), who created (real))167		/// TODO: Off chain worker should remove from this map when collection gets removed168		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;169		/// Collection id (controlled?2), token id (controlled?2)170		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;171		/// Collection id (controlled?2), owning user (real)172		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;173		/// Collection id (controlled?2), token id (controlled?2)174		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>;175		//#endregion176177		/// Variable metadata sponsoring178		/// Collection id (controlled?2), token id (controlled?2)179		#[deprecated]180		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;181		/// Last sponsoring of token property setting // todo:doc rephrase this and the following182		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;183184		/// Last sponsoring of NFT approval in a collection185		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;186		/// Last sponsoring of fungible tokens approval in a collection187		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;188		/// Last sponsoring of RFT approval in a collection189		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>;190	}191}192193decl_module! {194	/// Type alias to Pallet, to be used by construct_runtime.195	pub struct Module<T: Config> for enum Call196	where197		origin: T::RuntimeOrigin198	{199		type Error = Error<T>;200201		#[doc = "A maximum number of levels of depth in the token nesting tree."]202		const NESTING_BUDGET: u32 = NESTING_BUDGET;203204		#[doc = "Maximal length of a collection name."]205		const MAX_COLLECTION_NAME_LENGTH: u32 = MAX_COLLECTION_NAME_LENGTH;206207		#[doc = "Maximal length of a collection description."]208		const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = MAX_COLLECTION_DESCRIPTION_LENGTH;209210		#[doc = "Maximal length of a token prefix."]211		const MAX_TOKEN_PREFIX_LENGTH: u32 = MAX_TOKEN_PREFIX_LENGTH;212213		#[doc = "Maximum admins per collection."]214		const COLLECTION_ADMINS_LIMIT: u32 = COLLECTION_ADMINS_LIMIT;215216		#[doc = "Maximal length of a property key."]217		const MAX_PROPERTY_KEY_LENGTH: u32 = MAX_PROPERTY_KEY_LENGTH;218219		#[doc = "Maximal length of a property value."]220		const MAX_PROPERTY_VALUE_LENGTH: u32 = MAX_PROPERTY_VALUE_LENGTH;221222		#[doc = "A maximum number of token properties."]223		const MAX_PROPERTIES_PER_ITEM: u32 = MAX_PROPERTIES_PER_ITEM;224225		#[doc = "Maximum size for all collection properties."]226		const MAX_COLLECTION_PROPERTIES_SIZE: u32 = MAX_COLLECTION_PROPERTIES_SIZE;227228		#[doc = "Maximum size of all token properties."]229		const MAX_TOKEN_PROPERTIES_SIZE: u32 = MAX_TOKEN_PROPERTIES_SIZE;230231		#[doc = "Default NFT collection limit."]232		const NFT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::NFT);233234		#[doc = "Default RFT collection limit."]235		const RFT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::ReFungible);236237		#[doc = "Default FT collection limit."]238		const FT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::Fungible(0));239240		fn on_initialize(_now: T::BlockNumber) -> Weight {241			Weight::zero()242		}243244		fn on_runtime_upgrade() -> Weight {245			Weight::zero()246		}247248		/// Create a collection of tokens.249		///250		/// Each Token may have multiple properties encoded as an array of bytes251		/// of certain length. The initial owner of the collection is set252		/// to the address that signed the transaction and can be changed later.253		///254		/// Prefer the more advanced [`create_collection_ex`][`Pallet::create_collection_ex`] instead.255		///256		/// # Permissions257		///258		/// * Anyone - becomes the owner of the new collection.259		///260		/// # Arguments261		///262		/// * `collection_name`: Wide-character string with collection name263		/// (limit [`MAX_COLLECTION_NAME_LENGTH`]).264		/// * `collection_description`: Wide-character string with collection description265		/// (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).266		/// * `token_prefix`: Byte string containing the token prefix to mark a collection267		/// to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).268		/// * `mode`: Type of items stored in the collection and type dependent data.269		///270		/// returns collection ID271		///272		/// Deprecated: `create_collection_ex` is more up-to-date and advanced, prefer it instead.273		#[weight = <SelfWeightOf<T>>::create_collection()]274		fn create_collection(275			origin,276			collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,277			collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,278			token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,279			mode: CollectionMode280		) -> DispatchResult {281			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {282				name: collection_name,283				description: collection_description,284				token_prefix,285				mode,286				..Default::default()287			};288			Self::create_collection_ex(origin, data)289		}290291		/// Create a collection with explicit parameters.292		///293		/// Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.294		///295		/// # Permissions296		///297		/// * Anyone - becomes the owner of the new collection.298		///299		/// # Arguments300		///301		/// * `data`: Explicit data of a collection used for its creation.302		#[weight = <SelfWeightOf<T>>::create_collection()]303		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {304			let sender = ensure_signed(origin)?;305306			// =========307			let sender = T::CrossAccountId::from_sub(sender);308			let _id = T::CollectionDispatch::create(sender.clone(), sender, data, Default::default())?;309310			Ok(())311		}312313		/// Destroy a collection if no tokens exist within.314		///315		/// # Permissions316		///317		/// * Collection owner318		///319		/// # Arguments320		///321		/// * `collection_id`: Collection to destroy.322		#[weight = <SelfWeightOf<T>>::destroy_collection()]323		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {324			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);325326			Self::destroy_collection_internal(sender, collection_id)327		}328329		/// Add an address to allow list.330		///331		/// # Permissions332		///333		/// * Collection owner334		/// * Collection admin335		///336		/// # Arguments337		///338		/// * `collection_id`: ID of the modified collection.339		/// * `address`: ID of the address to be added to the allowlist.340		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]341		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{342343			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);344			let collection = <CollectionHandle<T>>::try_get(collection_id)?;345			collection.check_is_internal()?;346347			<PalletCommon<T>>::toggle_allowlist(348				&collection,349				&sender,350				&address,351				true,352			)?;353354			Ok(())355		}356357		/// Remove an address from allow list.358		///359		/// # Permissions360		///361		/// * Collection owner362		/// * Collection admin363		///364		/// # Arguments365		///366		/// * `collection_id`: ID of the modified collection.367		/// * `address`: ID of the address to be removed from the allowlist.368		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]369		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{370371			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);372			let collection = <CollectionHandle<T>>::try_get(collection_id)?;373			collection.check_is_internal()?;374375			<PalletCommon<T>>::toggle_allowlist(376				&collection,377				&sender,378				&address,379				false,380			)?;381382			Ok(())383		}384385		/// Change the owner of the collection.386		///387		/// # Permissions388		///389		/// * Collection owner390		///391		/// # Arguments392		///393		/// * `collection_id`: ID of the modified collection.394		/// * `new_owner`: ID of the account that will become the owner.395		#[weight = <SelfWeightOf<T>>::change_collection_owner()]396		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {397			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);398			let new_owner = T::CrossAccountId::from_sub(new_owner);399			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;400			target_collection.change_owner(sender, new_owner.clone())401		}402403		/// Add an admin to a collection.404		///405		/// NFT Collection can be controlled by multiple admin addresses406		/// (some which can also be servers, for example). Admins can issue407		/// and burn NFTs, as well as add and remove other admins,408		/// but cannot change NFT or Collection ownership.409		///410		/// # Permissions411		///412		/// * Collection owner413		/// * Collection admin414		///415		/// # Arguments416		///417		/// * `collection_id`: ID of the Collection to add an admin for.418		/// * `new_admin`: Address of new admin to add.419		#[weight = <SelfWeightOf<T>>::add_collection_admin()]420		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {421			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);422			let collection = <CollectionHandle<T>>::try_get(collection_id)?;423			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)424		}425426		/// Remove admin of a collection.427		///428		/// An admin address can remove itself. List of admins may become empty,429		/// in which case only Collection Owner will be able to add an Admin.430		///431		/// # Permissions432		///433		/// * Collection owner434		/// * Collection admin435		///436		/// # Arguments437		///438		/// * `collection_id`: ID of the collection to remove the admin for.439		/// * `account_id`: Address of the admin to remove.440		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]441		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {442			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);443			let collection = <CollectionHandle<T>>::try_get(collection_id)?;444			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)445		}446447		/// Set (invite) a new collection sponsor.448		///449		/// If successful, confirmation from the sponsor-to-be will be pending.450		///451		/// # Permissions452		///453		/// * Collection owner454		/// * Collection admin455		///456		/// # Arguments457		///458		/// * `collection_id`: ID of the modified collection.459		/// * `new_sponsor`: ID of the account of the sponsor-to-be.460		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]461		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {462			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);463			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;464			target_collection.set_sponsor(&sender, new_sponsor.clone())465		}466467		/// Confirm own sponsorship of a collection, becoming the sponsor.468		///469		/// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].470		/// Sponsor can pay the fees of a transaction instead of the sender,471		/// but only within specified limits.472		///473		/// # Permissions474		///475		/// * Sponsor-to-be476		///477		/// # Arguments478		///479		/// * `collection_id`: ID of the collection with the pending sponsor.480		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]481		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {482			let sender = ensure_signed(origin)?;483			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;484			target_collection.confirm_sponsorship(&sender)485		}486487		/// Remove a collection's a sponsor, making everyone pay for their own transactions.488		///489		/// # Permissions490		///491		/// * Collection owner492		///493		/// # Arguments494		///495		/// * `collection_id`: ID of the collection with the sponsor to remove.496		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]497		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {498			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);499			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;500			target_collection.remove_sponsor(&sender)501		}502503		/// Mint an item within a collection.504		///505		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].506		///507		/// # Permissions508		///509		/// * Collection owner510		/// * Collection admin511		/// * Anyone if512		///     * Allow List is enabled, and513		///     * Address is added to allow list, and514		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])515		///516		/// # Arguments517		///518		/// * `collection_id`: ID of the collection to which an item would belong.519		/// * `owner`: Address of the initial owner of the item.520		/// * `data`: Token data describing the item to store on chain.521		#[weight = T::CommonWeightInfo::create_item(&data)]522		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {523			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);524			let budget = budget::Value::new(NESTING_BUDGET);525526			dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))527		}528529		/// Create multiple items within a collection.530		///531		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].532		///533		/// # Permissions534		///535		/// * Collection owner536		/// * Collection admin537		/// * Anyone if538		///     * Allow List is enabled, and539		///     * Address is added to the allow list, and540		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])541		///542		/// # Arguments543		///544		/// * `collection_id`: ID of the collection to which the tokens would belong.545		/// * `owner`: Address of the initial owner of the tokens.546		/// * `items_data`: Vector of data describing each item to be created.547		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]548		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {549			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);550			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);551			let budget = budget::Value::new(NESTING_BUDGET);552553			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))554		}555556		/// Add or change collection properties.557		///558		/// # Permissions559		///560		/// * Collection owner561		/// * Collection admin562		///563		/// # Arguments564		///565		/// * `collection_id`: ID of the modified collection.566		/// * `properties`: Vector of key-value pairs stored as the collection's metadata.567		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.568		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]569		pub fn set_collection_properties(570			origin,571			collection_id: CollectionId,572			properties: Vec<Property>573		) -> DispatchResultWithPostInfo {574			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);575576			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);577578			dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))579		}580581		/// Delete specified collection properties.582		///583		/// # Permissions584		///585		/// * Collection Owner586		/// * Collection Admin587		///588		/// # Arguments589		///590		/// * `collection_id`: ID of the modified collection.591		/// * `property_keys`: Vector of keys of the properties to be deleted.592		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.593		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]594		pub fn delete_collection_properties(595			origin,596			collection_id: CollectionId,597			property_keys: Vec<PropertyKey>,598		) -> DispatchResultWithPostInfo {599			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);600601			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);602603			dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))604		}605606		/// Add or change token properties according to collection's permissions.607		/// Currently properties only work with NFTs.608		///609		/// # Permissions610		///611		/// * Depends on collection's token property permissions and specified property mutability:612		/// 	* Collection owner613		/// 	* Collection admin614		/// 	* Token owner615		///616		/// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].617		///618		/// # Arguments619		///620		/// * `collection_id: ID of the collection to which the token belongs.621		/// * `token_id`: ID of the modified token.622		/// * `properties`: Vector of key-value pairs stored as the token's metadata.623		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.624		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]625		pub fn set_token_properties(626			origin,627			collection_id: CollectionId,628			token_id: TokenId,629			properties: Vec<Property>630		) -> DispatchResultWithPostInfo {631			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);632633			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);634			let budget = budget::Value::new(NESTING_BUDGET);635636			dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))637		}638639		/// Delete specified token properties. Currently properties only work with NFTs.640		///641		/// # Permissions642		///643		/// * Depends on collection's token property permissions and specified property mutability:644		/// 	* Collection owner645		/// 	* Collection admin646		/// 	* Token owner647		///648		/// # Arguments649		///650		/// * `collection_id`: ID of the collection to which the token belongs.651		/// * `token_id`: ID of the modified token.652		/// * `property_keys`: Vector of keys of the properties to be deleted.653		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.654		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]655		pub fn delete_token_properties(656			origin,657			collection_id: CollectionId,658			token_id: TokenId,659			property_keys: Vec<PropertyKey>660		) -> DispatchResultWithPostInfo {661			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);662663			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);664			let budget = budget::Value::new(NESTING_BUDGET);665666			dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))667		}668669		/// Add or change token property permissions of a collection.670		///671		/// Without a permission for a particular key, a property with that key672		/// cannot be created in a token.673		///674		/// # Permissions675		///676		/// * Collection owner677		/// * Collection admin678		///679		/// # Arguments680		///681		/// * `collection_id`: ID of the modified collection.682		/// * `property_permissions`: Vector of permissions for property keys.683		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.684		#[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]685		pub fn set_token_property_permissions(686			origin,687			collection_id: CollectionId,688			property_permissions: Vec<PropertyKeyPermission>,689		) -> DispatchResultWithPostInfo {690			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);691692			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);693694			dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))695		}696697		/// Create multiple items within a collection with explicitly specified initial parameters.698		///699		/// # Permissions700		///701		/// * Collection owner702		/// * Collection admin703		/// * Anyone if704		///     * Allow List is enabled, and705		///     * Address is added to allow list, and706		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])707		///708		/// # Arguments709		///710		/// * `collection_id`: ID of the collection to which the tokens would belong.711		/// * `data`: Explicit item creation data.712		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]713		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {714			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);715			let budget = budget::Value::new(NESTING_BUDGET);716717			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))718		}719720		/// Completely allow or disallow transfers for a particular collection.721		///722		/// # Permissions723		///724		/// * Collection owner725		///726		/// # Arguments727		///728		/// * `collection_id`: ID of the collection.729		/// * `value`: New value of the flag, are transfers allowed?730		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]731		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {732			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);733			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;734			target_collection.check_is_internal()?;735			target_collection.check_is_owner(&sender)?;736737			// =========738739			target_collection.limits.transfers_enabled = Some(value);740			target_collection.save()741		}742743		/// Destroy an item.744		///745		/// # Permissions746		///747		/// * Collection owner748		/// * Collection admin749		/// * Current item owner750		///751		/// # Arguments752		///753		/// * `collection_id`: ID of the collection to which the item belongs.754		/// * `item_id`: ID of item to burn.755		/// * `value`: Number of pieces of the item to destroy.756		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.757		///     * Fungible Mode: The desired number of pieces to burn.758		///     * Re-Fungible Mode: The desired number of pieces to burn.759		#[weight = T::CommonWeightInfo::burn_item()]760		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {761			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);762763			let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;764			if value == 1 {765				<NftTransferBasket<T>>::remove(collection_id, item_id);766				<NftApproveBasket<T>>::remove(collection_id, item_id);767			}768			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?769			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());770			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));771			Ok(post_info)772		}773774		/// Destroy a token on behalf of the owner as a non-owner account.775		///776		/// See also: [`approve`][`Pallet::approve`].777		///778		/// After this method executes, one approval is removed from the total so that779		/// the approved address will not be able to transfer this item again from this owner.780		///781		/// # Permissions782		///783		/// * Collection owner784		/// * Collection admin785		/// * Current token owner786		/// * Address approved by current item owner787		///788		/// # Arguments789		///790		/// * `from`: The owner of the burning item.791		/// * `collection_id`: ID of the collection to which the item belongs.792		/// * `item_id`: ID of item to burn.793		/// * `value`: Number of pieces to burn.794		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.795		///     * Fungible Mode: The desired number of pieces to burn.796		///     * Re-Fungible Mode: The desired number of pieces to burn.797		#[weight = T::CommonWeightInfo::burn_from()]798		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {799			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);800			let budget = budget::Value::new(NESTING_BUDGET);801802			dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))803		}804805		/// Change ownership of the token.806		///807		/// # Permissions808		///809		/// * Collection owner810		/// * Collection admin811		/// * Current token owner812		///813		/// # Arguments814		///815		/// * `recipient`: Address of token recipient.816		/// * `collection_id`: ID of the collection the item belongs to.817		/// * `item_id`: ID of the item.818		///     * Non-Fungible Mode: Required.819		///     * Fungible Mode: Ignored.820		///     * Re-Fungible Mode: Required.821		///822		/// * `value`: Amount to transfer.823		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.824		///     * Fungible Mode: The desired number of pieces to transfer.825		///     * Re-Fungible Mode: The desired number of pieces to transfer.826		#[weight = T::CommonWeightInfo::transfer()]827		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {828			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);829			let budget = budget::Value::new(NESTING_BUDGET);830831			dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))832		}833834		/// Allow a non-permissioned address to transfer or burn an item.835		///836		/// # Permissions837		///838		/// * Collection owner839		/// * Collection admin840		/// * Current item owner841		///842		/// # Arguments843		///844		/// * `spender`: Account to be approved to make specific transactions on non-owned tokens.845		/// * `collection_id`: ID of the collection the item belongs to.846		/// * `item_id`: ID of the item transactions on which are now approved.847		/// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).848		/// Set to 0 to revoke the approval.849		#[weight = T::CommonWeightInfo::approve()]850		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {851			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);852853			dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))854		}855856		/// Allow a non-permissioned address to transfer or burn an item from owner's eth mirror.857		///858		/// # Permissions859		///860		/// * Collection owner861		/// * Collection admin862		/// * Current item owner863		///864		/// # Arguments865		///866		/// * `from`: Owner's account eth mirror867		/// * `to`: Account to be approved to make specific transactions on non-owned tokens.868		/// * `collection_id`: ID of the collection the item belongs to.869		/// * `item_id`: ID of the item transactions on which are now approved.870		/// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).871		/// Set to 0 to revoke the approval.872		#[weight = T::CommonWeightInfo::approve_from()]873		pub fn approve_from(origin, from:T::CrossAccountId, to: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {874			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);875876			dispatch_tx::<T, _>(collection_id, |d| d.approve_from(sender, from, to, item_id, amount))877		}878879		/// Change ownership of an item on behalf of the owner as a non-owner account.880		///881		/// See the [`approve`][`Pallet::approve`] method for additional information.882		///883		/// After this method executes, one approval is removed from the total so that884		/// the approved address will not be able to transfer this item again from this owner.885		///886		/// # Permissions887		///888		/// * Collection owner889		/// * Collection admin890		/// * Current item owner891		/// * Address approved by current item owner892		///893		/// # Arguments894		///895		/// * `from`: Address that currently owns the token.896		/// * `recipient`: Address of the new token-owner-to-be.897		/// * `collection_id`: ID of the collection the item.898		/// * `item_id`: ID of the item to be transferred.899		/// * `value`: Amount to transfer.900		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.901		///     * Fungible Mode: The desired number of pieces to transfer.902		///     * Re-Fungible Mode: The desired number of pieces to transfer.903		#[weight = T::CommonWeightInfo::transfer_from()]904		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {905			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);906			let budget = budget::Value::new(NESTING_BUDGET);907908			dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))909		}910911		/// Set specific limits of a collection. Empty, or None fields mean chain default.912		///913		/// # Permissions914		///915		/// * Collection owner916		/// * Collection admin917		///918		/// # Arguments919		///920		/// * `collection_id`: ID of the modified collection.921		/// * `new_limit`: New limits of the collection. Fields that are not set (None)922		/// will not overwrite the old ones.923		#[weight = <SelfWeightOf<T>>::set_collection_limits()]924		pub fn set_collection_limits(925			origin,926			collection_id: CollectionId,927			new_limit: CollectionLimits,928		) -> DispatchResult {929			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);930			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;931			<PalletCommon<T>>::update_limits(&sender, &mut target_collection, new_limit)932		}933934		/// Set specific permissions of a collection. Empty, or None fields mean chain default.935		///936		/// # Permissions937		///938		/// * Collection owner939		/// * Collection admin940		///941		/// # Arguments942		///943		/// * `collection_id`: ID of the modified collection.944		/// * `new_permission`: New permissions of the collection. Fields that are not set (None)945		/// will not overwrite the old ones.946		#[weight = <SelfWeightOf<T>>::set_collection_limits()]947		pub fn set_collection_permissions(948			origin,949			collection_id: CollectionId,950			new_permission: CollectionPermissions,951		) -> DispatchResult {952			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);953			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;954			<PalletCommon<T>>::update_permissions(955				&sender,956				&mut target_collection,957				new_permission958			)959		}960961		/// Re-partition a refungible token, while owning all of its parts/pieces.962		///963		/// # Permissions964		///965		/// * Token owner (must own every part)966		///967		/// # Arguments968		///969		/// * `collection_id`: ID of the collection the RFT belongs to.970		/// * `token_id`: ID of the RFT.971		/// * `amount`: New number of parts/pieces into which the token shall be partitioned.972		#[weight = T::RefungibleExtensionsWeightInfo::repartition()]973		pub fn repartition(974			origin,975			collection_id: CollectionId,976			token_id: TokenId,977			amount: u128,978		) -> DispatchResultWithPostInfo {979			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);980			dispatch_tx::<T, _>(collection_id, |d| {981				if let Some(refungible_extensions) = d.refungible_extensions() {982					refungible_extensions.repartition(&sender, token_id, amount)983				} else {984					fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)985				}986			})987		}988989		/// Sets or unsets the approval of a given operator.990		///991		/// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.992		///993		/// # Arguments994		///995		/// * `owner`: Token owner996		/// * `operator`: Operator997		/// * `approve`: Should operator status be granted or revoked?998		#[weight = T::CommonWeightInfo::set_allowance_for_all()]999		pub fn set_allowance_for_all(1000			origin,1001			collection_id: CollectionId,1002			operator: T::CrossAccountId,1003			approve: bool,1004		) -> DispatchResultWithPostInfo {1005			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1006			dispatch_tx::<T, _>(collection_id, |d| {1007				d.set_allowance_for_all(sender, operator, approve)1008			})1009		}10101011		/// Repairs a collection if the data was somehow corrupted.1012		///1013		/// # Arguments1014		///1015		/// * `collection_id`: ID of the collection to repair.1016		#[weight = <SelfWeightOf<T>>::force_repair_collection()]1017		pub fn force_repair_collection(1018			origin,1019			collection_id: CollectionId,1020		) -> DispatchResult {1021			ensure_root(origin)?;1022			<PalletCommon<T>>::repair_collection(collection_id)1023		}10241025		/// Repairs a token if the data was somehow corrupted.1026		///1027		/// # Arguments1028		///1029		/// * `collection_id`: ID of the collection the item belongs to.1030		/// * `item_id`: ID of the item.1031		#[weight = T::CommonWeightInfo::force_repair_item()]1032		pub fn force_repair_item(1033			origin,1034			collection_id: CollectionId,1035			item_id: TokenId,1036		) -> DispatchResultWithPostInfo {1037			ensure_root(origin)?;1038			dispatch_tx::<T, _>(collection_id, |d| {1039				d.repair_item(item_id)1040			})1041		}1042	}1043}10441045impl<T: Config> Pallet<T> {1046	/// Force set `sponsor` for `collection`.1047	///1048	/// Differs from [`set_collection_sponsor`][`Pallet::set_collection_sponsor`] in that confirmation1049	/// from the `sponsor` is not required.1050	///1051	/// # Arguments1052	///1053	/// * `sponsor`: ID of the account of the sponsor-to-be.1054	/// * `collection_id`: ID of the modified collection.1055	pub fn force_set_sponsor(sponsor: T::AccountId, collection_id: CollectionId) -> DispatchResult {1056		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1057		target_collection.force_set_sponsor(sponsor.clone())1058	}10591060	/// Force remove `sponsor` for `collection`.1061	///1062	/// Differs from `remove_sponsor` in that1063	/// it doesn't require consent from the `owner` of the collection.1064	///1065	/// # Arguments1066	///1067	/// * `collection_id`: ID of the modified collection.1068	pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult {1069		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1070		target_collection.force_remove_sponsor()1071	}10721073	#[inline(always)]1074	pub(crate) fn destroy_collection_internal(1075		sender: T::CrossAccountId,1076		collection_id: CollectionId,1077	) -> DispatchResult {1078		let collection = <CollectionHandle<T>>::try_get(collection_id)?;1079		collection.check_is_internal()?;10801081		T::CollectionDispatch::destroy(sender, collection)?;10821083		// TODO: basket cleanup should be moved elsewhere1084		// Maybe runtime dispatch.rs should perform it?10851086		let _ = <NftTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1087		let _ = <FungibleTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1088		let _ = <ReFungibleTransferBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);10891090		let _ = <NftApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1091		let _ = <FungibleApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1092		let _ = <RefungibleApproveBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);10931094		Ok(())1095	}1096}
after · pallets/unique/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # 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;7576pub use pallet::*;77use frame_support::pallet_prelude::*;78use frame_system::pallet_prelude::*;79pub mod eth;8081#[cfg(feature = "runtime-benchmarks")]82pub mod benchmarking;83pub mod weights;8485#[frame_support::pallet]86pub mod pallet {87	use super::*;8889	use frame_support::{90		dispatch::DispatchResult,91		ensure, fail,92		weights::{Weight},93		pallet_prelude::{*},94		BoundedVec,95		storage::Key,96	};97	use frame_system::pallet_prelude::*;98	use scale_info::TypeInfo;99	use frame_system::{self as system, ensure_signed, ensure_root};100	use sp_std::{vec, vec::Vec};101	use up_data_structs::{102		MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,103		MAX_PROPERTIES_PER_ITEM, MAX_PROPERTY_KEY_LENGTH, MAX_PROPERTY_VALUE_LENGTH,104		MAX_COLLECTION_PROPERTIES_SIZE, COLLECTION_ADMINS_LIMIT, MAX_TOKEN_PROPERTIES_SIZE,105		CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode,106		TokenId, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,107		PropertyKeyPermission,108	};109	use pallet_evm::account::CrossAccountId;110	use pallet_common::{111		CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,112		dispatch::CollectionDispatch, RefungibleExtensionsWeightInfo,113	};114	use weights::WeightInfo;115116	/// A maximum number of levels of depth in the token nesting tree.117	pub const NESTING_BUDGET: u32 = 5;118119	/// Errors for the common Unique transactions.120	#[pallet::error]121	pub enum Error<T> {122		/// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].123		CollectionDecimalPointLimitExceeded,124		/// Length of items properties must be greater than 0.125		EmptyArgument,126		/// Repertition is only supported by refungible collection.127		RepartitionCalledOnNonRefungibleCollection,128	}129130	/// Configuration trait of this pallet.131	#[pallet::config]132	pub trait Config: frame_system::Config + pallet_common::Config + Sized + TypeInfo {133		/// Weight information for extrinsics in this pallet.134		type WeightInfo: WeightInfo;135136		/// Weight information for common pallet operations.137		type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;138139		/// Weight info information for extra refungible pallet operations.140		type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;141	}142143	#[pallet::pallet]144	pub struct Pallet<T>(_);145146	pub type SelfWeightOf<T> = <T as Config>::WeightInfo;147148	// # Used definitions149	//150	// ## User control levels151	//152	// chain-controlled - key is uncontrolled by user153	//                    i.e autoincrementing index154	//                    can use non-cryptographic hash155	// real - key is controlled by user156	//        but it is hard to generate enough colliding values, i.e owner of signed txs157	//        can use non-cryptographic hash158	// controlled - key is completly controlled by users159	//              i.e maps with mutable keys160	//              should use cryptographic hash161	//162	// ## User control level downgrade reasons163	//164	// ?1 - chain-controlled -> controlled165	//      collections/tokens can be destroyed, resulting in massive holes166	// ?2 - chain-controlled -> controlled167	//      same as ?1, but can be only added, resulting in easier exploitation168	// ?3 - real -> controlled169	//      no confirmation required, so addresses can be easily generated170171	//#region Private members172	/// Used for migrations173	#[pallet::storage]174	pub type ChainVersion<T> = StorageValue<_, u64, ValueQuery>;175	//#endregion176177	//#region Tokens transfer sponosoring rate limit baskets178	/// (Collection id (controlled?2), who created (real))179	/// TODO: Off chain worker should remove from this map when collection gets removed180	#[pallet::storage]181	#[pallet::getter(fn create_item_busket)]182	pub type CreateItemBasket<T: Config> = StorageMap<183		Hasher = Blake2_128Concat,184		Key = (CollectionId, T::AccountId),185		Value = T::BlockNumber,186		QueryKind = OptionQuery,187	>;188	/// Collection id (controlled?2), token id (controlled?2)189	#[pallet::storage]190	#[pallet::getter(fn nft_transfer_basket)]191	pub type NftTransferBasket<T: Config> = StorageDoubleMap<192		Hasher1 = Blake2_128Concat,193		Key1 = CollectionId,194		Hasher2 = Blake2_128Concat,195		Key2 = TokenId,196		Value = T::BlockNumber,197		QueryKind = OptionQuery,198	>;199	/// Collection id (controlled?2), owning user (real)200	#[pallet::storage]201	#[pallet::getter(fn fungible_transfer_basket)]202	pub type FungibleTransferBasket<T: Config> = StorageDoubleMap<203		Hasher1 = Blake2_128Concat,204		Key1 = CollectionId,205		Hasher2 = Twox64Concat,206		Key2 = T::AccountId,207		Value = T::BlockNumber,208		QueryKind = OptionQuery,209	>;210	/// Collection id (controlled?2), token id (controlled?2)211	#[pallet::storage]212	#[pallet::getter(fn refungible_transfer_basket)]213	pub type ReFungibleTransferBasket<T: Config> = StorageNMap<214		Key = (215			Key<Blake2_128Concat, CollectionId>,216			Key<Blake2_128Concat, TokenId>,217			Key<Twox64Concat, T::AccountId>,218		),219		Value = T::BlockNumber,220		QueryKind = OptionQuery,221	>;222	//#endregion223224	/// Last sponsoring of token property setting // todo:doc rephrase this and the following225	#[pallet::storage]226	#[pallet::getter(fn token_property_basket)]227	pub type TokenPropertyBasket<T: Config> = StorageDoubleMap<228		Hasher1 = Blake2_128Concat,229		Key1 = CollectionId,230		Hasher2 = Blake2_128Concat,231		Key2 = TokenId,232		Value = T::BlockNumber,233		QueryKind = OptionQuery,234	>;235236	/// Last sponsoring of NFT approval in a collection237	#[pallet::storage]238	#[pallet::getter(fn nft_approve_basket)]239	pub type NftApproveBasket<T: Config> = StorageDoubleMap<240		Hasher1 = Blake2_128Concat,241		Key1 = CollectionId,242		Hasher2 = Blake2_128Concat,243		Key2 = TokenId,244		Value = T::BlockNumber,245		QueryKind = OptionQuery,246	>;247	/// Last sponsoring of fungible tokens approval in a collection248	#[pallet::storage]249	#[pallet::getter(fn fungible_approve_basket)]250	pub type FungibleApproveBasket<T: Config> = StorageDoubleMap<251		Hasher1 = Blake2_128Concat,252		Key1 = CollectionId,253		Hasher2 = Twox64Concat,254		Key2 = T::AccountId,255		Value = T::BlockNumber,256		QueryKind = OptionQuery,257	>;258	/// Last sponsoring of RFT approval in a collection259	#[pallet::storage]260	#[pallet::getter(fn refungible_approve_basket)]261	pub type RefungibleApproveBasket<T: Config> = StorageNMap<262		Key = (263			Key<Blake2_128Concat, CollectionId>,264			Key<Blake2_128Concat, TokenId>,265			Key<Twox64Concat, T::AccountId>,266		),267		Value = T::BlockNumber,268		QueryKind = OptionQuery,269	>;270271	#[pallet::extra_constants]272	impl<T: Config> Pallet<T> {273		/// A maximum number of levels of depth in the token nesting tree.274		fn nesting_budget() -> u32 {275			NESTING_BUDGET276		}277278		/// Maximal length of a collection name.279		fn max_collection_name_length() -> u32 {280			MAX_COLLECTION_NAME_LENGTH281		}282283		/// Maximal length of a collection description.284		fn max_collection_description_length() -> u32 {285			MAX_COLLECTION_DESCRIPTION_LENGTH286		}287288		/// Maximal length of a token prefix.289		fn max_token_prefix_length() -> u32 {290			MAX_TOKEN_PREFIX_LENGTH291		}292293		/// Maximum admins per collection.294		fn collection_admins_limit() -> u32 {295			COLLECTION_ADMINS_LIMIT296		}297298		/// Maximal length of a property key.299		fn max_property_key_length() -> u32 {300			MAX_PROPERTY_KEY_LENGTH301		}302303		/// Maximal length of a property value.304		fn max_property_value_length() -> u32 {305			MAX_PROPERTY_VALUE_LENGTH306		}307308		/// A maximum number of token properties.309		fn max_properties_per_item() -> u32 {310			MAX_PROPERTIES_PER_ITEM311		}312313		/// Maximum size for all collection properties.314		fn max_collection_properties_size() -> u32 {315			MAX_COLLECTION_PROPERTIES_SIZE316		}317318		/// Maximum size of all token properties.319		fn max_token_properties_size() -> u32 {320			MAX_TOKEN_PROPERTIES_SIZE321		}322323		/// Default NFT collection limit.324		fn nft_default_collection_limits() -> CollectionLimits {325			CollectionLimits::with_default_limits(CollectionMode::NFT)326		}327328		/// Default RFT collection limit.329		fn rft_default_collection_limits() -> CollectionLimits {330			CollectionLimits::with_default_limits(CollectionMode::ReFungible)331		}332333		/// Default FT collection limit.334		fn ft_default_collection_limits() -> CollectionLimits {335			CollectionLimits::with_default_limits(CollectionMode::Fungible(0))336		}337	}338339	/// Type alias to Pallet, to be used by construct_runtime.340	#[pallet::call]341	impl<T: Config> Pallet<T> {342		/// Create a collection of tokens.343		///344		/// Each Token may have multiple properties encoded as an array of bytes345		/// of certain length. The initial owner of the collection is set346		/// to the address that signed the transaction and can be changed later.347		///348		/// Prefer the more advanced [`create_collection_ex`][`Pallet::create_collection_ex`] instead.349		///350		/// # Permissions351		///352		/// * Anyone - becomes the owner of the new collection.353		///354		/// # Arguments355		///356		/// * `collection_name`: Wide-character string with collection name357		/// (limit [`MAX_COLLECTION_NAME_LENGTH`]).358		/// * `collection_description`: Wide-character string with collection description359		/// (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).360		/// * `token_prefix`: Byte string containing the token prefix to mark a collection361		/// to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).362		/// * `mode`: Type of items stored in the collection and type dependent data.363		///364		/// returns collection ID365		///366		/// Deprecated: `create_collection_ex` is more up-to-date and advanced, prefer it instead.367		#[pallet::call_index(0)]368		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]369		pub fn create_collection(370			origin: OriginFor<T>,371			collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,372			collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,373			token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,374			mode: CollectionMode,375		) -> DispatchResult {376			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {377				name: collection_name,378				description: collection_description,379				token_prefix,380				mode,381				..Default::default()382			};383			Self::create_collection_ex(origin, data)384		}385386		/// Create a collection with explicit parameters.387		///388		/// Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.389		///390		/// # Permissions391		///392		/// * Anyone - becomes the owner of the new collection.393		///394		/// # Arguments395		///396		/// * `data`: Explicit data of a collection used for its creation.397		#[pallet::call_index(1)]398		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]399		pub fn create_collection_ex(400			origin: OriginFor<T>,401			data: CreateCollectionData<T::AccountId>,402		) -> DispatchResult {403			let sender = ensure_signed(origin)?;404405			// =========406			let sender = T::CrossAccountId::from_sub(sender);407			let _id =408				T::CollectionDispatch::create(sender.clone(), sender, data, Default::default())?;409410			Ok(())411		}412413		/// Destroy a collection if no tokens exist within.414		///415		/// # Permissions416		///417		/// * Collection owner418		///419		/// # Arguments420		///421		/// * `collection_id`: Collection to destroy.422		#[pallet::call_index(2)]423		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]424		pub fn destroy_collection(425			origin: OriginFor<T>,426			collection_id: CollectionId,427		) -> DispatchResult {428			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);429430			Self::destroy_collection_internal(sender, collection_id)431		}432433		/// Add an address to allow list.434		///435		/// # Permissions436		///437		/// * Collection owner438		/// * Collection admin439		///440		/// # Arguments441		///442		/// * `collection_id`: ID of the modified collection.443		/// * `address`: ID of the address to be added to the allowlist.444		#[pallet::call_index(3)]445		#[pallet::weight(<SelfWeightOf<T>>::add_to_allow_list())]446		pub fn add_to_allow_list(447			origin: OriginFor<T>,448			collection_id: CollectionId,449			address: T::CrossAccountId,450		) -> DispatchResult {451			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);452			let collection = <CollectionHandle<T>>::try_get(collection_id)?;453			collection.check_is_internal()?;454455			<PalletCommon<T>>::toggle_allowlist(&collection, &sender, &address, true)?;456457			Ok(())458		}459460		/// Remove an address from allow list.461		///462		/// # Permissions463		///464		/// * Collection owner465		/// * Collection admin466		///467		/// # Arguments468		///469		/// * `collection_id`: ID of the modified collection.470		/// * `address`: ID of the address to be removed from the allowlist.471		#[pallet::call_index(4)]472		#[pallet::weight(<SelfWeightOf<T>>::remove_from_allow_list())]473		pub fn remove_from_allow_list(474			origin: OriginFor<T>,475			collection_id: CollectionId,476			address: T::CrossAccountId,477		) -> DispatchResult {478			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);479			let collection = <CollectionHandle<T>>::try_get(collection_id)?;480			collection.check_is_internal()?;481482			<PalletCommon<T>>::toggle_allowlist(&collection, &sender, &address, false)?;483484			Ok(())485		}486487		/// Change the owner of the collection.488		///489		/// # Permissions490		///491		/// * Collection owner492		///493		/// # Arguments494		///495		/// * `collection_id`: ID of the modified collection.496		/// * `new_owner`: ID of the account that will become the owner.497		#[pallet::call_index(5)]498		#[pallet::weight(<SelfWeightOf<T>>::change_collection_owner())]499		pub fn change_collection_owner(500			origin: OriginFor<T>,501			collection_id: CollectionId,502			new_owner: T::AccountId,503		) -> DispatchResult {504			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);505			let new_owner = T::CrossAccountId::from_sub(new_owner);506			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;507			target_collection.change_owner(sender, new_owner.clone())508		}509510		/// Add an admin to a collection.511		///512		/// NFT Collection can be controlled by multiple admin addresses513		/// (some which can also be servers, for example). Admins can issue514		/// and burn NFTs, as well as add and remove other admins,515		/// but cannot change NFT or Collection ownership.516		///517		/// # Permissions518		///519		/// * Collection owner520		/// * Collection admin521		///522		/// # Arguments523		///524		/// * `collection_id`: ID of the Collection to add an admin for.525		/// * `new_admin`: Address of new admin to add.526		#[pallet::call_index(6)]527		#[pallet::weight(<SelfWeightOf<T>>::add_collection_admin())]528		pub fn add_collection_admin(529			origin: OriginFor<T>,530			collection_id: CollectionId,531			new_admin_id: T::CrossAccountId,532		) -> DispatchResult {533			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);534			let collection = <CollectionHandle<T>>::try_get(collection_id)?;535			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)536		}537538		/// Remove admin of a collection.539		///540		/// An admin address can remove itself. List of admins may become empty,541		/// in which case only Collection Owner will be able to add an Admin.542		///543		/// # Permissions544		///545		/// * Collection owner546		/// * Collection admin547		///548		/// # Arguments549		///550		/// * `collection_id`: ID of the collection to remove the admin for.551		/// * `account_id`: Address of the admin to remove.552		#[pallet::call_index(7)]553		#[pallet::weight(<SelfWeightOf<T>>::remove_collection_admin())]554		pub fn remove_collection_admin(555			origin: OriginFor<T>,556			collection_id: CollectionId,557			account_id: T::CrossAccountId,558		) -> DispatchResult {559			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);560			let collection = <CollectionHandle<T>>::try_get(collection_id)?;561			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)562		}563564		/// Set (invite) a new collection sponsor.565		///566		/// If successful, confirmation from the sponsor-to-be will be pending.567		///568		/// # Permissions569		///570		/// * Collection owner571		/// * Collection admin572		///573		/// # Arguments574		///575		/// * `collection_id`: ID of the modified collection.576		/// * `new_sponsor`: ID of the account of the sponsor-to-be.577		#[pallet::call_index(8)]578		#[pallet::weight(<SelfWeightOf<T>>::set_collection_sponsor())]579		pub fn set_collection_sponsor(580			origin: OriginFor<T>,581			collection_id: CollectionId,582			new_sponsor: T::AccountId,583		) -> DispatchResult {584			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);585			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;586			target_collection.set_sponsor(&sender, new_sponsor.clone())587		}588589		/// Confirm own sponsorship of a collection, becoming the sponsor.590		///591		/// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].592		/// Sponsor can pay the fees of a transaction instead of the sender,593		/// but only within specified limits.594		///595		/// # Permissions596		///597		/// * Sponsor-to-be598		///599		/// # Arguments600		///601		/// * `collection_id`: ID of the collection with the pending sponsor.602		#[pallet::call_index(9)]603		#[pallet::weight(<SelfWeightOf<T>>::confirm_sponsorship())]604		pub fn confirm_sponsorship(605			origin: OriginFor<T>,606			collection_id: CollectionId,607		) -> DispatchResult {608			let sender = ensure_signed(origin)?;609			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;610			target_collection.confirm_sponsorship(&sender)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		#[pallet::call_index(10)]623		#[pallet::weight(<SelfWeightOf<T>>::remove_collection_sponsor())]624		pub fn remove_collection_sponsor(625			origin: OriginFor<T>,626			collection_id: CollectionId,627		) -> DispatchResult {628			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);629			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;630			target_collection.remove_sponsor(&sender)631		}632633		/// Mint an item within a collection.634		///635		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].636		///637		/// # Permissions638		///639		/// * Collection owner640		/// * Collection admin641		/// * Anyone if642		///     * Allow List is enabled, and643		///     * Address is added to allow list, and644		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])645		///646		/// # Arguments647		///648		/// * `collection_id`: ID of the collection to which an item would belong.649		/// * `owner`: Address of the initial owner of the item.650		/// * `data`: Token data describing the item to store on chain.651		#[pallet::call_index(11)]652		#[pallet::weight(T::CommonWeightInfo::create_item(&data))]653		pub fn create_item(654			origin: OriginFor<T>,655			collection_id: CollectionId,656			owner: T::CrossAccountId,657			data: CreateItemData,658		) -> DispatchResultWithPostInfo {659			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);660			let budget = budget::Value::new(NESTING_BUDGET);661662			dispatch_tx::<T, _>(collection_id, |d| {663				d.create_item(sender, owner, data, &budget)664			})665		}666667		/// Create multiple items within a collection.668		///669		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].670		///671		/// # Permissions672		///673		/// * Collection owner674		/// * Collection admin675		/// * Anyone if676		///     * Allow List is enabled, and677		///     * Address is added to the allow list, and678		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])679		///680		/// # Arguments681		///682		/// * `collection_id`: ID of the collection to which the tokens would belong.683		/// * `owner`: Address of the initial owner of the tokens.684		/// * `items_data`: Vector of data describing each item to be created.685		#[pallet::call_index(12)]686		#[pallet::weight(T::CommonWeightInfo::create_multiple_items(&items_data))]687		pub fn create_multiple_items(688			origin: OriginFor<T>,689			collection_id: CollectionId,690			owner: T::CrossAccountId,691			items_data: Vec<CreateItemData>,692		) -> DispatchResultWithPostInfo {693			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);694			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);695			let budget = budget::Value::new(NESTING_BUDGET);696697			dispatch_tx::<T, _>(collection_id, |d| {698				d.create_multiple_items(sender, owner, items_data, &budget)699			})700		}701702		/// Add or change collection properties.703		///704		/// # Permissions705		///706		/// * Collection owner707		/// * Collection admin708		///709		/// # Arguments710		///711		/// * `collection_id`: ID of the modified collection.712		/// * `properties`: Vector of key-value pairs stored as the collection's metadata.713		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.714		#[pallet::call_index(13)]715		#[pallet::weight(T::CommonWeightInfo::set_collection_properties(properties.len() as u32))]716		pub fn set_collection_properties(717			origin: OriginFor<T>,718			collection_id: CollectionId,719			properties: Vec<Property>,720		) -> DispatchResultWithPostInfo {721			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);722723			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);724725			dispatch_tx::<T, _>(collection_id, |d| {726				d.set_collection_properties(sender, properties)727			})728		}729730		/// Delete specified collection properties.731		///732		/// # Permissions733		///734		/// * Collection Owner735		/// * Collection Admin736		///737		/// # Arguments738		///739		/// * `collection_id`: ID of the modified collection.740		/// * `property_keys`: Vector of keys of the properties to be deleted.741		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.742		#[pallet::call_index(14)]743		#[pallet::weight(T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32))]744		pub fn delete_collection_properties(745			origin: OriginFor<T>,746			collection_id: CollectionId,747			property_keys: Vec<PropertyKey>,748		) -> DispatchResultWithPostInfo {749			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);750751			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);752753			dispatch_tx::<T, _>(collection_id, |d| {754				d.delete_collection_properties(&sender, property_keys)755			})756		}757758		/// Add or change token properties according to collection's permissions.759		/// Currently properties only work with NFTs.760		///761		/// # Permissions762		///763		/// * Depends on collection's token property permissions and specified property mutability:764		/// 	* Collection owner765		/// 	* Collection admin766		/// 	* Token owner767		///768		/// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].769		///770		/// # Arguments771		///772		/// * `collection_id: ID of the collection to which the token belongs.773		/// * `token_id`: ID of the modified token.774		/// * `properties`: Vector of key-value pairs stored as the token's metadata.775		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.776		#[pallet::call_index(15)]777		#[pallet::weight(T::CommonWeightInfo::set_token_properties(properties.len() as u32))]778		pub fn set_token_properties(779			origin: OriginFor<T>,780			collection_id: CollectionId,781			token_id: TokenId,782			properties: Vec<Property>,783		) -> DispatchResultWithPostInfo {784			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);785786			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);787			let budget = budget::Value::new(NESTING_BUDGET);788789			dispatch_tx::<T, _>(collection_id, |d| {790				d.set_token_properties(sender, token_id, properties, &budget)791			})792		}793794		/// Delete specified token properties. Currently properties only work with NFTs.795		///796		/// # Permissions797		///798		/// * Depends on collection's token property permissions and specified property mutability:799		/// 	* Collection owner800		/// 	* Collection admin801		/// 	* Token owner802		///803		/// # Arguments804		///805		/// * `collection_id`: ID of the collection to which the token belongs.806		/// * `token_id`: ID of the modified token.807		/// * `property_keys`: Vector of keys of the properties to be deleted.808		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.809		#[pallet::call_index(16)]810		#[pallet::weight(T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32))]811		pub fn delete_token_properties(812			origin: OriginFor<T>,813			collection_id: CollectionId,814			token_id: TokenId,815			property_keys: Vec<PropertyKey>,816		) -> DispatchResultWithPostInfo {817			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);818819			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);820			let budget = budget::Value::new(NESTING_BUDGET);821822			dispatch_tx::<T, _>(collection_id, |d| {823				d.delete_token_properties(sender, token_id, property_keys, &budget)824			})825		}826827		/// Add or change token property permissions of a collection.828		///829		/// Without a permission for a particular key, a property with that key830		/// cannot be created in a token.831		///832		/// # Permissions833		///834		/// * Collection owner835		/// * Collection admin836		///837		/// # Arguments838		///839		/// * `collection_id`: ID of the modified collection.840		/// * `property_permissions`: Vector of permissions for property keys.841		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.842		#[pallet::call_index(17)]843		#[pallet::weight(T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32))]844		pub fn set_token_property_permissions(845			origin: OriginFor<T>,846			collection_id: CollectionId,847			property_permissions: Vec<PropertyKeyPermission>,848		) -> DispatchResultWithPostInfo {849			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);850851			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);852853			dispatch_tx::<T, _>(collection_id, |d| {854				d.set_token_property_permissions(&sender, property_permissions)855			})856		}857858		/// Create multiple items within a collection with explicitly specified initial parameters.859		///860		/// # Permissions861		///862		/// * Collection owner863		/// * Collection admin864		/// * Anyone if865		///     * Allow List is enabled, and866		///     * Address is added to allow list, and867		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])868		///869		/// # Arguments870		///871		/// * `collection_id`: ID of the collection to which the tokens would belong.872		/// * `data`: Explicit item creation data.873		#[pallet::call_index(18)]874		#[pallet::weight(T::CommonWeightInfo::create_multiple_items_ex(&data))]875		pub fn create_multiple_items_ex(876			origin: OriginFor<T>,877			collection_id: CollectionId,878			data: CreateItemExData<T::CrossAccountId>,879		) -> DispatchResultWithPostInfo {880			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);881			let budget = budget::Value::new(NESTING_BUDGET);882883			dispatch_tx::<T, _>(collection_id, |d| {884				d.create_multiple_items_ex(sender, data, &budget)885			})886		}887888		/// Completely allow or disallow transfers for a particular collection.889		///890		/// # Permissions891		///892		/// * Collection owner893		///894		/// # Arguments895		///896		/// * `collection_id`: ID of the collection.897		/// * `value`: New value of the flag, are transfers allowed?898		#[pallet::call_index(19)]899		#[pallet::weight(<SelfWeightOf<T>>::set_transfers_enabled_flag())]900		pub fn set_transfers_enabled_flag(901			origin: OriginFor<T>,902			collection_id: CollectionId,903			value: bool,904		) -> DispatchResult {905			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);906			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;907			target_collection.check_is_internal()?;908			target_collection.check_is_owner(&sender)?;909910			// =========911912			target_collection.limits.transfers_enabled = Some(value);913			target_collection.save()914		}915916		/// Destroy an item.917		///918		/// # Permissions919		///920		/// * Collection owner921		/// * Collection admin922		/// * Current item owner923		///924		/// # Arguments925		///926		/// * `collection_id`: ID of the collection to which the item belongs.927		/// * `item_id`: ID of item to burn.928		/// * `value`: Number of pieces of the item to destroy.929		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.930		///     * Fungible Mode: The desired number of pieces to burn.931		///     * Re-Fungible Mode: The desired number of pieces to burn.932		#[pallet::call_index(20)]933		#[pallet::weight(T::CommonWeightInfo::burn_item())]934		pub fn burn_item(935			origin: OriginFor<T>,936			collection_id: CollectionId,937			item_id: TokenId,938			value: u128,939		) -> DispatchResultWithPostInfo {940			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);941942			let post_info =943				dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;944			if value == 1 {945				<NftTransferBasket<T>>::remove(collection_id, item_id);946				<NftApproveBasket<T>>::remove(collection_id, item_id);947			}948			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?949			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());950			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));951			Ok(post_info)952		}953954		/// Destroy a token on behalf of the owner as a non-owner account.955		///956		/// See also: [`approve`][`Pallet::approve`].957		///958		/// After this method executes, one approval is removed from the total so that959		/// the approved address will not be able to transfer this item again from this owner.960		///961		/// # Permissions962		///963		/// * Collection owner964		/// * Collection admin965		/// * Current token owner966		/// * Address approved by current item owner967		///968		/// # Arguments969		///970		/// * `from`: The owner of the burning item.971		/// * `collection_id`: ID of the collection to which the item belongs.972		/// * `item_id`: ID of item to burn.973		/// * `value`: Number of pieces to burn.974		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.975		///     * Fungible Mode: The desired number of pieces to burn.976		///     * Re-Fungible Mode: The desired number of pieces to burn.977		#[pallet::call_index(21)]978		#[pallet::weight(T::CommonWeightInfo::burn_from())]979		pub fn burn_from(980			origin: OriginFor<T>,981			collection_id: CollectionId,982			from: T::CrossAccountId,983			item_id: TokenId,984			value: u128,985		) -> DispatchResultWithPostInfo {986			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);987			let budget = budget::Value::new(NESTING_BUDGET);988989			dispatch_tx::<T, _>(collection_id, |d| {990				d.burn_from(sender, from, item_id, value, &budget)991			})992		}993994		/// Change ownership of the token.995		///996		/// # Permissions997		///998		/// * Collection owner999		/// * Collection admin1000		/// * Current token owner1001		///1002		/// # Arguments1003		///1004		/// * `recipient`: Address of token recipient.1005		/// * `collection_id`: ID of the collection the item belongs to.1006		/// * `item_id`: ID of the item.1007		///     * Non-Fungible Mode: Required.1008		///     * Fungible Mode: Ignored.1009		///     * Re-Fungible Mode: Required.1010		///1011		/// * `value`: Amount to transfer.1012		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1013		///     * Fungible Mode: The desired number of pieces to transfer.1014		///     * Re-Fungible Mode: The desired number of pieces to transfer.1015		#[pallet::call_index(22)]1016		#[pallet::weight(T::CommonWeightInfo::transfer())]1017		pub fn transfer(1018			origin: OriginFor<T>,1019			recipient: T::CrossAccountId,1020			collection_id: CollectionId,1021			item_id: TokenId,1022			value: u128,1023		) -> DispatchResultWithPostInfo {1024			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1025			let budget = budget::Value::new(NESTING_BUDGET);10261027			dispatch_tx::<T, _>(collection_id, |d| {1028				d.transfer(sender, recipient, item_id, value, &budget)1029			})1030		}10311032		/// Allow a non-permissioned address to transfer or burn an item.1033		///1034		/// # Permissions1035		///1036		/// * Collection owner1037		/// * Collection admin1038		/// * Current item owner1039		///1040		/// # Arguments1041		///1042		/// * `spender`: Account to be approved to make specific transactions on non-owned tokens.1043		/// * `collection_id`: ID of the collection the item belongs to.1044		/// * `item_id`: ID of the item transactions on which are now approved.1045		/// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).1046		/// Set to 0 to revoke the approval.1047		#[pallet::call_index(23)]1048		#[pallet::weight(T::CommonWeightInfo::approve())]1049		pub fn approve(1050			origin: OriginFor<T>,1051			spender: T::CrossAccountId,1052			collection_id: CollectionId,1053			item_id: TokenId,1054			amount: u128,1055		) -> DispatchResultWithPostInfo {1056			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10571058			dispatch_tx::<T, _>(collection_id, |d| {1059				d.approve(sender, spender, item_id, amount)1060			})1061		}10621063		/// Allow a non-permissioned address to transfer or burn an item from owner's eth mirror.1064		///1065		/// # Permissions1066		///1067		/// * Collection owner1068		/// * Collection admin1069		/// * Current item owner1070		///1071		/// # Arguments1072		///1073		/// * `from`: Owner's account eth mirror1074		/// * `to`: Account to be approved to make specific transactions on non-owned tokens.1075		/// * `collection_id`: ID of the collection the item belongs to.1076		/// * `item_id`: ID of the item transactions on which are now approved.1077		/// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).1078		/// Set to 0 to revoke the approval.1079		#[pallet::call_index(24)]1080		#[pallet::weight(T::CommonWeightInfo::approve_from())]1081		pub fn approve_from(1082			origin: OriginFor<T>,1083			from: T::CrossAccountId,1084			to: T::CrossAccountId,1085			collection_id: CollectionId,1086			item_id: TokenId,1087			amount: u128,1088		) -> DispatchResultWithPostInfo {1089			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10901091			dispatch_tx::<T, _>(collection_id, |d| {1092				d.approve_from(sender, from, to, item_id, amount)1093			})1094		}10951096		/// Change ownership of an item on behalf of the owner as a non-owner account.1097		///1098		/// See the [`approve`][`Pallet::approve`] method for additional information.1099		///1100		/// After this method executes, one approval is removed from the total so that1101		/// the approved address will not be able to transfer this item again from this owner.1102		///1103		/// # Permissions1104		///1105		/// * Collection owner1106		/// * Collection admin1107		/// * Current item owner1108		/// * Address approved by current item owner1109		///1110		/// # Arguments1111		///1112		/// * `from`: Address that currently owns the token.1113		/// * `recipient`: Address of the new token-owner-to-be.1114		/// * `collection_id`: ID of the collection the item.1115		/// * `item_id`: ID of the item to be transferred.1116		/// * `value`: Amount to transfer.1117		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1118		///     * Fungible Mode: The desired number of pieces to transfer.1119		///     * Re-Fungible Mode: The desired number of pieces to transfer.1120		#[pallet::call_index(25)]1121		#[pallet::weight(T::CommonWeightInfo::transfer_from())]1122		pub fn transfer_from(1123			origin: OriginFor<T>,1124			from: T::CrossAccountId,1125			recipient: T::CrossAccountId,1126			collection_id: CollectionId,1127			item_id: TokenId,1128			value: u128,1129		) -> DispatchResultWithPostInfo {1130			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1131			let budget = budget::Value::new(NESTING_BUDGET);11321133			dispatch_tx::<T, _>(collection_id, |d| {1134				d.transfer_from(sender, from, recipient, item_id, value, &budget)1135			})1136		}11371138		/// Set specific limits of a collection. Empty, or None fields mean chain default.1139		///1140		/// # Permissions1141		///1142		/// * Collection owner1143		/// * Collection admin1144		///1145		/// # Arguments1146		///1147		/// * `collection_id`: ID of the modified collection.1148		/// * `new_limit`: New limits of the collection. Fields that are not set (None)1149		/// will not overwrite the old ones.1150		#[pallet::call_index(26)]1151		#[pallet::weight(<SelfWeightOf<T>>::set_collection_limits())]1152		pub fn set_collection_limits(1153			origin: OriginFor<T>,1154			collection_id: CollectionId,1155			new_limit: CollectionLimits,1156		) -> DispatchResult {1157			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1158			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1159			<PalletCommon<T>>::update_limits(&sender, &mut target_collection, new_limit)1160		}11611162		/// Set specific permissions of a collection. Empty, or None fields mean chain default.1163		///1164		/// # Permissions1165		///1166		/// * Collection owner1167		/// * Collection admin1168		///1169		/// # Arguments1170		///1171		/// * `collection_id`: ID of the modified collection.1172		/// * `new_permission`: New permissions of the collection. Fields that are not set (None)1173		/// will not overwrite the old ones.1174		#[pallet::call_index(27)]1175		#[pallet::weight(<SelfWeightOf<T>>::set_collection_limits())]1176		pub fn set_collection_permissions(1177			origin: OriginFor<T>,1178			collection_id: CollectionId,1179			new_permission: CollectionPermissions,1180		) -> DispatchResult {1181			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1182			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1183			<PalletCommon<T>>::update_permissions(&sender, &mut target_collection, new_permission)1184		}11851186		/// Re-partition a refungible token, while owning all of its parts/pieces.1187		///1188		/// # Permissions1189		///1190		/// * Token owner (must own every part)1191		///1192		/// # Arguments1193		///1194		/// * `collection_id`: ID of the collection the RFT belongs to.1195		/// * `token_id`: ID of the RFT.1196		/// * `amount`: New number of parts/pieces into which the token shall be partitioned.1197		#[pallet::call_index(28)]1198		#[pallet::weight(T::RefungibleExtensionsWeightInfo::repartition())]1199		pub fn repartition(1200			origin: OriginFor<T>,1201			collection_id: CollectionId,1202			token_id: TokenId,1203			amount: u128,1204		) -> DispatchResultWithPostInfo {1205			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1206			dispatch_tx::<T, _>(collection_id, |d| {1207				if let Some(refungible_extensions) = d.refungible_extensions() {1208					refungible_extensions.repartition(&sender, token_id, amount)1209				} else {1210					fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1211				}1212			})1213		}12141215		/// Sets or unsets the approval of a given operator.1216		///1217		/// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.1218		///1219		/// # Arguments1220		///1221		/// * `owner`: Token owner1222		/// * `operator`: Operator1223		/// * `approve`: Should operator status be granted or revoked?1224		#[pallet::call_index(29)]1225		#[pallet::weight(T::CommonWeightInfo::set_allowance_for_all())]1226		pub fn set_allowance_for_all(1227			origin: OriginFor<T>,1228			collection_id: CollectionId,1229			operator: T::CrossAccountId,1230			approve: bool,1231		) -> DispatchResultWithPostInfo {1232			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1233			dispatch_tx::<T, _>(collection_id, |d| {1234				d.set_allowance_for_all(sender, operator, approve)1235			})1236		}12371238		/// Repairs a collection if the data was somehow corrupted.1239		///1240		/// # Arguments1241		///1242		/// * `collection_id`: ID of the collection to repair.1243		#[pallet::call_index(30)]1244		#[pallet::weight(<SelfWeightOf<T>>::force_repair_collection())]1245		pub fn force_repair_collection(1246			origin: OriginFor<T>,1247			collection_id: CollectionId,1248		) -> DispatchResult {1249			ensure_root(origin)?;1250			<PalletCommon<T>>::repair_collection(collection_id)1251		}12521253		/// Repairs a token if the data was somehow corrupted.1254		///1255		/// # Arguments1256		///1257		/// * `collection_id`: ID of the collection the item belongs to.1258		/// * `item_id`: ID of the item.1259		#[pallet::call_index(31)]1260		#[pallet::weight(T::CommonWeightInfo::force_repair_item())]1261		pub fn force_repair_item(1262			origin: OriginFor<T>,1263			collection_id: CollectionId,1264			item_id: TokenId,1265		) -> DispatchResultWithPostInfo {1266			ensure_root(origin)?;1267			dispatch_tx::<T, _>(collection_id, |d| d.repair_item(item_id))1268		}1269	}12701271	impl<T: Config> Pallet<T> {1272		/// Force set `sponsor` for `collection`.1273		///1274		/// Differs from [`set_collection_sponsor`][`Pallet::set_collection_sponsor`] in that confirmation1275		/// from the `sponsor` is not required.1276		///1277		/// # Arguments1278		///1279		/// * `sponsor`: ID of the account of the sponsor-to-be.1280		/// * `collection_id`: ID of the modified collection.1281		pub fn force_set_sponsor(1282			sponsor: T::AccountId,1283			collection_id: CollectionId,1284		) -> DispatchResult {1285			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1286			target_collection.force_set_sponsor(sponsor.clone())1287		}12881289		/// Force remove `sponsor` for `collection`.1290		///1291		/// Differs from `remove_sponsor` in that1292		/// it doesn't require consent from the `owner` of the collection.1293		///1294		/// # Arguments1295		///1296		/// * `collection_id`: ID of the modified collection.1297		pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult {1298			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1299			target_collection.force_remove_sponsor()1300		}13011302		#[inline(always)]1303		pub(crate) fn destroy_collection_internal(1304			sender: T::CrossAccountId,1305			collection_id: CollectionId,1306		) -> DispatchResult {1307			let collection = <CollectionHandle<T>>::try_get(collection_id)?;1308			collection.check_is_internal()?;13091310			T::CollectionDispatch::destroy(sender, collection)?;13111312			// TODO: basket cleanup should be moved elsewhere1313			// Maybe runtime dispatch.rs should perform it?13141315			let _ = <NftTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1316			let _ = <FungibleTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1317			let _ = <ReFungibleTransferBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);13181319			let _ = <NftApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1320			let _ = <FungibleApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1321			let _ = <RefungibleApproveBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);13221323			Ok(())1324		}1325	}1326}
modifiedruntime/common/sponsoring.rsdiffbeforeafterboth
--- a/runtime/common/sponsoring.rs
+++ b/runtime/common/sponsoring.rs
@@ -18,7 +18,6 @@
 use up_sponsorship::SponsorshipHandler;
 use frame_support::{
 	traits::{IsSubType},
-	storage::{StorageMap, StorageDoubleMap, StorageNMap},
 };
 use up_data_structs::{
 	CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, NFT_SPONSOR_TRANSFER_TIMEOUT,