git.delta.rocks / unique-network / refs/commits / 9bfc203a27ca

difftreelog

source

pallets/unique/src/lib.rs35.2 KiBsourcehistory
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9	clippy::too_many_arguments,10	clippy::unnecessary_mut_passed,11	clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19	construct_runtime, decl_module, decl_storage, decl_error, decl_event,20	dispatch::DispatchResult,21	ensure, fail, parameter_types,22	traits::{23		ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,24		IsSubType, WithdrawReasons,25	},26	weights::{27		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29		WeightToFeePolynomial, DispatchClass,30	},31	StorageValue, transactional,32	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},33	BoundedVec,34};35use scale_info::TypeInfo;36use frame_system::{self as system, ensure_signed};37use sp_runtime::{sp_std::prelude::Vec};38use up_data_structs::{39	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,40	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,41	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,42	NFT_SPONSOR_TRANSFER_TIMEOUT, MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH,43	MAX_TOKEN_PREFIX_LENGTH, AccessMode, Collection, CreateItemData, CollectionLimits,44	CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,45	CustomDataLimit,46};47use pallet_common::{48	account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,49	CommonWeightInfo,50};51use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};52use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};53use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};5455#[cfg(test)]56mod mock;5758#[cfg(test)]59mod tests;6061mod eth;62mod sponsorship;63pub use sponsorship::UniqueSponsorshipHandler;64pub use eth::sponsoring::UniqueEthSponsorshipHandler;6566pub use eth::UniqueErcSupport;6768pub mod common;69use common::CommonWeights;70pub mod dispatch;71use dispatch::dispatch_call;7273#[cfg(feature = "runtime-benchmarks")]74mod benchmarking;75pub mod weights;76use weights::WeightInfo;7778decl_error! {79	/// Error for non-fungible-token module.80	pub enum Error for Module<T: Config> {81		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.82		CollectionDecimalPointLimitExceeded,83		/// This address is not set as sponsor, use setCollectionSponsor first.84		ConfirmUnsetSponsorFail,85		/// Length of items properties must be greater than 0.86		EmptyArgument,87		/// Collection limit bounds per collection exceeded88		CollectionLimitBoundsExceeded,89		/// Tried to enable permissions which are only permitted to be disabled90		OwnerPermissionsCantBeReverted,91	}92}9394pub trait Config:95	system::Config96	+ pallet_evm_coder_substrate::Config97	+ pallet_common::Config98	+ pallet_nonfungible::Config99	+ pallet_refungible::Config100	+ pallet_fungible::Config101	+ Sized102	+ TypeInfo103{104	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;105106	/// Weight information for extrinsics in this pallet.107	type WeightInfo: WeightInfo;108}109110decl_event! {111	pub enum Event<T>112	where113		<T as frame_system::Config>::AccountId,114		<T as pallet_common::Config>::CrossAccountId,115	{116		/// Collection sponsor was removed117		///118		/// # Arguments119		///120		/// * collection_id: Globally unique collection identifier.121		CollectionSponsorRemoved(CollectionId),122123		/// Collection admin was added124		///125		/// # Arguments126		///127		/// * collection_id: Globally unique collection identifier.128		///129		/// * admin:  Admin address.130		CollectionAdminAdded(CollectionId, CrossAccountId),131132		/// Collection owned was change133		///134		/// # Arguments135		///136		/// * collection_id: Globally unique collection identifier.137		///138		/// * owner:  New owner address.139		CollectionOwnedChanged(CollectionId, AccountId),140141		/// Collection sponsor was set142		///143		/// # Arguments144		///145		/// * collection_id: Globally unique collection identifier.146		///147		/// * owner:  New sponsor address.148		CollectionSponsorSet(CollectionId, AccountId),149150		/// const on chain schema was set151		///152		/// # Arguments153		///154		/// * collection_id: Globally unique collection identifier.155		ConstOnChainSchemaSet(CollectionId),156157		/// New sponsor was confirm158		///159		/// # Arguments160		///161		/// * collection_id: Globally unique collection identifier.162		///163		/// * sponsor:  New sponsor address.164		SponsorshipConfirmed(CollectionId, AccountId),165166		/// Collection admin was removed167		///168		/// # Arguments169		///170		/// * collection_id: Globally unique collection identifier.171		///172		/// * admin:  Admin address.173		CollectionAdminRemoved(CollectionId, CrossAccountId),174175		/// Address was remove from allow list176		///177		/// # Arguments178		///179		/// * collection_id: Globally unique collection identifier.180		///181		/// * user:  Address.182		AllowListAddressRemoved(CollectionId, CrossAccountId),183184		/// Address was add to allow list185		///186		/// # Arguments187		///188		/// * collection_id: Globally unique collection identifier.189		///190		/// * user:  Address.191		AllowListAddressAdded(CollectionId, CrossAccountId),192193		/// Collection limits was set194		///195		/// # Arguments196		///197		/// * collection_id: Globally unique collection identifier.198		CollectionLimitSet(CollectionId),199200		/// Mint permission	was set201		///202		/// # Arguments203		///204		/// * collection_id: Globally unique collection identifier.205		MintPermissionSet(CollectionId),206207		/// Offchain schema was set208		///209		/// # Arguments210		///211		/// * collection_id: Globally unique collection identifier.212		OffchainSchemaSet(CollectionId),213214		/// Public access mode was set215		///216		/// # Arguments217		///218		/// * collection_id: Globally unique collection identifier.219		///220		/// * mode: New access state.221		PublicAccessModeSet(CollectionId, AccessMode),222223		/// Schema version was set224		///225		/// # Arguments226		///227		/// * collection_id: Globally unique collection identifier.228		SchemaVersionSet(CollectionId),229230		/// Variable on chain schema was set231		///232		/// # Arguments233		///234		/// * collection_id: Globally unique collection identifier.235		VariableOnChainSchemaSet(CollectionId),236	}237}238239type SelfWeightOf<T> = <T as Config>::WeightInfo;240241// # Used definitions242//243// ## User control levels244//245// chain-controlled - key is uncontrolled by user246//                    i.e autoincrementing index247//                    can use non-cryptographic hash248// real - key is controlled by user249//        but it is hard to generate enough colliding values, i.e owner of signed txs250//        can use non-cryptographic hash251// controlled - key is completly controlled by users252//              i.e maps with mutable keys253//              should use cryptographic hash254//255// ## User control level downgrade reasons256//257// ?1 - chain-controlled -> controlled258//      collections/tokens can be destroyed, resulting in massive holes259// ?2 - chain-controlled -> controlled260//      same as ?1, but can be only added, resulting in easier exploitation261// ?3 - real -> controlled262//      no confirmation required, so addresses can be easily generated263decl_storage! {264	trait Store for Module<T: Config> as Unique {265266		//#region Private members267		/// Used for migrations268		ChainVersion: u64;269		//#endregion270271		//#region Tokens transfer rate limit baskets272		/// (Collection id (controlled?2), who created (real))273		/// TODO: Off chain worker should remove from this map when collection gets removed274		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;275		/// Collection id (controlled?2), token id (controlled?2)276		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;277		/// Collection id (controlled?2), owning user (real)278		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;279		/// Collection id (controlled?2), token id (controlled?2)280		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>;281		//#endregion282283		/// Variable metadata sponsoring284		/// Collection id (controlled?2), token id (controlled?2)285		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;286		/// Approval sponsoring287		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;288		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;289		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>;290	}291}292293decl_module! {294	pub struct Module<T: Config> for enum Call295	where296		origin: T::Origin297	{298		type Error = Error<T>;299300		fn deposit_event() = default;301302		fn on_initialize(_now: T::BlockNumber) -> Weight {303			0304		}305306		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.307		///308		/// # Permissions309		///310		/// * Anyone.311		///312		/// # Arguments313		///314		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.315		///316		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.317		///318		/// * token_prefix: UTF-8 string with token prefix.319		///320		/// * mode: [CollectionMode] collection type and type dependent data.321		// returns collection ID322		#[weight = <SelfWeightOf<T>>::create_collection()]323		#[transactional]324		pub fn create_collection(origin,325								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,326								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,327								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,328								 mode: CollectionMode) -> DispatchResult {329330			// Anyone can create a collection331			let who = ensure_signed(origin)?;332333			// Create new collection334			let new_collection = Collection {335				owner: who,336				name: collection_name,337				mode: mode.clone(),338				mint_mode: false,339				access: AccessMode::Normal,340				description: collection_description,341				token_prefix,342				offchain_schema: BoundedVec::default(),343				schema_version: SchemaVersion::ImageURL,344				sponsorship: SponsorshipState::Disabled,345				variable_on_chain_schema: BoundedVec::default(),346				const_on_chain_schema: BoundedVec::default(),347				limits: Default::default(),348				meta_update_permission: Default::default(),349			};350351			let _id = match mode {352				CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(new_collection)?},353				CollectionMode::Fungible(decimal_points) => {354					// check params355					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);356					<PalletFungible<T>>::init_collection(new_collection)?357				}358				CollectionMode::ReFungible => {359					<PalletRefungible<T>>::init_collection(new_collection)?360				}361			};362363			Ok(())364		}365366		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.367		///368		/// # Permissions369		///370		/// * Collection Owner.371		///372		/// # Arguments373		///374		/// * collection_id: collection to destroy.375		#[weight = <SelfWeightOf<T>>::destroy_collection()]376		#[transactional]377		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {378			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);379380			let collection = <CollectionHandle<T>>::try_get(collection_id)?;381			collection.check_is_owner(&sender)?;382383			// =========384385			match collection.mode {386				CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,387				CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,388				CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,389			}390391			<NftTransferBasket<T>>::remove_prefix(collection_id, None);392			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);393			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);394395			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);396			<NftApproveBasket<T>>::remove_prefix(collection_id, None);397			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);398			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);399400			Ok(())401		}402403		/// Add an address to allow list.404		///405		/// # Permissions406		///407		/// * Collection Owner408		/// * Collection Admin409		///410		/// # Arguments411		///412		/// * collection_id.413		///414		/// * address.415		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]416		#[transactional]417		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{418419			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);420			let collection = <CollectionHandle<T>>::try_get(collection_id)?;421422			<PalletCommon<T>>::toggle_allowlist(423				&collection,424				&sender,425				&address,426				true,427			)?;428429			Self::deposit_event(Event::<T>::AllowListAddressAdded(430				collection_id,431				address432			));433434			Ok(())435		}436437		/// Remove an address from allow list.438		///439		/// # Permissions440		///441		/// * Collection Owner442		/// * Collection Admin443		///444		/// # Arguments445		///446		/// * collection_id.447		///448		/// * address.449		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]450		#[transactional]451		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{452453			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);454			let collection = <CollectionHandle<T>>::try_get(collection_id)?;455456			<PalletCommon<T>>::toggle_allowlist(457				&collection,458				&sender,459				&address,460				false,461			)?;462463			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(464				collection_id,465				address466			));467468			Ok(())469		}470471		/// Toggle between normal and allow list access for the methods with access for `Anyone`.472		///473		/// # Permissions474		///475		/// * Collection Owner.476		///477		/// # Arguments478		///479		/// * collection_id.480		///481		/// * mode: [AccessMode]482		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]483		#[transactional]484		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult485		{486			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);487488			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;489			target_collection.check_is_owner(&sender)?;490491			target_collection.access = mode.clone();492493			<Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(494				collection_id,495				mode496			));497498			target_collection.save()499		}500501		/// Allows Anyone to create tokens if:502		/// * Allow List is enabled, and503		/// * Address is added to allow list, and504		/// * This method was called with True parameter505		///506		/// # Permissions507		/// * Collection Owner508		///509		/// # Arguments510		///511		/// * collection_id.512		///513		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.514		#[weight = <SelfWeightOf<T>>::set_mint_permission()]515		#[transactional]516		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult517		{518			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);519520			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;521			target_collection.check_is_owner(&sender)?;522523			target_collection.mint_mode = mint_permission;524525			<Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(526				collection_id527			));528529			target_collection.save()530		}531532		/// Change the owner of the collection.533		///534		/// # Permissions535		///536		/// * Collection Owner.537		///538		/// # Arguments539		///540		/// * collection_id.541		///542		/// * new_owner.543		#[weight = <SelfWeightOf<T>>::change_collection_owner()]544		#[transactional]545		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {546547			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);548549			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;550			target_collection.check_is_owner(&sender)?;551552			target_collection.owner = new_owner.clone();553			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(554				collection_id,555				new_owner556			));557558			target_collection.save()559		}560561		/// Adds an admin of the Collection.562		/// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.563		///564		/// # Permissions565		///566		/// * Collection Owner.567		/// * Collection Admin.568		///569		/// # Arguments570		///571		/// * collection_id: ID of the Collection to add admin for.572		///573		/// * new_admin_id: Address of new admin to add.574		#[weight = <SelfWeightOf<T>>::add_collection_admin()]575		#[transactional]576		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {577			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);578			let collection = <CollectionHandle<T>>::try_get(collection_id)?;579580			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(581				collection_id,582				new_admin_id.clone()583			));584585			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)586		}587588		/// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.589		///590		/// # Permissions591		///592		/// * Collection Owner.593		/// * Collection Admin.594		///595		/// # Arguments596		///597		/// * collection_id: ID of the Collection to remove admin for.598		///599		/// * account_id: Address of admin to remove.600		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]601		#[transactional]602		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {603			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);604			let collection = <CollectionHandle<T>>::try_get(collection_id)?;605606			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(607				collection_id,608				account_id.clone()609			));610611			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)612		}613614		/// # Permissions615		///616		/// * Collection Owner617		///618		/// # Arguments619		///620		/// * collection_id.621		///622		/// * new_sponsor.623		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]624		#[transactional]625		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {626			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);627628			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;629			target_collection.check_is_owner(&sender)?;630631			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());632633			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(634				collection_id,635				new_sponsor636			));637638			target_collection.save()639		}640641		/// # Permissions642		///643		/// * Sponsor.644		///645		/// # Arguments646		///647		/// * collection_id.648		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]649		#[transactional]650		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {651			let sender = ensure_signed(origin)?;652653			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;654			ensure!(655				target_collection.sponsorship.pending_sponsor() == Some(&sender),656				Error::<T>::ConfirmUnsetSponsorFail657			);658659			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());660661			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(662				collection_id,663				sender664			));665666			target_collection.save()667		}668669		/// Switch back to pay-per-own-transaction model.670		///671		/// # Permissions672		///673		/// * Collection owner.674		///675		/// # Arguments676		///677		/// * collection_id.678		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]679		#[transactional]680		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {681			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);682683			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;684			target_collection.check_is_owner(&sender)?;685686			target_collection.sponsorship = SponsorshipState::Disabled;687688			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(689				collection_id690			));691			target_collection.save()692		}693694		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.695		///696		/// # Permissions697		///698		/// * Collection Owner.699		/// * Collection Admin.700		/// * Anyone if701		///     * Allow List is enabled, and702		///     * Address is added to allow list, and703		///     * MintPermission is enabled (see SetMintPermission method)704		///705		/// # Arguments706		///707		/// * collection_id: ID of the collection.708		///709		/// * owner: Address, initial owner of the NFT.710		///711		/// * data: Token data to store on chain.712		#[weight = <CommonWeights<T>>::create_item()]713		#[transactional]714		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {715			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);716717			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))718		}719720		/// This method creates multiple items in a collection created with CreateCollection method.721		///722		/// # Permissions723		///724		/// * Collection Owner.725		/// * Collection Admin.726		/// * Anyone if727		///     * Allow List is enabled, and728		///     * Address is added to allow list, and729		///     * MintPermission is enabled (see SetMintPermission method)730		///731		/// # Arguments732		///733		/// * collection_id: ID of the collection.734		///735		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].736		///737		/// * owner: Address, initial owner of the NFT.738		#[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]739		#[transactional]740		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {741			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);742			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);743744			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))745		}746747		// TODO! transaction weight748749		/// Set transfers_enabled value for particular collection750		///751		/// # Permissions752		///753		/// * Collection Owner.754		///755		/// # Arguments756		///757		/// * collection_id: ID of the collection.758		///759		/// * value: New flag value.760		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]761		#[transactional]762		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {763			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);764			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;765			target_collection.check_is_owner(&sender)?;766767			// =========768769			target_collection.limits.transfers_enabled = Some(value);770			target_collection.save()771		}772773		/// Destroys a concrete instance of NFT.774		///775		/// # Permissions776		///777		/// * Collection Owner.778		/// * Collection Admin.779		/// * Current NFT Owner.780		///781		/// # Arguments782		///783		/// * collection_id: ID of the collection.784		///785		/// * item_id: ID of NFT to burn.786		#[weight = <CommonWeights<T>>::burn_item()]787		#[transactional]788		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {789			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);790791			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;792			if value == 1 {793				<NftTransferBasket<T>>::remove(collection_id, item_id);794				<NftApproveBasket<T>>::remove(collection_id, item_id);795			}796			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?797			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());798			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));799			Ok(post_info)800		}801802		/// Destroys a concrete instance of NFT on behalf of the owner803		/// See also: [`approve`]804		///805		/// # Permissions806		///807		/// * Collection Owner.808		/// * Collection Admin.809		/// * Current NFT Owner.810		///811		/// # Arguments812		///813		/// * collection_id: ID of the collection.814		///815		/// * item_id: ID of NFT to burn.816		///817		/// * from: owner of item818		#[weight = <CommonWeights<T>>::burn_from()]819		#[transactional]820		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {821			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);822823			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))824		}825826		/// Change ownership of the token.827		///828		/// # Permissions829		///830		/// * Collection Owner831		/// * Collection Admin832		/// * Current NFT owner833		///834		/// # Arguments835		///836		/// * recipient: Address of token recipient.837		///838		/// * collection_id.839		///840		/// * item_id: ID of the item841		///     * Non-Fungible Mode: Required.842		///     * Fungible Mode: Ignored.843		///     * Re-Fungible Mode: Required.844		///845		/// * value: Amount to transfer.846		///     * Non-Fungible Mode: Ignored847		///     * Fungible Mode: Must specify transferred amount848		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)849		#[weight = <CommonWeights<T>>::transfer()]850		#[transactional]851		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {852			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);853854			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))855		}856857		/// Set, change, or remove approved address to transfer the ownership of the NFT.858		///859		/// # Permissions860		///861		/// * Collection Owner862		/// * Collection Admin863		/// * Current NFT owner864		///865		/// # Arguments866		///867		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).868		///869		/// * collection_id.870		///871		/// * item_id: ID of the item.872		#[weight = <CommonWeights<T>>::approve()]873		#[transactional]874		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {875			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);876877			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))878		}879880		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.881		///882		/// # Permissions883		/// * Collection Owner884		/// * Collection Admin885		/// * Current NFT owner886		/// * Address approved by current NFT owner887		///888		/// # Arguments889		///890		/// * from: Address that owns token.891		///892		/// * recipient: Address of token recipient.893		///894		/// * collection_id.895		///896		/// * item_id: ID of the item.897		///898		/// * value: Amount to transfer.899		#[weight = <CommonWeights<T>>::transfer_from()]900		#[transactional]901		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {902			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);903904			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))905		}906907		/// Set off-chain data schema.908		///909		/// # Permissions910		///911		/// * Collection Owner912		/// * Collection Admin913		///914		/// # Arguments915		///916		/// * collection_id.917		///918		/// * schema: String representing the offchain data schema.919		#[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]920		#[transactional]921		pub fn set_variable_meta_data (922			origin,923			collection_id: CollectionId,924			item_id: TokenId,925			data: BoundedVec<u8, CustomDataLimit>,926		) -> DispatchResultWithPostInfo {927			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);928929			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))930		}931932		/// Set meta_update_permission value for particular collection933		///934		/// # Permissions935		///936		/// * Collection Owner.937		///938		/// # Arguments939		///940		/// * collection_id: ID of the collection.941		///942		/// * value: New flag value.943		#[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]944		#[transactional]945		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {946			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);947			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;948949			ensure!(950				target_collection.meta_update_permission != MetaUpdatePermission::None,951				<CommonError<T>>::MetadataFlagFrozen,952			);953			target_collection.check_is_owner(&sender)?;954955			target_collection.meta_update_permission = value;956957			target_collection.save()958		}959960		/// Set schema standard961		/// ImageURL962		/// Unique963		///964		/// # Permissions965		///966		/// * Collection Owner967		/// * Collection Admin968		///969		/// # Arguments970		///971		/// * collection_id.972		///973		/// * schema: SchemaVersion: enum974		#[weight = <SelfWeightOf<T>>::set_schema_version()]975		#[transactional]976		pub fn set_schema_version(977			origin,978			collection_id: CollectionId,979			version: SchemaVersion980		) -> DispatchResult {981			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);982			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;983			target_collection.check_is_owner_or_admin(&sender)?;984			target_collection.schema_version = version;985986			<Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(987				collection_id988			));989990			target_collection.save()991		}992993		/// Set off-chain data schema.994		///995		/// # Permissions996		///997		/// * Collection Owner998		/// * Collection Admin999		///1000		/// # Arguments1001		///1002		/// * collection_id.1003		///1004		/// * schema: String representing the offchain data schema.1005		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1006		#[transactional]1007		pub fn set_offchain_schema(1008			origin,1009			collection_id: CollectionId,1010			schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,1011		) -> DispatchResult {1012			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1013			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1014			target_collection.check_is_owner_or_admin(&sender)?;10151016			target_collection.offchain_schema = schema;10171018			<Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1019				collection_id1020			));10211022			target_collection.save()1023		}10241025		/// Set const on-chain data schema.1026		///1027		/// # Permissions1028		///1029		/// * Collection Owner1030		/// * Collection Admin1031		///1032		/// # Arguments1033		///1034		/// * collection_id.1035		///1036		/// * schema: String representing the const on-chain data schema.1037		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1038		#[transactional]1039		pub fn set_const_on_chain_schema (1040			origin,1041			collection_id: CollectionId,1042			schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1043		) -> DispatchResult {1044			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1045			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1046			target_collection.check_is_owner_or_admin(&sender)?;10471048			target_collection.const_on_chain_schema = schema;10491050			<Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1051				collection_id1052			));10531054			target_collection.save()1055		}10561057		/// Set variable on-chain data schema.1058		///1059		/// # Permissions1060		///1061		/// * Collection Owner1062		/// * Collection Admin1063		///1064		/// # Arguments1065		///1066		/// * collection_id.1067		///1068		/// * schema: String representing the variable on-chain data schema.1069		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1070		#[transactional]1071		pub fn set_variable_on_chain_schema (1072			origin,1073			collection_id: CollectionId,1074			schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1075		) -> DispatchResult {1076			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1077			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1078			target_collection.check_is_owner_or_admin(&sender)?;10791080			target_collection.variable_on_chain_schema = schema;10811082			<Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1083				collection_id1084			));10851086			target_collection.save()1087		}10881089		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1090		#[transactional]1091		pub fn set_collection_limits(1092			origin,1093			collection_id: CollectionId,1094			new_limit: CollectionLimits,1095		) -> DispatchResult {1096			let mut new_limit = new_limit;1097			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1098			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1099			target_collection.check_is_owner(&sender)?;1100			let old_limit = &target_collection.limits;11011102			macro_rules! limit_default {1103				($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1104					$(1105						if let Some($new) = $new.$field {1106							let $old = $old.$field($($arg)?);1107							let _ = $new;1108							let _ = $old;1109							$check1110						} else {1111							$new.$field = $old.$field1112						}1113					)*1114				}};1115			}11161117			limit_default!(old_limit, new_limit,1118				account_token_ownership_limit => ensure!(1119					new_limit <= MAX_TOKEN_OWNERSHIP,1120					<Error<T>>::CollectionLimitBoundsExceeded,1121				),1122				sponsor_transfer_timeout(match target_collection.mode {1123					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1124					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1125					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1126				}) => ensure!(1127					new_limit <= MAX_SPONSOR_TIMEOUT,1128					<Error<T>>::CollectionLimitBoundsExceeded,1129				),1130				sponsored_data_size => ensure!(1131					new_limit <= CUSTOM_DATA_LIMIT,1132					<Error<T>>::CollectionLimitBoundsExceeded,1133				),1134				token_limit => ensure!(1135					old_limit >= new_limit && new_limit > 0,1136					<CommonError<T>>::CollectionTokenLimitExceeded1137				),1138				owner_can_transfer => ensure!(1139					old_limit || !new_limit,1140					<Error<T>>::OwnerPermissionsCantBeReverted,1141				),1142				owner_can_destroy => ensure!(1143					old_limit || !new_limit,1144					<Error<T>>::OwnerPermissionsCantBeReverted,1145				),1146				sponsored_data_rate_limit => {},1147				transfers_enabled => {},1148			);11491150			target_collection.limits = new_limit;11511152			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1153				collection_id1154			));11551156			target_collection.save()1157		}1158	}1159}