git.delta.rocks / unique-network / refs/commits / 283713e8c803

difftreelog

source

pallets/nft/src/lib.rs29.0 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, COLLECTION_ADMINS_LIMIT,40	OFFCHAIN_SCHEMA_LIMIT, AccessMode, Collection, CreateItemData, CollectionLimits, CollectionId,41	CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,42};43use pallet_common::{44	account::CrossAccountId, CollectionHandle, IsAdmin, Pallet as PalletCommon,45	Error as CommonError, CommonWeightInfo, Allowlist,46};47use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};48use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};49use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};5051#[cfg(test)]52mod mock;5354#[cfg(test)]55mod tests;5657mod eth;58mod sponsorship;59pub use sponsorship::NftSponsorshipHandler;60pub use eth::sponsoring::NftEthSponsorshipHandler;6162pub use eth::NftErcSupport;6364pub mod common;65use common::CommonWeights;66pub mod dispatch;67use dispatch::dispatch_call;6869#[cfg(feature = "runtime-benchmarks")]70mod benchmarking;71pub mod weights;72use weights::WeightInfo;7374decl_error! {75	/// Error for non-fungible-token module.76	pub enum Error for Module<T: Config> {77		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.78		CollectionDecimalPointLimitExceeded,79		/// This address is not set as sponsor, use setCollectionSponsor first.80		ConfirmUnsetSponsorFail,81		/// Length of items properties must be greater than 0.82		EmptyArgument,83		/// Collection limit bounds per collection exceeded84		CollectionLimitBoundsExceeded,85		/// Tried to enable permissions which are only permitted to be disabled86		OwnerPermissionsCantBeReverted,87	}88}89pub trait Config:90	system::Config91	+ pallet_evm_coder_substrate::Config92	+ pallet_common::Config93	+ pallet_nonfungible::Config94	+ pallet_refungible::Config95	+ pallet_fungible::Config96	+ Sized97	+ TypeInfo98{99	/// Weight information for extrinsics in this pallet.100	type WeightInfo: WeightInfo;101}102103type SelfWeightOf<T> = <T as Config>::WeightInfo;104105// # Used definitions106//107// ## User control levels108//109// chain-controlled - key is uncontrolled by user110//                    i.e autoincrementing index111//                    can use non-cryptographic hash112// real - key is controlled by user113//        but it is hard to generate enough colliding values, i.e owner of signed txs114//        can use non-cryptographic hash115// controlled - key is completly controlled by users116//              i.e maps with mutable keys117//              should use cryptographic hash118//119// ## User control level downgrade reasons120//121// ?1 - chain-controlled -> controlled122//      collections/tokens can be destroyed, resulting in massive holes123// ?2 - chain-controlled -> controlled124//      same as ?1, but can be only added, resulting in easier exploitation125// ?3 - real -> controlled126//      no confirmation required, so addresses can be easily generated127decl_storage! {128	trait Store for Module<T: Config> as Nft {129130		//#region Private members131		/// Used for migrations132		ChainVersion: u64;133		//#endregion134135		//#region Tokens transfer rate limit baskets136		/// (Collection id (controlled?2), who created (real))137		/// TODO: Off chain worker should remove from this map when collection gets removed138		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;139		/// Collection id (controlled?2), token id (controlled?2)140		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;141		/// Collection id (controlled?2), owning user (real)142		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;143		/// Collection id (controlled?2), token id (controlled?2)144		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;145		//#endregion146147		/// Variable metadata sponsoring148		/// Collection id (controlled?2), token id (controlled?2)149		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;150	}151}152153decl_module! {154	pub struct Module<T: Config> for enum Call155	where156		origin: T::Origin157	{158		const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;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			let limits = CollectionLimits::<T::BlockNumber> {193				sponsored_data_size: CUSTOM_DATA_LIMIT,194				..Default::default()195			};196197			// Create new collection198			let new_collection = Collection::<T> {199				owner: who.clone(),200				name: collection_name,201				mode: mode.clone(),202				mint_mode: false,203				access: AccessMode::Normal,204				description: collection_description,205				token_prefix,206				offchain_schema: Vec::new(),207				schema_version: SchemaVersion::ImageURL,208				sponsorship: SponsorshipState::Disabled,209				variable_on_chain_schema: Vec::new(),210				const_on_chain_schema: Vec::new(),211				limits,212				transfers_enabled: true,213				meta_update_permission: Default::default(),214			};215216			let _id = match mode {217				CollectionMode::NFT => {PalletNonfungible::init_collection(new_collection)?},218				CollectionMode::Fungible(decimal_points) => {219					// check params220					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);221					PalletFungible::init_collection(new_collection)?222				}223				CollectionMode::ReFungible => {224					PalletRefungible::init_collection(new_collection)?225				}226			};227228			Ok(())229		}230231		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.232		///233		/// # Permissions234		///235		/// * Collection Owner.236		///237		/// # Arguments238		///239		/// * collection_id: collection to destroy.240		#[weight = <SelfWeightOf<T>>::destroy_collection()]241		#[transactional]242		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {243			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);244245			let collection = <CollectionHandle<T>>::try_get(collection_id)?;246			collection.check_is_owner(&sender)?;247248			// =========249250			match collection.mode {251				CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,252				CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,253				CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,254			}255256			<NftTransferBasket<T>>::remove_prefix(collection_id, None);257			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);258			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);259260			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);261262			Ok(())263		}264265		/// Add an address to white list.266		///267		/// # Permissions268		///269		/// * Collection Owner270		/// * Collection Admin271		///272		/// # Arguments273		///274		/// * collection_id.275		///276		/// * address.277		#[weight = <SelfWeightOf<T>>::add_to_white_list()]278		#[transactional]279		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{280281			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);282			let collection = <CollectionHandle<T>>::try_get(collection_id)?;283284			<PalletCommon<T>>::toggle_allowlist(285				&collection,286				&sender,287				&address,288				true,289			)?;290291			Ok(())292		}293294		/// Remove an address from white list.295		///296		/// # Permissions297		///298		/// * Collection Owner299		/// * Collection Admin300		///301		/// # Arguments302		///303		/// * collection_id.304		///305		/// * address.306		#[weight = <SelfWeightOf<T>>::remove_from_white_list()]307		#[transactional]308		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{309310			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);311			let collection = <CollectionHandle<T>>::try_get(collection_id)?;312313			<PalletCommon<T>>::toggle_allowlist(314				&collection,315				&sender,316				&address,317				false,318			)?;319320			Ok(())321		}322323		/// Toggle between normal and white list access for the methods with access for `Anyone`.324		///325		/// # Permissions326		///327		/// * Collection Owner.328		///329		/// # Arguments330		///331		/// * collection_id.332		///333		/// * mode: [AccessMode]334		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]335		#[transactional]336		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult337		{338			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);339340			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;341			target_collection.check_is_owner(&sender)?;342343			target_collection.access = mode;344			target_collection.save()345		}346347		/// Allows Anyone to create tokens if:348		/// * White List is enabled, and349		/// * Address is added to white list, and350		/// * This method was called with True parameter351		///352		/// # Permissions353		/// * Collection Owner354		///355		/// # Arguments356		///357		/// * collection_id.358		///359		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.360		#[weight = <SelfWeightOf<T>>::set_mint_permission()]361		#[transactional]362		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult363		{364			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);365366			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;367			target_collection.check_is_owner(&sender)?;368369			target_collection.mint_mode = mint_permission;370			target_collection.save()371		}372373		/// Change the owner of the collection.374		///375		/// # Permissions376		///377		/// * Collection Owner.378		///379		/// # Arguments380		///381		/// * collection_id.382		///383		/// * new_owner.384		#[weight = <SelfWeightOf<T>>::change_collection_owner()]385		#[transactional]386		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {387388			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);389390			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;391			target_collection.check_is_owner(&sender)?;392393			target_collection.owner = new_owner;394			target_collection.save()395		}396397		/// Adds an admin of the Collection.398		/// 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.399		///400		/// # Permissions401		///402		/// * Collection Owner.403		/// * Collection Admin.404		///405		/// # Arguments406		///407		/// * collection_id: ID of the Collection to add admin for.408		///409		/// * new_admin_id: Address of new admin to add.410		#[weight = <SelfWeightOf<T>>::add_collection_admin()]411		#[transactional]412		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {413			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);414415			let collection = <CollectionHandle<T>>::try_get(collection_id)?;416			collection.check_is_owner_or_admin(&sender)?;417418			<IsAdmin<T>>::insert((collection_id, new_admin_id.as_sub()), true);419			Ok(())420		}421422		/// 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.423		///424		/// # Permissions425		///426		/// * Collection Owner.427		/// * Collection Admin.428		///429		/// # Arguments430		///431		/// * collection_id: ID of the Collection to remove admin for.432		///433		/// * account_id: Address of admin to remove.434		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]435		#[transactional]436		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {437			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);438439			let collection = <CollectionHandle<T>>::try_get(collection_id)?;440			collection.check_is_owner_or_admin(&sender)?;441442			<IsAdmin<T>>::remove((collection_id, account_id.as_sub()));443			Ok(())444		}445446		/// # Permissions447		///448		/// * Collection Owner449		///450		/// # Arguments451		///452		/// * collection_id.453		///454		/// * new_sponsor.455		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]456		#[transactional]457		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {458			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);459460			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;461			target_collection.check_is_owner_or_admin(&sender)?;462463			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);464			target_collection.save()465		}466467		/// # Permissions468		///469		/// * Sponsor.470		///471		/// # Arguments472		///473		/// * collection_id.474		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]475		#[transactional]476		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {477			let sender = ensure_signed(origin)?;478479			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;480			ensure!(481				target_collection.sponsorship.pending_sponsor() == Some(&sender),482				Error::<T>::ConfirmUnsetSponsorFail483			);484485			target_collection.sponsorship = SponsorshipState::Confirmed(sender);486			target_collection.save()487		}488489		/// Switch back to pay-per-own-transaction model.490		///491		/// # Permissions492		///493		/// * Collection owner.494		///495		/// # Arguments496		///497		/// * collection_id.498		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]499		#[transactional]500		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {501			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);502503			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;504			target_collection.check_is_owner(&sender)?;505506			target_collection.sponsorship = SponsorshipState::Disabled;507			target_collection.save()508		}509510		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.511		///512		/// # Permissions513		///514		/// * Collection Owner.515		/// * Collection Admin.516		/// * Anyone if517		///     * White List is enabled, and518		///     * Address is added to white list, and519		///     * MintPermission is enabled (see SetMintPermission method)520		///521		/// # Arguments522		///523		/// * collection_id: ID of the collection.524		///525		/// * owner: Address, initial owner of the NFT.526		///527		/// * data: Token data to store on chain.528		#[weight = <CommonWeights<T>>::create_item()]529		#[transactional]530		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {531			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);532533			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))534		}535536		/// This method creates multiple items in a collection created with CreateCollection method.537		///538		/// # Permissions539		///540		/// * Collection Owner.541		/// * Collection Admin.542		/// * Anyone if543		///     * White List is enabled, and544		///     * Address is added to white list, and545		///     * MintPermission is enabled (see SetMintPermission method)546		///547		/// # Arguments548		///549		/// * collection_id: ID of the collection.550		///551		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].552		///553		/// * owner: Address, initial owner of the NFT.554		#[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]555		#[transactional]556		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {557			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);558			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);559560			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))561		}562563		// TODO! transaction weight564565		/// Set transfers_enabled value for particular collection566		///567		/// # Permissions568		///569		/// * Collection Owner.570		///571		/// # Arguments572		///573		/// * collection_id: ID of the collection.574		///575		/// * value: New flag value.576		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]577		#[transactional]578		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {579			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);580			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;581			target_collection.check_is_owner(&sender)?;582583			// =========584585			target_collection.transfers_enabled = value;586			target_collection.save()587		}588589		/// Destroys a concrete instance of NFT.590		///591		/// # Permissions592		///593		/// * Collection Owner.594		/// * Collection Admin.595		/// * Current NFT Owner.596		///597		/// # Arguments598		///599		/// * collection_id: ID of the collection.600		///601		/// * item_id: ID of NFT to burn.602		#[weight = <CommonWeights<T>>::burn_item()]603		#[transactional]604		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {605			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);606607			dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))608		}609610		/// Destroys a concrete instance of NFT on behalf of the owner611		/// See also: [`approve`]612		///613		/// # Permissions614		///615		/// * Collection Owner.616		/// * Collection Admin.617		/// * Current NFT Owner.618		///619		/// # Arguments620		///621		/// * collection_id: ID of the collection.622		///623		/// * item_id: ID of NFT to burn.624		///625		/// * from: owner of item626		// #[weight = 0]627		// #[transactional]628		// pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> PostDispatchInfo {629		// 	let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);630631		// 	// dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))632		// 	todo!()633		// }634635		/// Change ownership of the token.636		///637		/// # Permissions638		///639		/// * Collection Owner640		/// * Collection Admin641		/// * Current NFT owner642		///643		/// # Arguments644		///645		/// * recipient: Address of token recipient.646		///647		/// * collection_id.648		///649		/// * item_id: ID of the item650		///     * Non-Fungible Mode: Required.651		///     * Fungible Mode: Ignored.652		///     * Re-Fungible Mode: Required.653		///654		/// * value: Amount to transfer.655		///     * Non-Fungible Mode: Ignored656		///     * Fungible Mode: Must specify transferred amount657		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)658		#[weight = <CommonWeights<T>>::transfer()]659		#[transactional]660		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {661			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);662663			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))664		}665666		/// Set, change, or remove approved address to transfer the ownership of the NFT.667		///668		/// # Permissions669		///670		/// * Collection Owner671		/// * Collection Admin672		/// * Current NFT owner673		///674		/// # Arguments675		///676		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).677		///678		/// * collection_id.679		///680		/// * item_id: ID of the item.681		#[weight = <CommonWeights<T>>::approve()]682		#[transactional]683		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {684			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);685686			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))687		}688689		/// 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.690		///691		/// # Permissions692		/// * Collection Owner693		/// * Collection Admin694		/// * Current NFT owner695		/// * Address approved by current NFT owner696		///697		/// # Arguments698		///699		/// * from: Address that owns token.700		///701		/// * recipient: Address of token recipient.702		///703		/// * collection_id.704		///705		/// * item_id: ID of the item.706		///707		/// * value: Amount to transfer.708		#[weight = <CommonWeights<T>>::transfer_from()]709		#[transactional]710		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {711			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);712713			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))714		}715716		/// Set off-chain data schema.717		///718		/// # Permissions719		///720		/// * Collection Owner721		/// * Collection Admin722		///723		/// # Arguments724		///725		/// * collection_id.726		///727		/// * schema: String representing the offchain data schema.728		#[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]729		#[transactional]730		pub fn set_variable_meta_data (731			origin,732			collection_id: CollectionId,733			item_id: TokenId,734			data: Vec<u8>735		) -> DispatchResultWithPostInfo {736			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);737738			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))739		}740741		/// Set meta_update_permission value for particular collection742		///743		/// # Permissions744		///745		/// * Collection Owner.746		///747		/// # Arguments748		///749		/// * collection_id: ID of the collection.750		///751		/// * value: New flag value.752		#[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]753		#[transactional]754		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {755			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);756			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;757758			ensure!(759				target_collection.meta_update_permission != MetaUpdatePermission::None,760				<CommonError<T>>::MetadataFlagFrozen,761			);762			target_collection.check_is_owner(&sender)?;763764			target_collection.meta_update_permission = value;765766			target_collection.save()767		}768769		/// Set schema standard770		/// ImageURL771		/// Unique772		///773		/// # Permissions774		///775		/// * Collection Owner776		/// * Collection Admin777		///778		/// # Arguments779		///780		/// * collection_id.781		///782		/// * schema: SchemaVersion: enum783		#[weight = <SelfWeightOf<T>>::set_schema_version()]784		#[transactional]785		pub fn set_schema_version(786			origin,787			collection_id: CollectionId,788			version: SchemaVersion789		) -> DispatchResult {790			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);791			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;792			target_collection.check_is_owner_or_admin(&sender)?;793			target_collection.schema_version = version;794			target_collection.save()795		}796797		/// Set off-chain data schema.798		///799		/// # Permissions800		///801		/// * Collection Owner802		/// * Collection Admin803		///804		/// # Arguments805		///806		/// * collection_id.807		///808		/// * schema: String representing the offchain data schema.809		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]810		#[transactional]811		pub fn set_offchain_schema(812			origin,813			collection_id: CollectionId,814			schema: Vec<u8>815		) -> DispatchResult {816			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);817			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;818			target_collection.check_is_owner_or_admin(&sender)?;819820			// check schema limit821			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");822823			target_collection.offchain_schema = schema;824			target_collection.save()825		}826827		/// Set const on-chain data schema.828		///829		/// # Permissions830		///831		/// * Collection Owner832		/// * Collection Admin833		///834		/// # Arguments835		///836		/// * collection_id.837		///838		/// * schema: String representing the const on-chain data schema.839		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]840		#[transactional]841		pub fn set_const_on_chain_schema (842			origin,843			collection_id: CollectionId,844			schema: Vec<u8>845		) -> DispatchResult {846			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);847			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;848			target_collection.check_is_owner_or_admin(&sender)?;849850			// check schema limit851			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");852853			target_collection.const_on_chain_schema = schema;854			target_collection.save()855		}856857		/// Set variable on-chain data schema.858		///859		/// # Permissions860		///861		/// * Collection Owner862		/// * Collection Admin863		///864		/// # Arguments865		///866		/// * collection_id.867		///868		/// * schema: String representing the variable on-chain data schema.869		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]870		#[transactional]871		pub fn set_variable_on_chain_schema (872			origin,873			collection_id: CollectionId,874			schema: Vec<u8>875		) -> DispatchResult {876			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);877			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;878			target_collection.check_is_owner_or_admin(&sender)?;879880			// check schema limit881			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");882883			target_collection.variable_on_chain_schema = schema;884			target_collection.save()885		}886887		#[weight = <SelfWeightOf<T>>::set_collection_limits()]888		#[transactional]889		pub fn set_collection_limits(890			origin,891			collection_id: CollectionId,892			new_limits: CollectionLimits<T::BlockNumber>,893		) -> DispatchResult {894			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);895			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;896			target_collection.check_is_owner(&sender)?;897			let old_limits = &target_collection.limits;898899			// collection bounds900			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&901				new_limits.account_token_ownership_limit.unwrap_or(0) <= MAX_TOKEN_OWNERSHIP &&902				new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,903				Error::<T>::CollectionLimitBoundsExceeded);904905			// token_limit   check  prev906			ensure!(old_limits.token_limit >= new_limits.token_limit, <CommonError<T>>::CollectionTokenLimitExceeded);907			ensure!(new_limits.token_limit > 0, <CommonError<T>>::CollectionTokenLimitExceeded);908909			ensure!(910				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&911				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),912				Error::<T>::OwnerPermissionsCantBeReverted,913			);914915			target_collection.limits = new_limits;916917			target_collection.save()918		}919	}920}921922// TODO: limit returned entries?923impl<T: Config> Pallet<T> {924	pub fn adminlist(collection: CollectionId) -> Vec<T::AccountId> {925		<IsAdmin<T>>::iter_prefix((collection,))926			.map(|(a, _)| a)927			.collect()928	}929	pub fn allowlist(collection: CollectionId) -> Vec<T::AccountId> {930		<Allowlist<T>>::iter_prefix((collection,))931			.map(|(a, _)| a)932			.collect()933	}934}