git.delta.rocks / unique-network / refs/commits / 8484aa77f543

difftreelog

source

pallets/nft/src/lib.rs28.3 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, Allowlist,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;102103// # Used definitions104//105// ## User control levels106//107// chain-controlled - key is uncontrolled by user108//                    i.e autoincrementing index109//                    can use non-cryptographic hash110// real - key is controlled by user111//        but it is hard to generate enough colliding values, i.e owner of signed txs112//        can use non-cryptographic hash113// controlled - key is completly controlled by users114//              i.e maps with mutable keys115//              should use cryptographic hash116//117// ## User control level downgrade reasons118//119// ?1 - chain-controlled -> controlled120//      collections/tokens can be destroyed, resulting in massive holes121// ?2 - chain-controlled -> controlled122//      same as ?1, but can be only added, resulting in easier exploitation123// ?3 - real -> controlled124//      no confirmation required, so addresses can be easily generated125decl_storage! {126	trait Store for Module<T: Config> as Nft {127128		//#region Private members129		/// Used for migrations130		ChainVersion: u64;131		//#endregion132133		//#region Tokens transfer rate limit baskets134		/// (Collection id (controlled?2), who created (real))135		/// TODO: Off chain worker should remove from this map when collection gets removed136		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;137		/// Collection id (controlled?2), token id (controlled?2)138		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;139		/// Collection id (controlled?2), owning user (real)140		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;141		/// Collection id (controlled?2), token id (controlled?2)142		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;143		//#endregion144145		/// Variable metadata sponsoring146		/// Collection id (controlled?2), token id (controlled?2)147		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;148	}149}150151decl_module! {152	pub struct Module<T: Config> for enum Call153	where154		origin: T::Origin155	{156		const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;157		type Error = Error<T>;158159		fn on_initialize(_now: T::BlockNumber) -> Weight {160			0161		}162163		/// 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.164		///165		/// # Permissions166		///167		/// * Anyone.168		///169		/// # Arguments170		///171		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.172		///173		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.174		///175		/// * token_prefix: UTF-8 string with token prefix.176		///177		/// * mode: [CollectionMode] collection type and type dependent data.178		// returns collection ID179		#[weight = <SelfWeightOf<T>>::create_collection()]180		#[transactional]181		pub fn create_collection(origin,182								 collection_name: Vec<u16>,183								 collection_description: Vec<u16>,184								 token_prefix: Vec<u8>,185								 mode: CollectionMode) -> DispatchResult {186187			// Anyone can create a collection188			let who = ensure_signed(origin)?;189190			let limits = CollectionLimits::<T::BlockNumber> {191				sponsored_data_size: CUSTOM_DATA_LIMIT,192				..Default::default()193			};194195			// Create new collection196			let new_collection = Collection::<T> {197				owner: who.clone(),198				name: collection_name,199				mode: mode.clone(),200				mint_mode: false,201				access: AccessMode::Normal,202				description: collection_description,203				token_prefix,204				offchain_schema: Vec::new(),205				schema_version: SchemaVersion::ImageURL,206				sponsorship: SponsorshipState::Disabled,207				variable_on_chain_schema: Vec::new(),208				const_on_chain_schema: Vec::new(),209				limits,210				transfers_enabled: true,211				meta_update_permission: Default::default(),212			};213214			let _id = match mode {215				CollectionMode::NFT => {PalletNonfungible::init_collection(new_collection)?},216				CollectionMode::Fungible(decimal_points) => {217					// check params218					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);219					PalletFungible::init_collection(new_collection)?220				}221				CollectionMode::ReFungible => {222					PalletRefungible::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);259260			Ok(())261		}262263		/// Add an address to white list.264		///265		/// # Permissions266		///267		/// * Collection Owner268		/// * Collection Admin269		///270		/// # Arguments271		///272		/// * collection_id.273		///274		/// * address.275		#[weight = <SelfWeightOf<T>>::add_to_white_list()]276		#[transactional]277		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{278279			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);280			let collection = <CollectionHandle<T>>::try_get(collection_id)?;281282			<PalletCommon<T>>::toggle_allowlist(283				&collection,284				&sender,285				&address,286				true,287			)?;288289			Ok(())290		}291292		/// Remove an address from white list.293		///294		/// # Permissions295		///296		/// * Collection Owner297		/// * Collection Admin298		///299		/// # Arguments300		///301		/// * collection_id.302		///303		/// * address.304		#[weight = <SelfWeightOf<T>>::remove_from_white_list()]305		#[transactional]306		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{307308			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);309			let collection = <CollectionHandle<T>>::try_get(collection_id)?;310311			<PalletCommon<T>>::toggle_allowlist(312				&collection,313				&sender,314				&address,315				false,316			)?;317318			Ok(())319		}320321		/// Toggle between normal and white list access for the methods with access for `Anyone`.322		///323		/// # Permissions324		///325		/// * Collection Owner.326		///327		/// # Arguments328		///329		/// * collection_id.330		///331		/// * mode: [AccessMode]332		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]333		#[transactional]334		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult335		{336			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);337338			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;339			target_collection.check_is_owner(&sender)?;340341			target_collection.access = mode;342			target_collection.save()343		}344345		/// Allows Anyone to create tokens if:346		/// * White List is enabled, and347		/// * Address is added to white list, and348		/// * This method was called with True parameter349		///350		/// # Permissions351		/// * Collection Owner352		///353		/// # Arguments354		///355		/// * collection_id.356		///357		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.358		#[weight = <SelfWeightOf<T>>::set_mint_permission()]359		#[transactional]360		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult361		{362			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);363364			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;365			target_collection.check_is_owner(&sender)?;366367			target_collection.mint_mode = mint_permission;368			target_collection.save()369		}370371		/// Change the owner of the collection.372		///373		/// # Permissions374		///375		/// * Collection Owner.376		///377		/// # Arguments378		///379		/// * collection_id.380		///381		/// * new_owner.382		#[weight = <SelfWeightOf<T>>::change_collection_owner()]383		#[transactional]384		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {385386			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);387388			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;389			target_collection.check_is_owner(&sender)?;390391			target_collection.owner = new_owner;392			target_collection.save()393		}394395		/// Adds an admin of the Collection.396		/// 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.397		///398		/// # Permissions399		///400		/// * Collection Owner.401		/// * Collection Admin.402		///403		/// # Arguments404		///405		/// * collection_id: ID of the Collection to add admin for.406		///407		/// * new_admin_id: Address of new admin to add.408		#[weight = <SelfWeightOf<T>>::add_collection_admin()]409		#[transactional]410		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {411			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);412413			let collection = <CollectionHandle<T>>::try_get(collection_id)?;414			collection.check_is_owner_or_admin(&sender)?;415416			<IsAdmin<T>>::insert((collection_id, new_admin_id.as_sub()), true);417			Ok(())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)?);436437			let collection = <CollectionHandle<T>>::try_get(collection_id)?;438			collection.check_is_owner_or_admin(&sender)?;439440			<IsAdmin<T>>::remove((collection_id, account_id.as_sub()));441			Ok(())442		}443444		/// # Permissions445		///446		/// * Collection Owner447		///448		/// # Arguments449		///450		/// * collection_id.451		///452		/// * new_sponsor.453		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]454		#[transactional]455		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {456			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);457458			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;459			target_collection.check_is_owner_or_admin(&sender)?;460461			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);462			target_collection.save()463		}464465		/// # Permissions466		///467		/// * Sponsor.468		///469		/// # Arguments470		///471		/// * collection_id.472		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]473		#[transactional]474		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {475			let sender = ensure_signed(origin)?;476477			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;478			ensure!(479				target_collection.sponsorship.pending_sponsor() == Some(&sender),480				Error::<T>::ConfirmUnsetSponsorFail481			);482483			target_collection.sponsorship = SponsorshipState::Confirmed(sender);484			target_collection.save()485		}486487		/// Switch back to pay-per-own-transaction model.488		///489		/// # Permissions490		///491		/// * Collection owner.492		///493		/// # Arguments494		///495		/// * collection_id.496		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]497		#[transactional]498		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {499			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);500501			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;502			target_collection.check_is_owner(&sender)?;503504			target_collection.sponsorship = SponsorshipState::Disabled;505			target_collection.save()506		}507508		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.509		///510		/// # Permissions511		///512		/// * Collection Owner.513		/// * Collection Admin.514		/// * Anyone if515		///     * White List is enabled, and516		///     * Address is added to white list, and517		///     * MintPermission is enabled (see SetMintPermission method)518		///519		/// # Arguments520		///521		/// * collection_id: ID of the collection.522		///523		/// * owner: Address, initial owner of the NFT.524		///525		/// * data: Token data to store on chain.526		#[weight = <CommonWeights<T>>::create_item()]527		#[transactional]528		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {529			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);530531			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))532		}533534		/// This method creates multiple items in a collection created with CreateCollection method.535		///536		/// # Permissions537		///538		/// * Collection Owner.539		/// * Collection Admin.540		/// * Anyone if541		///     * White List is enabled, and542		///     * Address is added to white list, and543		///     * MintPermission is enabled (see SetMintPermission method)544		///545		/// # Arguments546		///547		/// * collection_id: ID of the collection.548		///549		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].550		///551		/// * owner: Address, initial owner of the NFT.552		#[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]553		#[transactional]554		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {555			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);556			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);557558			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))559		}560561		// TODO! transaction weight562563		/// Set transfers_enabled value for particular collection564		///565		/// # Permissions566		///567		/// * Collection Owner.568		///569		/// # Arguments570		///571		/// * collection_id: ID of the collection.572		///573		/// * value: New flag value.574		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]575		#[transactional]576		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {577			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);578			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;579			target_collection.check_is_owner(&sender)?;580581			// =========582583			target_collection.transfers_enabled = value;584			target_collection.save()585		}586587		/// Destroys a concrete instance of NFT.588		///589		/// # Permissions590		///591		/// * Collection Owner.592		/// * Collection Admin.593		/// * Current NFT Owner.594		///595		/// # Arguments596		///597		/// * collection_id: ID of the collection.598		///599		/// * item_id: ID of NFT to burn.600		#[weight = <CommonWeights<T>>::burn_item()]601		#[transactional]602		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {603			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);604605			dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))606		}607608		/// Change ownership of the token.609		///610		/// # Permissions611		///612		/// * Collection Owner613		/// * Collection Admin614		/// * Current NFT owner615		///616		/// # Arguments617		///618		/// * recipient: Address of token recipient.619		///620		/// * collection_id.621		///622		/// * item_id: ID of the item623		///     * Non-Fungible Mode: Required.624		///     * Fungible Mode: Ignored.625		///     * Re-Fungible Mode: Required.626		///627		/// * value: Amount to transfer.628		///     * Non-Fungible Mode: Ignored629		///     * Fungible Mode: Must specify transferred amount630		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)631		#[weight = <CommonWeights<T>>::transfer()]632		#[transactional]633		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {634			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);635636			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))637		}638639		/// Set, change, or remove approved address to transfer the ownership of the NFT.640		///641		/// # Permissions642		///643		/// * Collection Owner644		/// * Collection Admin645		/// * Current NFT owner646		///647		/// # Arguments648		///649		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).650		///651		/// * collection_id.652		///653		/// * item_id: ID of the item.654		#[weight = <CommonWeights<T>>::approve()]655		#[transactional]656		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {657			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);658659			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))660		}661662		/// 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.663		///664		/// # Permissions665		/// * Collection Owner666		/// * Collection Admin667		/// * Current NFT owner668		/// * Address approved by current NFT owner669		///670		/// # Arguments671		///672		/// * from: Address that owns token.673		///674		/// * recipient: Address of token recipient.675		///676		/// * collection_id.677		///678		/// * item_id: ID of the item.679		///680		/// * value: Amount to transfer.681		#[weight = <CommonWeights<T>>::transfer_from()]682		#[transactional]683		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {684			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);685686			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))687		}688689		/// Set off-chain data schema.690		///691		/// # Permissions692		///693		/// * Collection Owner694		/// * Collection Admin695		///696		/// # Arguments697		///698		/// * collection_id.699		///700		/// * schema: String representing the offchain data schema.701		#[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]702		#[transactional]703		pub fn set_variable_meta_data (704			origin,705			collection_id: CollectionId,706			item_id: TokenId,707			data: Vec<u8>708		) -> DispatchResultWithPostInfo {709			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);710711			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))712		}713714		/// Set meta_update_permission value for particular collection715		///716		/// # Permissions717		///718		/// * Collection Owner.719		///720		/// # Arguments721		///722		/// * collection_id: ID of the collection.723		///724		/// * value: New flag value.725		#[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]726		#[transactional]727		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {728			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);729			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;730731			ensure!(732				target_collection.meta_update_permission != MetaUpdatePermission::None,733				<CommonError<T>>::MetadataFlagFrozen,734			);735			target_collection.check_is_owner(&sender)?;736737			target_collection.meta_update_permission = value;738739			target_collection.save()740		}741742		/// Set schema standard743		/// ImageURL744		/// Unique745		///746		/// # Permissions747		///748		/// * Collection Owner749		/// * Collection Admin750		///751		/// # Arguments752		///753		/// * collection_id.754		///755		/// * schema: SchemaVersion: enum756		#[weight = <SelfWeightOf<T>>::set_schema_version()]757		#[transactional]758		pub fn set_schema_version(759			origin,760			collection_id: CollectionId,761			version: SchemaVersion762		) -> DispatchResult {763			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);764			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;765			target_collection.check_is_owner_or_admin(&sender)?;766			target_collection.schema_version = version;767			target_collection.save()768		}769770		/// Set off-chain data schema.771		///772		/// # Permissions773		///774		/// * Collection Owner775		/// * Collection Admin776		///777		/// # Arguments778		///779		/// * collection_id.780		///781		/// * schema: String representing the offchain data schema.782		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]783		#[transactional]784		pub fn set_offchain_schema(785			origin,786			collection_id: CollectionId,787			schema: Vec<u8>788		) -> DispatchResult {789			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);790			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;791			target_collection.check_is_owner_or_admin(&sender)?;792793			// check schema limit794			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");795796			target_collection.offchain_schema = schema;797			target_collection.save()798		}799800		/// Set const on-chain data schema.801		///802		/// # Permissions803		///804		/// * Collection Owner805		/// * Collection Admin806		///807		/// # Arguments808		///809		/// * collection_id.810		///811		/// * schema: String representing the const on-chain data schema.812		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]813		#[transactional]814		pub fn set_const_on_chain_schema (815			origin,816			collection_id: CollectionId,817			schema: Vec<u8>818		) -> DispatchResult {819			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);820			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;821			target_collection.check_is_owner_or_admin(&sender)?;822823			// check schema limit824			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");825826			target_collection.const_on_chain_schema = schema;827			target_collection.save()828		}829830		/// Set variable on-chain data schema.831		///832		/// # Permissions833		///834		/// * Collection Owner835		/// * Collection Admin836		///837		/// # Arguments838		///839		/// * collection_id.840		///841		/// * schema: String representing the variable on-chain data schema.842		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]843		#[transactional]844		pub fn set_variable_on_chain_schema (845			origin,846			collection_id: CollectionId,847			schema: Vec<u8>848		) -> DispatchResult {849			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);850			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;851			target_collection.check_is_owner_or_admin(&sender)?;852853			// check schema limit854			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");855856			target_collection.variable_on_chain_schema = schema;857			target_collection.save()858		}859860		#[weight = <SelfWeightOf<T>>::set_collection_limits()]861		#[transactional]862		pub fn set_collection_limits(863			origin,864			collection_id: CollectionId,865			new_limits: CollectionLimits<T::BlockNumber>,866		) -> DispatchResult {867			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);868			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;869			target_collection.check_is_owner(&sender)?;870			let old_limits = &target_collection.limits;871872			// collection bounds873			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&874				new_limits.account_token_ownership_limit.unwrap_or(0) <= MAX_TOKEN_OWNERSHIP &&875				new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,876				Error::<T>::CollectionLimitBoundsExceeded);877878			// token_limit   check  prev879			ensure!(old_limits.token_limit >= new_limits.token_limit, <CommonError<T>>::CollectionTokenLimitExceeded);880			ensure!(new_limits.token_limit > 0, <CommonError<T>>::CollectionTokenLimitExceeded);881882			ensure!(883				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&884				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),885				Error::<T>::OwnerPermissionsCantBeReverted,886			);887888			target_collection.limits = new_limits;889890			target_collection.save()891		}892	}893}894895// TODO: limit returned entries?896impl<T: Config> Pallet<T> {897	pub fn adminlist(collection: CollectionId) -> Vec<T::AccountId> {898		<IsAdmin<T>>::iter_prefix((collection,))899			.map(|(a, _)| a)900			.collect()901	}902	pub fn allowlist(collection: CollectionId) -> Vec<T::AccountId> {903		<Allowlist<T>>::iter_prefix((collection,))904			.map(|(a, _)| a)905			.collect()906	}907}