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

difftreelog

source

pallets/unique/src/lib.rs30.5 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,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,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use up_data_structs::{38	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,39	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,40	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,41	NFT_SPONSOR_TRANSFER_TIMEOUT, AccessMode, Collection, CreateItemData, CollectionLimits,42	CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,43};44use pallet_common::{45	account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,46	CommonWeightInfo,47};48use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};49use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};50use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};5152#[cfg(test)]53mod mock;5455#[cfg(test)]56mod tests;5758mod eth;59mod sponsorship;60pub use sponsorship::UniqueSponsorshipHandler;61pub use eth::sponsoring::UniqueEthSponsorshipHandler;6263pub use eth::UniqueErcSupport;6465pub mod common;66use common::CommonWeights;67pub mod dispatch;68use dispatch::dispatch_call;6970#[cfg(feature = "runtime-benchmarks")]71mod benchmarking;72pub mod weights;73use weights::WeightInfo;7475decl_error! {76	/// Error for non-fungible-token module.77	pub enum Error for Module<T: Config> {78		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.79		CollectionDecimalPointLimitExceeded,80		/// This address is not set as sponsor, use setCollectionSponsor first.81		ConfirmUnsetSponsorFail,82		/// Length of items properties must be greater than 0.83		EmptyArgument,84		/// Collection limit bounds per collection exceeded85		CollectionLimitBoundsExceeded,86		/// Tried to enable permissions which are only permitted to be disabled87		OwnerPermissionsCantBeReverted,88	}89}90pub trait Config:91	system::Config92	+ pallet_evm_coder_substrate::Config93	+ pallet_common::Config94	+ pallet_nonfungible::Config95	+ pallet_refungible::Config96	+ pallet_fungible::Config97	+ Sized98	+ TypeInfo99{100	/// Weight information for extrinsics in this pallet.101	type WeightInfo: WeightInfo;102}103104type SelfWeightOf<T> = <T as Config>::WeightInfo;105106// # Used definitions107//108// ## User control levels109//110// chain-controlled - key is uncontrolled by user111//                    i.e autoincrementing index112//                    can use non-cryptographic hash113// real - key is controlled by user114//        but it is hard to generate enough colliding values, i.e owner of signed txs115//        can use non-cryptographic hash116// controlled - key is completly controlled by users117//              i.e maps with mutable keys118//              should use cryptographic hash119//120// ## User control level downgrade reasons121//122// ?1 - chain-controlled -> controlled123//      collections/tokens can be destroyed, resulting in massive holes124// ?2 - chain-controlled -> controlled125//      same as ?1, but can be only added, resulting in easier exploitation126// ?3 - real -> controlled127//      no confirmation required, so addresses can be easily generated128decl_storage! {129	trait Store for Module<T: Config> as Unique {130131		//#region Private members132		/// Used for migrations133		ChainVersion: u64;134		//#endregion135136		//#region Tokens transfer rate limit baskets137		/// (Collection id (controlled?2), who created (real))138		/// TODO: Off chain worker should remove from this map when collection gets removed139		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;140		/// Collection id (controlled?2), token id (controlled?2)141		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;142		/// Collection id (controlled?2), owning user (real)143		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;144		/// Collection id (controlled?2), token id (controlled?2)145		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>;146		//#endregion147148		/// Variable metadata sponsoring149		/// Collection id (controlled?2), token id (controlled?2)150		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;151		/// Approval sponsoring152		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;153		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;154		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>;155	}156}157158decl_module! {159	pub struct Module<T: Config> for enum Call160	where161		origin: T::Origin162	{163		type Error = Error<T>;164165		fn on_initialize(_now: T::BlockNumber) -> Weight {166			0167		}168169		/// 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.170		///171		/// # Permissions172		///173		/// * Anyone.174		///175		/// # Arguments176		///177		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.178		///179		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.180		///181		/// * token_prefix: UTF-8 string with token prefix.182		///183		/// * mode: [CollectionMode] collection type and type dependent data.184		// returns collection ID185		#[weight = <SelfWeightOf<T>>::create_collection()]186		#[transactional]187		pub fn create_collection(origin,188								 collection_name: Vec<u16>,189								 collection_description: Vec<u16>,190								 token_prefix: Vec<u8>,191								 mode: CollectionMode) -> DispatchResult {192193			// Anyone can create a collection194			let who = ensure_signed(origin)?;195196			// Create new collection197			let new_collection = Collection {198				owner: who,199				name: collection_name,200				mode: mode.clone(),201				mint_mode: false,202				access: AccessMode::Normal,203				description: collection_description,204				token_prefix,205				offchain_schema: Vec::new(),206				schema_version: SchemaVersion::ImageURL,207				sponsorship: SponsorshipState::Disabled,208				variable_on_chain_schema: Vec::new(),209				const_on_chain_schema: Vec::new(),210				limits: Default::default(),211				meta_update_permission: Default::default(),212			};213214			let _id = match mode {215				CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(new_collection)?},216				CollectionMode::Fungible(decimal_points) => {217					// check params218					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);219					<PalletFungible<T>>::init_collection(new_collection)?220				}221				CollectionMode::ReFungible => {222					<PalletRefungible<T>>::init_collection(new_collection)?223				}224			};225226			Ok(())227		}228229		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.230		///231		/// # Permissions232		///233		/// * Collection Owner.234		///235		/// # Arguments236		///237		/// * collection_id: collection to destroy.238		#[weight = <SelfWeightOf<T>>::destroy_collection()]239		#[transactional]240		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {241			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);242243			let collection = <CollectionHandle<T>>::try_get(collection_id)?;244			collection.check_is_owner(&sender)?;245246			// =========247248			match collection.mode {249				CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,250				CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,251				CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,252			}253254			<NftTransferBasket<T>>::remove_prefix(collection_id, None);255			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);256			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);257258			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);259			<NftApproveBasket<T>>::remove_prefix(collection_id, None);260			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);261			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);262263			Ok(())264		}265266		/// Add an address to allow list.267		///268		/// # Permissions269		///270		/// * Collection Owner271		/// * Collection Admin272		///273		/// # Arguments274		///275		/// * collection_id.276		///277		/// * address.278		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]279		#[transactional]280		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{281282			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);283			let collection = <CollectionHandle<T>>::try_get(collection_id)?;284285			<PalletCommon<T>>::toggle_allowlist(286				&collection,287				&sender,288				&address,289				true,290			)?;291292			Ok(())293		}294295		/// Remove an address from allow list.296		///297		/// # Permissions298		///299		/// * Collection Owner300		/// * Collection Admin301		///302		/// # Arguments303		///304		/// * collection_id.305		///306		/// * address.307		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]308		#[transactional]309		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{310311			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);312			let collection = <CollectionHandle<T>>::try_get(collection_id)?;313314			<PalletCommon<T>>::toggle_allowlist(315				&collection,316				&sender,317				&address,318				false,319			)?;320321			Ok(())322		}323324		/// Toggle between normal and allow list access for the methods with access for `Anyone`.325		///326		/// # Permissions327		///328		/// * Collection Owner.329		///330		/// # Arguments331		///332		/// * collection_id.333		///334		/// * mode: [AccessMode]335		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]336		#[transactional]337		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult338		{339			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);340341			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;342			target_collection.check_is_owner(&sender)?;343344			target_collection.access = mode;345			target_collection.save()346		}347348		/// Allows Anyone to create tokens if:349		/// * Allow List is enabled, and350		/// * Address is added to allow list, and351		/// * This method was called with True parameter352		///353		/// # Permissions354		/// * Collection Owner355		///356		/// # Arguments357		///358		/// * collection_id.359		///360		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.361		#[weight = <SelfWeightOf<T>>::set_mint_permission()]362		#[transactional]363		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult364		{365			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);366367			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;368			target_collection.check_is_owner(&sender)?;369370			target_collection.mint_mode = mint_permission;371			target_collection.save()372		}373374		/// Change the owner of the collection.375		///376		/// # Permissions377		///378		/// * Collection Owner.379		///380		/// # Arguments381		///382		/// * collection_id.383		///384		/// * new_owner.385		#[weight = <SelfWeightOf<T>>::change_collection_owner()]386		#[transactional]387		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {388389			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);390391			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;392			target_collection.check_is_owner(&sender)?;393394			target_collection.owner = new_owner;395			target_collection.save()396		}397398		/// Adds an admin of the Collection.399		/// 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.400		///401		/// # Permissions402		///403		/// * Collection Owner.404		/// * Collection Admin.405		///406		/// # Arguments407		///408		/// * collection_id: ID of the Collection to add admin for.409		///410		/// * new_admin_id: Address of new admin to add.411		#[weight = <SelfWeightOf<T>>::add_collection_admin()]412		#[transactional]413		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {414			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);415			let collection = <CollectionHandle<T>>::try_get(collection_id)?;416417			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)418		}419420		/// 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.421		///422		/// # Permissions423		///424		/// * Collection Owner.425		/// * Collection Admin.426		///427		/// # Arguments428		///429		/// * collection_id: ID of the Collection to remove admin for.430		///431		/// * account_id: Address of admin to remove.432		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]433		#[transactional]434		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {435			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);436			let collection = <CollectionHandle<T>>::try_get(collection_id)?;437438			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)439		}440441		/// # Permissions442		///443		/// * Collection Owner444		///445		/// # Arguments446		///447		/// * collection_id.448		///449		/// * new_sponsor.450		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]451		#[transactional]452		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {453			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);454455			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;456			target_collection.check_is_owner(&sender)?;457458			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);459			target_collection.save()460		}461462		/// # Permissions463		///464		/// * Sponsor.465		///466		/// # Arguments467		///468		/// * collection_id.469		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]470		#[transactional]471		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {472			let sender = ensure_signed(origin)?;473474			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;475			ensure!(476				target_collection.sponsorship.pending_sponsor() == Some(&sender),477				Error::<T>::ConfirmUnsetSponsorFail478			);479480			target_collection.sponsorship = SponsorshipState::Confirmed(sender);481			target_collection.save()482		}483484		/// Switch back to pay-per-own-transaction model.485		///486		/// # Permissions487		///488		/// * Collection owner.489		///490		/// # Arguments491		///492		/// * collection_id.493		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]494		#[transactional]495		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {496			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);497498			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;499			target_collection.check_is_owner(&sender)?;500501			target_collection.sponsorship = SponsorshipState::Disabled;502			target_collection.save()503		}504505		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.506		///507		/// # Permissions508		///509		/// * Collection Owner.510		/// * Collection Admin.511		/// * Anyone if512		///     * Allow List is enabled, and513		///     * Address is added to allow list, and514		///     * MintPermission is enabled (see SetMintPermission method)515		///516		/// # Arguments517		///518		/// * collection_id: ID of the collection.519		///520		/// * owner: Address, initial owner of the NFT.521		///522		/// * data: Token data to store on chain.523		#[weight = <CommonWeights<T>>::create_item()]524		#[transactional]525		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {526			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);527528			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))529		}530531		/// This method creates multiple items in a collection created with CreateCollection method.532		///533		/// # Permissions534		///535		/// * Collection Owner.536		/// * Collection Admin.537		/// * Anyone if538		///     * Allow List is enabled, and539		///     * Address is added to allow list, and540		///     * MintPermission is enabled (see SetMintPermission method)541		///542		/// # Arguments543		///544		/// * collection_id: ID of the collection.545		///546		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].547		///548		/// * owner: Address, initial owner of the NFT.549		#[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]550		#[transactional]551		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {552			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);553			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);554555			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))556		}557558		// TODO! transaction weight559560		/// Set transfers_enabled value for particular collection561		///562		/// # Permissions563		///564		/// * Collection Owner.565		///566		/// # Arguments567		///568		/// * collection_id: ID of the collection.569		///570		/// * value: New flag value.571		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]572		#[transactional]573		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {574			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);575			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;576			target_collection.check_is_owner(&sender)?;577578			// =========579580			target_collection.limits.transfers_enabled = Some(value);581			target_collection.save()582		}583584		/// Destroys a concrete instance of NFT.585		///586		/// # Permissions587		///588		/// * Collection Owner.589		/// * Collection Admin.590		/// * Current NFT Owner.591		///592		/// # Arguments593		///594		/// * collection_id: ID of the collection.595		///596		/// * item_id: ID of NFT to burn.597		#[weight = <CommonWeights<T>>::burn_item()]598		#[transactional]599		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {600			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);601602			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;603			if value == 1 {604				<NftTransferBasket<T>>::remove(collection_id, item_id);605				<NftApproveBasket<T>>::remove(collection_id, item_id);606			}607			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?608			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());609			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));610			Ok(post_info)611		}612613		/// Destroys a concrete instance of NFT on behalf of the owner614		/// See also: [`approve`]615		///616		/// # Permissions617		///618		/// * Collection Owner.619		/// * Collection Admin.620		/// * Current NFT Owner.621		///622		/// # Arguments623		///624		/// * collection_id: ID of the collection.625		///626		/// * item_id: ID of NFT to burn.627		///628		/// * from: owner of item629		#[weight = <CommonWeights<T>>::burn_from()]630		#[transactional]631		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {632			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);633634			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))635		}636637		/// Change ownership of the token.638		///639		/// # Permissions640		///641		/// * Collection Owner642		/// * Collection Admin643		/// * Current NFT owner644		///645		/// # Arguments646		///647		/// * recipient: Address of token recipient.648		///649		/// * collection_id.650		///651		/// * item_id: ID of the item652		///     * Non-Fungible Mode: Required.653		///     * Fungible Mode: Ignored.654		///     * Re-Fungible Mode: Required.655		///656		/// * value: Amount to transfer.657		///     * Non-Fungible Mode: Ignored658		///     * Fungible Mode: Must specify transferred amount659		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)660		#[weight = <CommonWeights<T>>::transfer()]661		#[transactional]662		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {663			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);664665			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))666		}667668		/// Set, change, or remove approved address to transfer the ownership of the NFT.669		///670		/// # Permissions671		///672		/// * Collection Owner673		/// * Collection Admin674		/// * Current NFT owner675		///676		/// # Arguments677		///678		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).679		///680		/// * collection_id.681		///682		/// * item_id: ID of the item.683		#[weight = <CommonWeights<T>>::approve()]684		#[transactional]685		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {686			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);687688			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))689		}690691		/// 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.692		///693		/// # Permissions694		/// * Collection Owner695		/// * Collection Admin696		/// * Current NFT owner697		/// * Address approved by current NFT owner698		///699		/// # Arguments700		///701		/// * from: Address that owns token.702		///703		/// * recipient: Address of token recipient.704		///705		/// * collection_id.706		///707		/// * item_id: ID of the item.708		///709		/// * value: Amount to transfer.710		#[weight = <CommonWeights<T>>::transfer_from()]711		#[transactional]712		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {713			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);714715			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))716		}717718		/// Set off-chain data schema.719		///720		/// # Permissions721		///722		/// * Collection Owner723		/// * Collection Admin724		///725		/// # Arguments726		///727		/// * collection_id.728		///729		/// * schema: String representing the offchain data schema.730		#[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]731		#[transactional]732		pub fn set_variable_meta_data (733			origin,734			collection_id: CollectionId,735			item_id: TokenId,736			data: Vec<u8>737		) -> DispatchResultWithPostInfo {738			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);739740			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))741		}742743		/// Set meta_update_permission value for particular collection744		///745		/// # Permissions746		///747		/// * Collection Owner.748		///749		/// # Arguments750		///751		/// * collection_id: ID of the collection.752		///753		/// * value: New flag value.754		#[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]755		#[transactional]756		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {757			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);758			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;759760			ensure!(761				target_collection.meta_update_permission != MetaUpdatePermission::None,762				<CommonError<T>>::MetadataFlagFrozen,763			);764			target_collection.check_is_owner(&sender)?;765766			target_collection.meta_update_permission = value;767768			target_collection.save()769		}770771		/// Set schema standard772		/// ImageURL773		/// Unique774		///775		/// # Permissions776		///777		/// * Collection Owner778		/// * Collection Admin779		///780		/// # Arguments781		///782		/// * collection_id.783		///784		/// * schema: SchemaVersion: enum785		#[weight = <SelfWeightOf<T>>::set_schema_version()]786		#[transactional]787		pub fn set_schema_version(788			origin,789			collection_id: CollectionId,790			version: SchemaVersion791		) -> DispatchResult {792			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);793			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;794			target_collection.check_is_owner_or_admin(&sender)?;795			target_collection.schema_version = version;796			target_collection.save()797		}798799		/// Set off-chain data schema.800		///801		/// # Permissions802		///803		/// * Collection Owner804		/// * Collection Admin805		///806		/// # Arguments807		///808		/// * collection_id.809		///810		/// * schema: String representing the offchain data schema.811		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]812		#[transactional]813		pub fn set_offchain_schema(814			origin,815			collection_id: CollectionId,816			schema: Vec<u8>817		) -> DispatchResult {818			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);819			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;820			target_collection.check_is_owner_or_admin(&sender)?;821822			// check schema limit823			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");824825			target_collection.offchain_schema = schema;826			target_collection.save()827		}828829		/// Set const on-chain data schema.830		///831		/// # Permissions832		///833		/// * Collection Owner834		/// * Collection Admin835		///836		/// # Arguments837		///838		/// * collection_id.839		///840		/// * schema: String representing the const on-chain data schema.841		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]842		#[transactional]843		pub fn set_const_on_chain_schema (844			origin,845			collection_id: CollectionId,846			schema: Vec<u8>847		) -> DispatchResult {848			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);849			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;850			target_collection.check_is_owner_or_admin(&sender)?;851852			// check schema limit853			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");854855			target_collection.const_on_chain_schema = schema;856			target_collection.save()857		}858859		/// Set variable on-chain data schema.860		///861		/// # Permissions862		///863		/// * Collection Owner864		/// * Collection Admin865		///866		/// # Arguments867		///868		/// * collection_id.869		///870		/// * schema: String representing the variable on-chain data schema.871		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]872		#[transactional]873		pub fn set_variable_on_chain_schema (874			origin,875			collection_id: CollectionId,876			schema: Vec<u8>877		) -> DispatchResult {878			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);879			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;880			target_collection.check_is_owner_or_admin(&sender)?;881882			// check schema limit883			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");884885			target_collection.variable_on_chain_schema = schema;886			target_collection.save()887		}888889		#[weight = <SelfWeightOf<T>>::set_collection_limits()]890		#[transactional]891		pub fn set_collection_limits(892			origin,893			collection_id: CollectionId,894			new_limit: CollectionLimits,895		) -> DispatchResult {896			let mut new_limit = new_limit;897			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);898			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;899			target_collection.check_is_owner(&sender)?;900			let old_limit = &target_collection.limits;901902			macro_rules! limit_default {903				($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{904					$(905						if let Some($new) = $new.$field {906							let $old = $old.$field($($arg)?);907							let _ = $new;908							let _ = $old;909							$check910						} else {911							$new.$field = $old.$field912						}913					)*914				}};915			}916917			limit_default!(old_limit, new_limit,918				account_token_ownership_limit => ensure!(919					new_limit <= MAX_TOKEN_OWNERSHIP,920					<Error<T>>::CollectionLimitBoundsExceeded,921				),922				sponsor_transfer_timeout(match target_collection.mode {923					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,924					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,925					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,926				}) => ensure!(927					new_limit <= MAX_SPONSOR_TIMEOUT,928					<Error<T>>::CollectionLimitBoundsExceeded,929				),930				sponsored_data_size => ensure!(931					new_limit <= CUSTOM_DATA_LIMIT,932					<Error<T>>::CollectionLimitBoundsExceeded,933				),934				token_limit => ensure!(935					old_limit >= new_limit && new_limit > 0,936					<CommonError<T>>::CollectionTokenLimitExceeded937				),938				owner_can_transfer => ensure!(939					old_limit || !new_limit,940					<Error<T>>::OwnerPermissionsCantBeReverted,941				),942				owner_can_destroy => ensure!(943					old_limit || !new_limit,944					<Error<T>>::OwnerPermissionsCantBeReverted,945				),946				sponsored_data_rate_limit => {},947				transfers_enabled => {},948			);949950			target_collection.limits = new_limit;951952			target_collection.save()953		}954	}955}