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

difftreelog

source

pallets/nft/src/lib.rs28.8 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 frame_system::{self as system, ensure_signed};35use sp_runtime::{sp_std::prelude::Vec};36use nft_data_structs::{37	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,38	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,39	OFFCHAIN_SCHEMA_LIMIT, AccessMode, Collection, CreateItemData, CollectionLimits, CollectionId,40	CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,41};42use pallet_common::{43	account::CrossAccountId, CollectionHandle, IsAdmin, Pallet as PalletCommon,44	Error as CommonError, CommonWeightInfo,45};46use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};47use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};48use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};4950#[cfg(test)]51mod mock;5253#[cfg(test)]54mod tests;5556mod eth;57mod sponsorship;58pub use sponsorship::NftSponsorshipHandler;59pub use eth::sponsoring::NftEthSponsorshipHandler;6061pub use eth::NftErcSupport;6263pub mod common;64use common::CommonWeights;65pub mod dispatch;66use dispatch::dispatch_call;6768#[cfg(feature = "runtime-benchmarks")]69mod benchmarking;70pub mod weights;71use weights::WeightInfo;7273decl_error! {74	/// Error for non-fungible-token module.75	pub enum Error for Module<T: Config> {76		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.77		CollectionDecimalPointLimitExceeded,78		/// This address is not set as sponsor, use setCollectionSponsor first.79		ConfirmUnsetSponsorFail,80		/// Length of items properties must be greater than 0.81		EmptyArgument,82		/// Collection limit bounds per collection exceeded83		CollectionLimitBoundsExceeded,84		/// Tried to enable permissions which are only permitted to be disabled85		OwnerPermissionsCantBeReverted,86	}87}88pub trait Config:89	system::Config90	+ pallet_evm_coder_substrate::Config91	+ pallet_common::Config92	+ pallet_nonfungible::Config93	+ pallet_refungible::Config94	+ pallet_fungible::Config95	+ Sized96{97	/// Weight information for extrinsics in this pallet.98	type WeightInfo: WeightInfo;99}100101type SelfWeightOf<T> = <T as Config>::WeightInfo;102103trait WeightInfoHelpers: WeightInfo {104	fn transfer() -> Weight {105		Self::transfer_nft()106			.max(Self::transfer_fungible())107			.max(Self::transfer_refungible())108	}109	fn transfer_from() -> Weight {110		Self::transfer_from_nft()111			.max(Self::transfer_from_fungible())112			.max(Self::transfer_from_refungible())113	}114	fn approve() -> Weight {115		// TODO: refungible, fungible116		Self::approve_nft()117	}118	fn set_variable_meta_data(data: u32) -> Weight {119		// TODO: refungible120		Self::set_variable_meta_data_nft(data)121	}122	fn create_item(data: u32) -> Weight {123		Self::create_item_nft(data)124			.max(Self::create_item_fungible())125			.max(Self::create_item_refungible(data))126	}127	fn create_multiple_items(amount: u32) -> Weight {128		Self::create_multiple_items_nft(amount)129			.max(Self::create_multiple_items_fungible(amount))130			.max(Self::create_multiple_items_refungible(amount))131	}132}133impl<T: WeightInfo> WeightInfoHelpers for T {}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 Nft {159160		//#region Private members161		/// Used for migrations162		ChainVersion: u64;163		//#endregion164165		//#region Tokens transfer 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) => 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 => 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 => T::BlockNumber;173		/// Collection id (controlled?2), token id (controlled?2)174		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;175		//#endregion176177		/// Variable metadata sponsoring178		/// Collection id (controlled?2), token id (controlled?2)179		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;180	}181}182183decl_module! {184	pub struct Module<T: Config> for enum Call185	where186		origin: T::Origin187	{188		const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;189		type Error = Error<T>;190191		fn on_initialize(_now: T::BlockNumber) -> Weight {192			0193		}194195		/// 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 and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.196		///197		/// # Permissions198		///199		/// * Anyone.200		///201		/// # Arguments202		///203		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.204		///205		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.206		///207		/// * token_prefix: UTF-8 string with token prefix.208		///209		/// * mode: [CollectionMode] collection type and type dependent data.210		// returns collection ID211		#[weight = <SelfWeightOf<T>>::create_collection()]212		#[transactional]213		pub fn create_collection(origin,214								 collection_name: Vec<u16>,215								 collection_description: Vec<u16>,216								 token_prefix: Vec<u8>,217								 mode: CollectionMode) -> DispatchResult {218219			// Anyone can create a collection220			let who = ensure_signed(origin)?;221222			let limits = CollectionLimits::<T::BlockNumber> {223				sponsored_data_size: CUSTOM_DATA_LIMIT,224				..Default::default()225			};226227			// Create new collection228			let new_collection = Collection::<T> {229				owner: who.clone(),230				name: collection_name,231				mode: mode.clone(),232				mint_mode: false,233				access: AccessMode::Normal,234				description: collection_description,235				token_prefix,236				offchain_schema: Vec::new(),237				schema_version: SchemaVersion::ImageURL,238				sponsorship: SponsorshipState::Disabled,239				variable_on_chain_schema: Vec::new(),240				const_on_chain_schema: Vec::new(),241				limits,242				transfers_enabled: true,243				meta_update_permission: Default::default(),244			};245246			let _id = match mode {247				CollectionMode::NFT => {PalletNonfungible::init_collection(new_collection)?},248				CollectionMode::Fungible(decimal_points) => {249					// check params250					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);251					PalletFungible::init_collection(new_collection)?252				}253				CollectionMode::ReFungible => {254					PalletRefungible::init_collection(new_collection)?255				}256			};257258			Ok(())259		}260261		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.262		///263		/// # Permissions264		///265		/// * Collection Owner.266		///267		/// # Arguments268		///269		/// * collection_id: collection to destroy.270		#[weight = <SelfWeightOf<T>>::destroy_collection()]271		#[transactional]272		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {273			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);274275			let collection = <CollectionHandle<T>>::try_get(collection_id)?;276			collection.check_is_owner(&sender)?;277278			// =========279280			match collection.mode {281				CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,282				CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,283				CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,284			}285286			<NftTransferBasket<T>>::remove_prefix(collection_id, None);287			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);288			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);289290			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);291292			Ok(())293		}294295		/// Add an address to white list.296		///297		/// # Permissions298		///299		/// * Collection Owner300		/// * Collection Admin301		///302		/// # Arguments303		///304		/// * collection_id.305		///306		/// * address.307		#[weight = <SelfWeightOf<T>>::add_to_white_list()]308		#[transactional]309		pub fn add_to_white_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_whitelist(315				&collection,316				&sender,317				&address,318				true,319			)?;320321			Ok(())322		}323324		/// Remove an address from white list.325		///326		/// # Permissions327		///328		/// * Collection Owner329		/// * Collection Admin330		///331		/// # Arguments332		///333		/// * collection_id.334		///335		/// * address.336		#[weight = <SelfWeightOf<T>>::remove_from_white_list()]337		#[transactional]338		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{339340			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);341			let collection = <CollectionHandle<T>>::try_get(collection_id)?;342343			<PalletCommon<T>>::toggle_whitelist(344				&collection,345				&sender,346				&address,347				false,348			)?;349350			Ok(())351		}352353		/// Toggle between normal and white list access for the methods with access for `Anyone`.354		///355		/// # Permissions356		///357		/// * Collection Owner.358		///359		/// # Arguments360		///361		/// * collection_id.362		///363		/// * mode: [AccessMode]364		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]365		#[transactional]366		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult367		{368			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);369370			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;371			target_collection.check_is_owner(&sender)?;372373			target_collection.access = mode;374			target_collection.save()375		}376377		/// Allows Anyone to create tokens if:378		/// * White List is enabled, and379		/// * Address is added to white list, and380		/// * This method was called with True parameter381		///382		/// # Permissions383		/// * Collection Owner384		///385		/// # Arguments386		///387		/// * collection_id.388		///389		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.390		#[weight = <SelfWeightOf<T>>::set_mint_permission()]391		#[transactional]392		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult393		{394			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);395396			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;397			target_collection.check_is_owner(&sender)?;398399			target_collection.mint_mode = mint_permission;400			target_collection.save()401		}402403		/// Change the owner of the collection.404		///405		/// # Permissions406		///407		/// * Collection Owner.408		///409		/// # Arguments410		///411		/// * collection_id.412		///413		/// * new_owner.414		#[weight = <SelfWeightOf<T>>::change_collection_owner()]415		#[transactional]416		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {417418			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);419420			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;421			target_collection.check_is_owner(&sender)?;422423			target_collection.owner = new_owner;424			target_collection.save()425		}426427		/// Adds an admin of the Collection.428		/// 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.429		///430		/// # Permissions431		///432		/// * Collection Owner.433		/// * Collection Admin.434		///435		/// # Arguments436		///437		/// * collection_id: ID of the Collection to add admin for.438		///439		/// * new_admin_id: Address of new admin to add.440		#[weight = <SelfWeightOf<T>>::add_collection_admin()]441		#[transactional]442		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {443			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);444445			let collection = <CollectionHandle<T>>::try_get(collection_id)?;446			collection.check_is_owner_or_admin(&sender)?;447448			<IsAdmin<T>>::insert((collection_id, new_admin_id.as_sub()), true);449			Ok(())450		}451452		/// 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.453		///454		/// # Permissions455		///456		/// * Collection Owner.457		/// * Collection Admin.458		///459		/// # Arguments460		///461		/// * collection_id: ID of the Collection to remove admin for.462		///463		/// * account_id: Address of admin to remove.464		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]465		#[transactional]466		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {467			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);468469			let collection = <CollectionHandle<T>>::try_get(collection_id)?;470			collection.check_is_owner_or_admin(&sender)?;471472			<IsAdmin<T>>::remove((collection_id, account_id.as_sub()));473			Ok(())474		}475476		/// # Permissions477		///478		/// * Collection Owner479		///480		/// # Arguments481		///482		/// * collection_id.483		///484		/// * new_sponsor.485		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]486		#[transactional]487		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {488			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);489490			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;491			target_collection.check_is_owner_or_admin(&sender)?;492493			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);494			target_collection.save()495		}496497		/// # Permissions498		///499		/// * Sponsor.500		///501		/// # Arguments502		///503		/// * collection_id.504		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]505		#[transactional]506		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {507			let sender = ensure_signed(origin)?;508509			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;510			ensure!(511				target_collection.sponsorship.pending_sponsor() == Some(&sender),512				Error::<T>::ConfirmUnsetSponsorFail513			);514515			target_collection.sponsorship = SponsorshipState::Confirmed(sender);516			target_collection.save()517		}518519		/// Switch back to pay-per-own-transaction model.520		///521		/// # Permissions522		///523		/// * Collection owner.524		///525		/// # Arguments526		///527		/// * collection_id.528		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]529		#[transactional]530		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {531			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);532533			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;534			target_collection.check_is_owner(&sender)?;535536			target_collection.sponsorship = SponsorshipState::Disabled;537			target_collection.save()538		}539540		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.541		///542		/// # Permissions543		///544		/// * Collection Owner.545		/// * Collection Admin.546		/// * Anyone if547		///     * White List is enabled, and548		///     * Address is added to white list, and549		///     * MintPermission is enabled (see SetMintPermission method)550		///551		/// # Arguments552		///553		/// * collection_id: ID of the collection.554		///555		/// * owner: Address, initial owner of the NFT.556		///557		/// * data: Token data to store on chain.558		#[weight = <CommonWeights<T>>::create_item()]559		#[transactional]560		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {561			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);562563			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))564		}565566		/// This method creates multiple items in a collection created with CreateCollection method.567		///568		/// # Permissions569		///570		/// * Collection Owner.571		/// * Collection Admin.572		/// * Anyone if573		///     * White List is enabled, and574		///     * Address is added to white list, and575		///     * MintPermission is enabled (see SetMintPermission method)576		///577		/// # Arguments578		///579		/// * collection_id: ID of the collection.580		///581		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].582		///583		/// * owner: Address, initial owner of the NFT.584		#[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]585		#[transactional]586		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {587			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);588			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);589590			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))591		}592593		// TODO! transaction weight594595		/// Set transfers_enabled value for particular collection596		///597		/// # Permissions598		///599		/// * Collection Owner.600		///601		/// # Arguments602		///603		/// * collection_id: ID of the collection.604		///605		/// * value: New flag value.606		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]607		#[transactional]608		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {609			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);610			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;611			target_collection.check_is_owner(&sender)?;612613			// =========614615			target_collection.transfers_enabled = value;616			target_collection.save()617		}618619		/// Destroys a concrete instance of NFT.620		///621		/// # Permissions622		///623		/// * Collection Owner.624		/// * Collection Admin.625		/// * Current NFT Owner.626		///627		/// # Arguments628		///629		/// * collection_id: ID of the collection.630		///631		/// * item_id: ID of NFT to burn.632		#[weight = <CommonWeights<T>>::burn_item()]633		#[transactional]634		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {635			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);636637			dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))638		}639640		/// Change ownership of the token.641		///642		/// # Permissions643		///644		/// * Collection Owner645		/// * Collection Admin646		/// * Current NFT owner647		///648		/// # Arguments649		///650		/// * recipient: Address of token recipient.651		///652		/// * collection_id.653		///654		/// * item_id: ID of the item655		///     * Non-Fungible Mode: Required.656		///     * Fungible Mode: Ignored.657		///     * Re-Fungible Mode: Required.658		///659		/// * value: Amount to transfer.660		///     * Non-Fungible Mode: Ignored661		///     * Fungible Mode: Must specify transferred amount662		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)663		#[weight = <CommonWeights<T>>::transfer()]664		#[transactional]665		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {666			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);667668			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))669		}670671		/// Set, change, or remove approved address to transfer the ownership of the NFT.672		///673		/// # Permissions674		///675		/// * Collection Owner676		/// * Collection Admin677		/// * Current NFT owner678		///679		/// # Arguments680		///681		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).682		///683		/// * collection_id.684		///685		/// * item_id: ID of the item.686		#[weight = <CommonWeights<T>>::approve()]687		#[transactional]688		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {689			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);690691			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))692		}693694		/// 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.695		///696		/// # Permissions697		/// * Collection Owner698		/// * Collection Admin699		/// * Current NFT owner700		/// * Address approved by current NFT owner701		///702		/// # Arguments703		///704		/// * from: Address that owns token.705		///706		/// * recipient: Address of token recipient.707		///708		/// * collection_id.709		///710		/// * item_id: ID of the item.711		///712		/// * value: Amount to transfer.713		#[weight = <CommonWeights<T>>::transfer_from()]714		#[transactional]715		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {716			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);717718			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))719		}720721		/// Set off-chain data schema.722		///723		/// # Permissions724		///725		/// * Collection Owner726		/// * Collection Admin727		///728		/// # Arguments729		///730		/// * collection_id.731		///732		/// * schema: String representing the offchain data schema.733		#[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]734		#[transactional]735		pub fn set_variable_meta_data (736			origin,737			collection_id: CollectionId,738			item_id: TokenId,739			data: Vec<u8>740		) -> DispatchResultWithPostInfo {741			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);742743			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))744		}745746		/// Set meta_update_permission value for particular collection747		///748		/// # Permissions749		///750		/// * Collection Owner.751		///752		/// # Arguments753		///754		/// * collection_id: ID of the collection.755		///756		/// * value: New flag value.757		#[weight = <SelfWeightOf<T>>::set_variable_meta_data(0)]758		#[transactional]759		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {760			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);761			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;762763			ensure!(764				target_collection.meta_update_permission != MetaUpdatePermission::None,765				<CommonError<T>>::MetadataFlagFrozen,766			);767			target_collection.check_is_owner(&sender)?;768769			target_collection.meta_update_permission = value;770771			target_collection.save()772		}773774		/// Set schema standard775		/// ImageURL776		/// Unique777		///778		/// # Permissions779		///780		/// * Collection Owner781		/// * Collection Admin782		///783		/// # Arguments784		///785		/// * collection_id.786		///787		/// * schema: SchemaVersion: enum788		#[weight = <SelfWeightOf<T>>::set_schema_version()]789		#[transactional]790		pub fn set_schema_version(791			origin,792			collection_id: CollectionId,793			version: SchemaVersion794		) -> DispatchResult {795			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);796			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;797			target_collection.check_is_owner_or_admin(&sender)?;798			target_collection.schema_version = version;799			target_collection.save()800		}801802		/// Set off-chain data schema.803		///804		/// # Permissions805		///806		/// * Collection Owner807		/// * Collection Admin808		///809		/// # Arguments810		///811		/// * collection_id.812		///813		/// * schema: String representing the offchain data schema.814		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]815		#[transactional]816		pub fn set_offchain_schema(817			origin,818			collection_id: CollectionId,819			schema: Vec<u8>820		) -> DispatchResult {821			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);822			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;823			target_collection.check_is_owner_or_admin(&sender)?;824825			// check schema limit826			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");827828			target_collection.offchain_schema = schema;829			target_collection.save()830		}831832		/// Set const on-chain data schema.833		///834		/// # Permissions835		///836		/// * Collection Owner837		/// * Collection Admin838		///839		/// # Arguments840		///841		/// * collection_id.842		///843		/// * schema: String representing the const on-chain data schema.844		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]845		#[transactional]846		pub fn set_const_on_chain_schema (847			origin,848			collection_id: CollectionId,849			schema: Vec<u8>850		) -> DispatchResult {851			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);852			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;853			target_collection.check_is_owner_or_admin(&sender)?;854855			// check schema limit856			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");857858			target_collection.const_on_chain_schema = schema;859			target_collection.save()860		}861862		/// Set variable on-chain data schema.863		///864		/// # Permissions865		///866		/// * Collection Owner867		/// * Collection Admin868		///869		/// # Arguments870		///871		/// * collection_id.872		///873		/// * schema: String representing the variable on-chain data schema.874		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]875		#[transactional]876		pub fn set_variable_on_chain_schema (877			origin,878			collection_id: CollectionId,879			schema: Vec<u8>880		) -> DispatchResult {881			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);882			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;883			target_collection.check_is_owner_or_admin(&sender)?;884885			// check schema limit886			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");887888			target_collection.variable_on_chain_schema = schema;889			target_collection.save()890		}891892		#[weight = <SelfWeightOf<T>>::set_collection_limits()]893		#[transactional]894		pub fn set_collection_limits(895			origin,896			collection_id: CollectionId,897			new_limits: CollectionLimits<T::BlockNumber>,898		) -> DispatchResult {899			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);900			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;901			target_collection.check_is_owner(&sender)?;902			let old_limits = &target_collection.limits;903904			// collection bounds905			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&906				new_limits.account_token_ownership_limit.unwrap_or(0) <= MAX_TOKEN_OWNERSHIP &&907				new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,908				Error::<T>::CollectionLimitBoundsExceeded);909910			// token_limit   check  prev911			ensure!(old_limits.token_limit >= new_limits.token_limit, <CommonError<T>>::CollectionTokenLimitExceeded);912			ensure!(new_limits.token_limit > 0, <CommonError<T>>::CollectionTokenLimitExceeded);913914			ensure!(915				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&916				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),917				Error::<T>::OwnerPermissionsCantBeReverted,918			);919920			target_collection.limits = new_limits;921922			target_collection.save()923		}924	}925}