git.delta.rocks / unique-network / refs/commits / 4e90c111d3d7

difftreelog

source

pallets/nft/src/lib.rs29.6 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 nft_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, IsAdmin, Pallet as PalletCommon,46	Error as CommonError, CommonWeightInfo, Allowlist,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::NftSponsorshipHandler;61pub use eth::sponsoring::NftEthSponsorshipHandler;6263pub use eth::NftErcSupport;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 Nft {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) => 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 => 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 => T::BlockNumber;144		/// Collection id (controlled?2), token id (controlled?2)145		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => 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> = None;151	}152}153154decl_module! {155	pub struct Module<T: Config> for enum Call156	where157		origin: T::Origin158	{159		type Error = Error<T>;160161		fn on_initialize(_now: T::BlockNumber) -> Weight {162			0163		}164165		/// 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.166		///167		/// # Permissions168		///169		/// * Anyone.170		///171		/// # Arguments172		///173		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.174		///175		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.176		///177		/// * token_prefix: UTF-8 string with token prefix.178		///179		/// * mode: [CollectionMode] collection type and type dependent data.180		// returns collection ID181		#[weight = <SelfWeightOf<T>>::create_collection()]182		#[transactional]183		pub fn create_collection(origin,184								 collection_name: Vec<u16>,185								 collection_description: Vec<u16>,186								 token_prefix: Vec<u8>,187								 mode: CollectionMode) -> DispatchResult {188189			// Anyone can create a collection190			let who = ensure_signed(origin)?;191192			// Create new collection193			let new_collection = Collection::<T> {194				owner: who.clone(),195				name: collection_name,196				mode: mode.clone(),197				mint_mode: false,198				access: AccessMode::Normal,199				description: collection_description,200				token_prefix,201				offchain_schema: Vec::new(),202				schema_version: SchemaVersion::ImageURL,203				sponsorship: SponsorshipState::Disabled,204				variable_on_chain_schema: Vec::new(),205				const_on_chain_schema: Vec::new(),206				limits: Default::default(),207				meta_update_permission: Default::default(),208			};209210			let _id = match mode {211				CollectionMode::NFT => {PalletNonfungible::init_collection(new_collection)?},212				CollectionMode::Fungible(decimal_points) => {213					// check params214					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);215					PalletFungible::init_collection(new_collection)?216				}217				CollectionMode::ReFungible => {218					PalletRefungible::init_collection(new_collection)?219				}220			};221222			Ok(())223		}224225		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.226		///227		/// # Permissions228		///229		/// * Collection Owner.230		///231		/// # Arguments232		///233		/// * collection_id: collection to destroy.234		#[weight = <SelfWeightOf<T>>::destroy_collection()]235		#[transactional]236		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {237			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);238239			let collection = <CollectionHandle<T>>::try_get(collection_id)?;240			collection.check_is_owner(&sender)?;241242			// =========243244			match collection.mode {245				CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,246				CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,247				CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,248			}249250			<NftTransferBasket<T>>::remove_prefix(collection_id, None);251			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);252			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);253254			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);255256			Ok(())257		}258259		/// Add an address to white list.260		///261		/// # Permissions262		///263		/// * Collection Owner264		/// * Collection Admin265		///266		/// # Arguments267		///268		/// * collection_id.269		///270		/// * address.271		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]272		#[transactional]273		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{274275			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);276			let collection = <CollectionHandle<T>>::try_get(collection_id)?;277278			<PalletCommon<T>>::toggle_allowlist(279				&collection,280				&sender,281				&address,282				true,283			)?;284285			Ok(())286		}287288		/// Remove an address from white list.289		///290		/// # Permissions291		///292		/// * Collection Owner293		/// * Collection Admin294		///295		/// # Arguments296		///297		/// * collection_id.298		///299		/// * address.300		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]301		#[transactional]302		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{303304			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);305			let collection = <CollectionHandle<T>>::try_get(collection_id)?;306307			<PalletCommon<T>>::toggle_allowlist(308				&collection,309				&sender,310				&address,311				false,312			)?;313314			Ok(())315		}316317		/// Toggle between normal and white list access for the methods with access for `Anyone`.318		///319		/// # Permissions320		///321		/// * Collection Owner.322		///323		/// # Arguments324		///325		/// * collection_id.326		///327		/// * mode: [AccessMode]328		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]329		#[transactional]330		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult331		{332			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);333334			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;335			target_collection.check_is_owner(&sender)?;336337			target_collection.access = mode;338			target_collection.save()339		}340341		/// Allows Anyone to create tokens if:342		/// * White List is enabled, and343		/// * Address is added to white list, and344		/// * This method was called with True parameter345		///346		/// # Permissions347		/// * Collection Owner348		///349		/// # Arguments350		///351		/// * collection_id.352		///353		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.354		#[weight = <SelfWeightOf<T>>::set_mint_permission()]355		#[transactional]356		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult357		{358			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);359360			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;361			target_collection.check_is_owner(&sender)?;362363			target_collection.mint_mode = mint_permission;364			target_collection.save()365		}366367		/// Change the owner of the collection.368		///369		/// # Permissions370		///371		/// * Collection Owner.372		///373		/// # Arguments374		///375		/// * collection_id.376		///377		/// * new_owner.378		#[weight = <SelfWeightOf<T>>::change_collection_owner()]379		#[transactional]380		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {381382			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);383384			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;385			target_collection.check_is_owner(&sender)?;386387			target_collection.owner = new_owner;388			target_collection.save()389		}390391		/// Adds an admin of the Collection.392		/// 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.393		///394		/// # Permissions395		///396		/// * Collection Owner.397		/// * Collection Admin.398		///399		/// # Arguments400		///401		/// * collection_id: ID of the Collection to add admin for.402		///403		/// * new_admin_id: Address of new admin to add.404		#[weight = <SelfWeightOf<T>>::add_collection_admin()]405		#[transactional]406		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {407			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);408			let collection = <CollectionHandle<T>>::try_get(collection_id)?;409410			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)411		}412413		/// 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.414		///415		/// # Permissions416		///417		/// * Collection Owner.418		/// * Collection Admin.419		///420		/// # Arguments421		///422		/// * collection_id: ID of the Collection to remove admin for.423		///424		/// * account_id: Address of admin to remove.425		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]426		#[transactional]427		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {428			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);429			let collection = <CollectionHandle<T>>::try_get(collection_id)?;430431			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)432		}433434		/// # Permissions435		///436		/// * Collection Owner437		///438		/// # Arguments439		///440		/// * collection_id.441		///442		/// * new_sponsor.443		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]444		#[transactional]445		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {446			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);447448			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;449			target_collection.check_is_owner(&sender)?;450451			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);452			target_collection.save()453		}454455		/// # Permissions456		///457		/// * Sponsor.458		///459		/// # Arguments460		///461		/// * collection_id.462		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]463		#[transactional]464		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {465			let sender = ensure_signed(origin)?;466467			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;468			ensure!(469				target_collection.sponsorship.pending_sponsor() == Some(&sender),470				Error::<T>::ConfirmUnsetSponsorFail471			);472473			target_collection.sponsorship = SponsorshipState::Confirmed(sender);474			target_collection.save()475		}476477		/// Switch back to pay-per-own-transaction model.478		///479		/// # Permissions480		///481		/// * Collection owner.482		///483		/// # Arguments484		///485		/// * collection_id.486		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]487		#[transactional]488		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {489			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);490491			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;492			target_collection.check_is_owner(&sender)?;493494			target_collection.sponsorship = SponsorshipState::Disabled;495			target_collection.save()496		}497498		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.499		///500		/// # Permissions501		///502		/// * Collection Owner.503		/// * Collection Admin.504		/// * Anyone if505		///     * White List is enabled, and506		///     * Address is added to white list, and507		///     * MintPermission is enabled (see SetMintPermission method)508		///509		/// # Arguments510		///511		/// * collection_id: ID of the collection.512		///513		/// * owner: Address, initial owner of the NFT.514		///515		/// * data: Token data to store on chain.516		#[weight = <CommonWeights<T>>::create_item()]517		#[transactional]518		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {519			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);520521			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))522		}523524		/// This method creates multiple items in a collection created with CreateCollection method.525		///526		/// # Permissions527		///528		/// * Collection Owner.529		/// * Collection Admin.530		/// * Anyone if531		///     * White List is enabled, and532		///     * Address is added to white list, and533		///     * MintPermission is enabled (see SetMintPermission method)534		///535		/// # Arguments536		///537		/// * collection_id: ID of the collection.538		///539		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].540		///541		/// * owner: Address, initial owner of the NFT.542		#[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]543		#[transactional]544		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {545			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);546			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);547548			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))549		}550551		// TODO! transaction weight552553		/// Set transfers_enabled value for particular collection554		///555		/// # Permissions556		///557		/// * Collection Owner.558		///559		/// # Arguments560		///561		/// * collection_id: ID of the collection.562		///563		/// * value: New flag value.564		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]565		#[transactional]566		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {567			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);568			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;569			target_collection.check_is_owner(&sender)?;570571			// =========572573			target_collection.limits.transfers_enabled = Some(value);574			target_collection.save()575		}576577		/// Destroys a concrete instance of NFT.578		///579		/// # Permissions580		///581		/// * Collection Owner.582		/// * Collection Admin.583		/// * Current NFT Owner.584		///585		/// # Arguments586		///587		/// * collection_id: ID of the collection.588		///589		/// * item_id: ID of NFT to burn.590		#[weight = <CommonWeights<T>>::burn_item()]591		#[transactional]592		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {593			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);594595			dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))596		}597598		/// Destroys a concrete instance of NFT on behalf of the owner599		/// See also: [`approve`]600		///601		/// # Permissions602		///603		/// * Collection Owner.604		/// * Collection Admin.605		/// * Current NFT Owner.606		///607		/// # Arguments608		///609		/// * collection_id: ID of the collection.610		///611		/// * item_id: ID of NFT to burn.612		///613		/// * from: owner of item614		#[weight = <CommonWeights<T>>::burn_from()]615		#[transactional]616		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {617			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);618619			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))620		}621622		/// Change ownership of the token.623		///624		/// # Permissions625		///626		/// * Collection Owner627		/// * Collection Admin628		/// * Current NFT owner629		///630		/// # Arguments631		///632		/// * recipient: Address of token recipient.633		///634		/// * collection_id.635		///636		/// * item_id: ID of the item637		///     * Non-Fungible Mode: Required.638		///     * Fungible Mode: Ignored.639		///     * Re-Fungible Mode: Required.640		///641		/// * value: Amount to transfer.642		///     * Non-Fungible Mode: Ignored643		///     * Fungible Mode: Must specify transferred amount644		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)645		#[weight = <CommonWeights<T>>::transfer()]646		#[transactional]647		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {648			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);649650			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))651		}652653		/// Set, change, or remove approved address to transfer the ownership of the NFT.654		///655		/// # Permissions656		///657		/// * Collection Owner658		/// * Collection Admin659		/// * Current NFT owner660		///661		/// # Arguments662		///663		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).664		///665		/// * collection_id.666		///667		/// * item_id: ID of the item.668		#[weight = <CommonWeights<T>>::approve()]669		#[transactional]670		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {671			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);672673			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))674		}675676		/// 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.677		///678		/// # Permissions679		/// * Collection Owner680		/// * Collection Admin681		/// * Current NFT owner682		/// * Address approved by current NFT owner683		///684		/// # Arguments685		///686		/// * from: Address that owns token.687		///688		/// * recipient: Address of token recipient.689		///690		/// * collection_id.691		///692		/// * item_id: ID of the item.693		///694		/// * value: Amount to transfer.695		#[weight = <CommonWeights<T>>::transfer_from()]696		#[transactional]697		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {698			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);699700			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))701		}702703		/// Set off-chain data schema.704		///705		/// # Permissions706		///707		/// * Collection Owner708		/// * Collection Admin709		///710		/// # Arguments711		///712		/// * collection_id.713		///714		/// * schema: String representing the offchain data schema.715		#[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]716		#[transactional]717		pub fn set_variable_meta_data (718			origin,719			collection_id: CollectionId,720			item_id: TokenId,721			data: Vec<u8>722		) -> DispatchResultWithPostInfo {723			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);724725			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))726		}727728		/// Set meta_update_permission value for particular collection729		///730		/// # Permissions731		///732		/// * Collection Owner.733		///734		/// # Arguments735		///736		/// * collection_id: ID of the collection.737		///738		/// * value: New flag value.739		#[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]740		#[transactional]741		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {742			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);743			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;744745			ensure!(746				target_collection.meta_update_permission != MetaUpdatePermission::None,747				<CommonError<T>>::MetadataFlagFrozen,748			);749			target_collection.check_is_owner(&sender)?;750751			target_collection.meta_update_permission = value;752753			target_collection.save()754		}755756		/// Set schema standard757		/// ImageURL758		/// Unique759		///760		/// # Permissions761		///762		/// * Collection Owner763		/// * Collection Admin764		///765		/// # Arguments766		///767		/// * collection_id.768		///769		/// * schema: SchemaVersion: enum770		#[weight = <SelfWeightOf<T>>::set_schema_version()]771		#[transactional]772		pub fn set_schema_version(773			origin,774			collection_id: CollectionId,775			version: SchemaVersion776		) -> DispatchResult {777			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);778			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;779			target_collection.check_is_owner_or_admin(&sender)?;780			target_collection.schema_version = version;781			target_collection.save()782		}783784		/// Set off-chain data schema.785		///786		/// # Permissions787		///788		/// * Collection Owner789		/// * Collection Admin790		///791		/// # Arguments792		///793		/// * collection_id.794		///795		/// * schema: String representing the offchain data schema.796		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]797		#[transactional]798		pub fn set_offchain_schema(799			origin,800			collection_id: CollectionId,801			schema: Vec<u8>802		) -> DispatchResult {803			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);804			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;805			target_collection.check_is_owner_or_admin(&sender)?;806807			// check schema limit808			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");809810			target_collection.offchain_schema = schema;811			target_collection.save()812		}813814		/// Set const on-chain data schema.815		///816		/// # Permissions817		///818		/// * Collection Owner819		/// * Collection Admin820		///821		/// # Arguments822		///823		/// * collection_id.824		///825		/// * schema: String representing the const on-chain data schema.826		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]827		#[transactional]828		pub fn set_const_on_chain_schema (829			origin,830			collection_id: CollectionId,831			schema: Vec<u8>832		) -> DispatchResult {833			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);834			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;835			target_collection.check_is_owner_or_admin(&sender)?;836837			// check schema limit838			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");839840			target_collection.const_on_chain_schema = schema;841			target_collection.save()842		}843844		/// Set variable on-chain data schema.845		///846		/// # Permissions847		///848		/// * Collection Owner849		/// * Collection Admin850		///851		/// # Arguments852		///853		/// * collection_id.854		///855		/// * schema: String representing the variable on-chain data schema.856		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]857		#[transactional]858		pub fn set_variable_on_chain_schema (859			origin,860			collection_id: CollectionId,861			schema: Vec<u8>862		) -> DispatchResult {863			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);864			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;865			target_collection.check_is_owner_or_admin(&sender)?;866867			// check schema limit868			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");869870			target_collection.variable_on_chain_schema = schema;871			target_collection.save()872		}873874		#[weight = <SelfWeightOf<T>>::set_collection_limits()]875		#[transactional]876		pub fn set_collection_limits(877			origin,878			collection_id: CollectionId,879			new_limit: CollectionLimits,880		) -> DispatchResult {881			let mut new_limit = new_limit;882			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);883			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;884			target_collection.check_is_owner(&sender)?;885			let old_limit = &target_collection.limits;886887			macro_rules! limit_default {888				($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{889					$(890						if let Some($new) = $new.$field {891							let $old = $old.$field($($arg)?);892							let _ = $new;893							let _ = $old;894							$check895						} else {896							$new.$field = $old.$field897						}898					)*899				}};900			}901902			limit_default!(old_limit, new_limit,903				account_token_ownership_limit => ensure!(904					new_limit <= MAX_TOKEN_OWNERSHIP,905					<Error<T>>::CollectionLimitBoundsExceeded,906				),907				sponsor_transfer_timeout(match target_collection.mode {908					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,909					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,910					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,911				}) => ensure!(912					new_limit <= MAX_SPONSOR_TIMEOUT,913					<Error<T>>::CollectionLimitBoundsExceeded,914				),915				sponsored_data_size => ensure!(916					new_limit <= CUSTOM_DATA_LIMIT,917					<Error<T>>::CollectionLimitBoundsExceeded,918				),919				token_limit => ensure!(920					old_limit >= new_limit && new_limit > 0,921					<CommonError<T>>::CollectionTokenLimitExceeded922				),923				owner_can_transfer => ensure!(924					old_limit || !new_limit,925					<Error<T>>::OwnerPermissionsCantBeReverted,926				),927				owner_can_destroy => ensure!(928					old_limit || !new_limit,929					<Error<T>>::OwnerPermissionsCantBeReverted,930				),931				sponsored_data_rate_limit => {},932				transfers_enabled => {},933			);934935			target_collection.limits = new_limit;936937			target_collection.save()938		}939	}940}941942// TODO: limit returned entries?943impl<T: Config> Pallet<T> {944	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {945		<IsAdmin<T>>::iter_prefix((collection,))946			.map(|(a, _)| a)947			.collect()948	}949	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {950		<Allowlist<T>>::iter_prefix((collection,))951			.map(|(a, _)| a)952			.collect()953	}954}