git.delta.rocks / unique-network / refs/commits / 54cf3dbad6fe

difftreelog

source

pallets/unique/src/lib.rs35.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, decl_event,20	dispatch::DispatchResult,21	ensure, fail, parameter_types,22	traits::{23		ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,24		IsSubType, WithdrawReasons,25	},26	weights::{27		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29		WeightToFeePolynomial, DispatchClass,30	},31	StorageValue, transactional,32	pallet_prelude::DispatchResultWithPostInfo,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use up_data_structs::{38	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,39	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,40	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,41	NFT_SPONSOR_TRANSFER_TIMEOUT, AccessMode, Collection, CreateItemData, CollectionLimits,42	CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,43};44use pallet_common::{45	account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,46	CommonWeightInfo,47};48use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};49use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};50use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};5152#[cfg(test)]53mod mock;5455#[cfg(test)]56mod tests;5758mod eth;59mod sponsorship;60pub use sponsorship::UniqueSponsorshipHandler;61pub use eth::sponsoring::UniqueEthSponsorshipHandler;6263pub use eth::UniqueErcSupport;6465pub mod common;66use common::CommonWeights;67pub mod dispatch;68use dispatch::dispatch_call;6970#[cfg(feature = "runtime-benchmarks")]71mod benchmarking;72pub mod weights;73use weights::WeightInfo;7475decl_error! {76	/// Error for non-fungible-token module.77	pub enum Error for Module<T: Config> {78		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.79		CollectionDecimalPointLimitExceeded,80		/// This address is not set as sponsor, use setCollectionSponsor first.81		ConfirmUnsetSponsorFail,82		/// Length of items properties must be greater than 0.83		EmptyArgument,84		/// Collection limit bounds per collection exceeded85		CollectionLimitBoundsExceeded,86		/// Tried to enable permissions which are only permitted to be disabled87		OwnerPermissionsCantBeReverted,88	}89}9091pub trait Config:92	system::Config93	+ pallet_evm_coder_substrate::Config94	+ pallet_common::Config95	+ pallet_nonfungible::Config96	+ pallet_refungible::Config97	+ pallet_fungible::Config98	+ Sized99	+ TypeInfo100{101	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;102103	/// Weight information for extrinsics in this pallet.104	type WeightInfo: WeightInfo;105}106107decl_event! {108	pub enum Event<T>109	where110		<T as frame_system::Config>::AccountId,111		<T as pallet_common::Config>::CrossAccountId,112	{113		/// Collection sponsor was removed114		///115		/// # Arguments116		///117		/// * collection_id: Globally unique collection identifier.118		CollectionSponsorRemoved(CollectionId),119120		/// Collection admin was added121		///122		/// # Arguments123		///124		/// * collection_id: Globally unique collection identifier.125		///126		/// * admin:  Admin address.127		CollectionAdminAdded(CollectionId, CrossAccountId),128129		/// Collection owned was change130		///131		/// # Arguments132		///133		/// * collection_id: Globally unique collection identifier.134		///135		/// * owner:  New owner address.136		CollectionOwnedChanged(CollectionId, AccountId),137138		/// Collection sponsor was set139		///140		/// # Arguments141		///142		/// * collection_id: Globally unique collection identifier.143		///144		/// * owner:  New sponsor address.145		CollectionSponsorSet(CollectionId, AccountId),146147		/// const on chain schema was set148		///149		/// # Arguments150		///151		/// * collection_id: Globally unique collection identifier.152		ConstOnChainSchemaSet(CollectionId),153154		/// New sponsor was confirm155		///156		/// # Arguments157		///158		/// * collection_id: Globally unique collection identifier.159		///160		/// * sponsor:  New sponsor address.161		SponsorshipConfirmed(CollectionId, AccountId),162163		/// Collection admin was removed164		///165		/// # Arguments166		///167		/// * collection_id: Globally unique collection identifier.168		///169		/// * admin:  Admin address.170		CollectionAdminRemoved(CollectionId, CrossAccountId),171172		/// Address was remove from allow list173		///174		/// # Arguments175		///176		/// * collection_id: Globally unique collection identifier.177		///178		/// * user:  Address.179		AllowListAddressRemoved(CollectionId, CrossAccountId),180181		/// Address was add to allow list182		///183		/// # Arguments184		///185		/// * collection_id: Globally unique collection identifier.186		///187		/// * user:  Address.188		AllowListAddressAdded(CollectionId, CrossAccountId),189190		/// Collection limits was set191		///192		/// # Arguments193		///194		/// * collection_id: Globally unique collection identifier.195		CollectionLimitSet(CollectionId),196197		/// Mint permission	was set198		///199		/// # Arguments200		///201		/// * collection_id: Globally unique collection identifier.202		MintPermissionSet(CollectionId),203204		/// Offchain schema was set205		///206		/// # Arguments207		///208		/// * collection_id: Globally unique collection identifier.209		OffchainSchemaSet(CollectionId),210211		/// Public access mode was set212		///213		/// # Arguments214		///215		/// * collection_id: Globally unique collection identifier.216		///217		/// * mode: New access state.218		PublicAccessModeSet(CollectionId, AccessMode),219220		/// Schema version was set221		///222		/// # Arguments223		///224		/// * collection_id: Globally unique collection identifier.225		SchemaVersionSet(CollectionId),226227		/// Variable on chain schema was set228		///229		/// # Arguments230		///231		/// * collection_id: Globally unique collection identifier.232		VariableOnChainSchemaSet(CollectionId),233	}234}235236type SelfWeightOf<T> = <T as Config>::WeightInfo;237238// # Used definitions239//240// ## User control levels241//242// chain-controlled - key is uncontrolled by user243//                    i.e autoincrementing index244//                    can use non-cryptographic hash245// real - key is controlled by user246//        but it is hard to generate enough colliding values, i.e owner of signed txs247//        can use non-cryptographic hash248// controlled - key is completly controlled by users249//              i.e maps with mutable keys250//              should use cryptographic hash251//252// ## User control level downgrade reasons253//254// ?1 - chain-controlled -> controlled255//      collections/tokens can be destroyed, resulting in massive holes256// ?2 - chain-controlled -> controlled257//      same as ?1, but can be only added, resulting in easier exploitation258// ?3 - real -> controlled259//      no confirmation required, so addresses can be easily generated260decl_storage! {261	trait Store for Module<T: Config> as Unique {262263		//#region Private members264		/// Used for migrations265		ChainVersion: u64;266		//#endregion267268		//#region Tokens transfer rate limit baskets269		/// (Collection id (controlled?2), who created (real))270		/// TODO: Off chain worker should remove from this map when collection gets removed271		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;272		/// Collection id (controlled?2), token id (controlled?2)273		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;274		/// Collection id (controlled?2), owning user (real)275		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;276		/// Collection id (controlled?2), token id (controlled?2)277		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;278		//#endregion279280		/// Variable metadata sponsoring281		/// Collection id (controlled?2), token id (controlled?2)282		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;283		/// Approval sponsoring284		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;285		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;286		pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;287	}288}289290decl_module! {291	pub struct Module<T: Config> for enum Call292	where293		origin: T::Origin294	{295		type Error = Error<T>;296297		fn deposit_event() = default;298299		fn on_initialize(_now: T::BlockNumber) -> Weight {300			0301		}302303		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.304		///305		/// # Permissions306		///307		/// * Anyone.308		///309		/// # Arguments310		///311		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.312		///313		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.314		///315		/// * token_prefix: UTF-8 string with token prefix.316		///317		/// * mode: [CollectionMode] collection type and type dependent data.318		// returns collection ID319		#[weight = <SelfWeightOf<T>>::create_collection()]320		#[transactional]321		pub fn create_collection(origin,322								 collection_name: Vec<u16>,323								 collection_description: Vec<u16>,324								 token_prefix: Vec<u8>,325								 mode: CollectionMode) -> DispatchResult {326327			// Anyone can create a collection328			let who = ensure_signed(origin)?;329330			// Create new collection331			let new_collection = Collection {332				owner: who,333				name: collection_name,334				mode: mode.clone(),335				mint_mode: false,336				access: AccessMode::Normal,337				description: collection_description,338				token_prefix,339				offchain_schema: Vec::new(),340				schema_version: SchemaVersion::ImageURL,341				sponsorship: SponsorshipState::Disabled,342				variable_on_chain_schema: Vec::new(),343				const_on_chain_schema: Vec::new(),344				limits: Default::default(),345				meta_update_permission: Default::default(),346			};347348			let _id = match mode {349				CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(new_collection)?},350				CollectionMode::Fungible(decimal_points) => {351					// check params352					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);353					<PalletFungible<T>>::init_collection(new_collection)?354				}355				CollectionMode::ReFungible => {356					<PalletRefungible<T>>::init_collection(new_collection)?357				}358			};359360			Ok(())361		}362363		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.364		///365		/// # Permissions366		///367		/// * Collection Owner.368		///369		/// # Arguments370		///371		/// * collection_id: collection to destroy.372		#[weight = <SelfWeightOf<T>>::destroy_collection()]373		#[transactional]374		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {375			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);376377			let collection = <CollectionHandle<T>>::try_get(collection_id)?;378			collection.check_is_owner(&sender)?;379380			// =========381382			match collection.mode {383				CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,384				CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,385				CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,386			}387388			<NftTransferBasket<T>>::remove_prefix(collection_id, None);389			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);390			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);391392			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);393			<NftApproveBasket<T>>::remove_prefix(collection_id, None);394			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);395			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);396397			Ok(())398		}399400		/// Add an address to allow list.401		///402		/// # Permissions403		///404		/// * Collection Owner405		/// * Collection Admin406		///407		/// # Arguments408		///409		/// * collection_id.410		///411		/// * address.412		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]413		#[transactional]414		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{415416			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);417			let collection = <CollectionHandle<T>>::try_get(collection_id)?;418419			<PalletCommon<T>>::toggle_allowlist(420				&collection,421				&sender,422				&address,423				true,424			)?;425426			Self::deposit_event(Event::<T>::AllowListAddressAdded(427				collection_id,428				address429			));430431			Ok(())432		}433434		/// Remove an address from allow list.435		///436		/// # Permissions437		///438		/// * Collection Owner439		/// * Collection Admin440		///441		/// # Arguments442		///443		/// * collection_id.444		///445		/// * address.446		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]447		#[transactional]448		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{449450			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);451			let collection = <CollectionHandle<T>>::try_get(collection_id)?;452453			<PalletCommon<T>>::toggle_allowlist(454				&collection,455				&sender,456				&address,457				false,458			)?;459460			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(461				collection_id,462				address463			));464465			Ok(())466		}467468		/// Toggle between normal and allow list access for the methods with access for `Anyone`.469		///470		/// # Permissions471		///472		/// * Collection Owner.473		///474		/// # Arguments475		///476		/// * collection_id.477		///478		/// * mode: [AccessMode]479		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]480		#[transactional]481		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult482		{483			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);484485			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;486			target_collection.check_is_owner(&sender)?;487488			target_collection.access = mode.clone();489490			<Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(491				collection_id,492				mode493			));494495			target_collection.save()496		}497498		/// Allows Anyone to create tokens if:499		/// * Allow List is enabled, and500		/// * Address is added to allow list, and501		/// * This method was called with True parameter502		///503		/// # Permissions504		/// * Collection Owner505		///506		/// # Arguments507		///508		/// * collection_id.509		///510		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.511		#[weight = <SelfWeightOf<T>>::set_mint_permission()]512		#[transactional]513		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult514		{515			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);516517			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;518			target_collection.check_is_owner(&sender)?;519520			target_collection.mint_mode = mint_permission;521522			<Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(523				collection_id524			));525526			target_collection.save()527		}528529		/// Change the owner of the collection.530		///531		/// # Permissions532		///533		/// * Collection Owner.534		///535		/// # Arguments536		///537		/// * collection_id.538		///539		/// * new_owner.540		#[weight = <SelfWeightOf<T>>::change_collection_owner()]541		#[transactional]542		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {543544			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);545546			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;547			target_collection.check_is_owner(&sender)?;548549			target_collection.owner = new_owner.clone();550			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(551				collection_id,552				new_owner553			));554555			target_collection.save()556		}557558		/// Adds an admin of the Collection.559		/// 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.560		///561		/// # Permissions562		///563		/// * Collection Owner.564		/// * Collection Admin.565		///566		/// # Arguments567		///568		/// * collection_id: ID of the Collection to add admin for.569		///570		/// * new_admin_id: Address of new admin to add.571		#[weight = <SelfWeightOf<T>>::add_collection_admin()]572		#[transactional]573		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {574			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);575			let collection = <CollectionHandle<T>>::try_get(collection_id)?;576577			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(578				collection_id,579				new_admin_id.clone()580			));581582			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)583		}584585		/// 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.586		///587		/// # Permissions588		///589		/// * Collection Owner.590		/// * Collection Admin.591		///592		/// # Arguments593		///594		/// * collection_id: ID of the Collection to remove admin for.595		///596		/// * account_id: Address of admin to remove.597		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]598		#[transactional]599		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {600			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);601			let collection = <CollectionHandle<T>>::try_get(collection_id)?;602603			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(604				collection_id,605				account_id.clone()606			));607608			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)609		}610611		/// # Permissions612		///613		/// * Collection Owner614		///615		/// # Arguments616		///617		/// * collection_id.618		///619		/// * new_sponsor.620		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]621		#[transactional]622		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {623			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);624625			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;626			target_collection.check_is_owner(&sender)?;627628			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());629630			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(631				collection_id,632				new_sponsor633			));634635			target_collection.save()636		}637638		/// # Permissions639		///640		/// * Sponsor.641		///642		/// # Arguments643		///644		/// * collection_id.645		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]646		#[transactional]647		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {648			let sender = ensure_signed(origin)?;649650			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;651			ensure!(652				target_collection.sponsorship.pending_sponsor() == Some(&sender),653				Error::<T>::ConfirmUnsetSponsorFail654			);655656			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());657658			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(659				collection_id,660				sender661			));662663			target_collection.save()664		}665666		/// Switch back to pay-per-own-transaction model.667		///668		/// # Permissions669		///670		/// * Collection owner.671		///672		/// # Arguments673		///674		/// * collection_id.675		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]676		#[transactional]677		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {678			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);679680			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;681			target_collection.check_is_owner(&sender)?;682683			target_collection.sponsorship = SponsorshipState::Disabled;684685			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(686				collection_id687			));688			target_collection.save()689		}690691		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.692		///693		/// # Permissions694		///695		/// * Collection Owner.696		/// * Collection Admin.697		/// * Anyone if698		///     * Allow List is enabled, and699		///     * Address is added to allow list, and700		///     * MintPermission is enabled (see SetMintPermission method)701		///702		/// # Arguments703		///704		/// * collection_id: ID of the collection.705		///706		/// * owner: Address, initial owner of the NFT.707		///708		/// * data: Token data to store on chain.709		#[weight = <CommonWeights<T>>::create_item()]710		#[transactional]711		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {712			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);713714			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))715		}716717		/// This method creates multiple items in a collection created with CreateCollection method.718		///719		/// # Permissions720		///721		/// * Collection Owner.722		/// * Collection Admin.723		/// * Anyone if724		///     * Allow List is enabled, and725		///     * Address is added to allow list, and726		///     * MintPermission is enabled (see SetMintPermission method)727		///728		/// # Arguments729		///730		/// * collection_id: ID of the collection.731		///732		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].733		///734		/// * owner: Address, initial owner of the NFT.735		#[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]736		#[transactional]737		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {738			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);739			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);740741			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))742		}743744		// TODO! transaction weight745746		/// Set transfers_enabled value for particular collection747		///748		/// # Permissions749		///750		/// * Collection Owner.751		///752		/// # Arguments753		///754		/// * collection_id: ID of the collection.755		///756		/// * value: New flag value.757		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]758		#[transactional]759		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {760			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);761			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;762			target_collection.check_is_owner(&sender)?;763764			// =========765766			target_collection.limits.transfers_enabled = Some(value);767			target_collection.save()768		}769770		/// Destroys a concrete instance of NFT.771		///772		/// # Permissions773		///774		/// * Collection Owner.775		/// * Collection Admin.776		/// * Current NFT Owner.777		///778		/// # Arguments779		///780		/// * collection_id: ID of the collection.781		///782		/// * item_id: ID of NFT to burn.783		#[weight = <CommonWeights<T>>::burn_item()]784		#[transactional]785		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {786			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);787788			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;789			if value == 1 {790				<NftTransferBasket<T>>::remove(collection_id, item_id);791				<NftApproveBasket<T>>::remove(collection_id, item_id);792			}793			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?794			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());795			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));796			Ok(post_info)797		}798799		/// Destroys a concrete instance of NFT on behalf of the owner800		/// See also: [`approve`]801		///802		/// # Permissions803		///804		/// * Collection Owner.805		/// * Collection Admin.806		/// * Current NFT Owner.807		///808		/// # Arguments809		///810		/// * collection_id: ID of the collection.811		///812		/// * item_id: ID of NFT to burn.813		///814		/// * from: owner of item815		#[weight = <CommonWeights<T>>::burn_from()]816		#[transactional]817		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {818			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);819820			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))821		}822823		/// Change ownership of the token.824		///825		/// # Permissions826		///827		/// * Collection Owner828		/// * Collection Admin829		/// * Current NFT owner830		///831		/// # Arguments832		///833		/// * recipient: Address of token recipient.834		///835		/// * collection_id.836		///837		/// * item_id: ID of the item838		///     * Non-Fungible Mode: Required.839		///     * Fungible Mode: Ignored.840		///     * Re-Fungible Mode: Required.841		///842		/// * value: Amount to transfer.843		///     * Non-Fungible Mode: Ignored844		///     * Fungible Mode: Must specify transferred amount845		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)846		#[weight = <CommonWeights<T>>::transfer()]847		#[transactional]848		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {849			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);850851			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))852		}853854		/// Set, change, or remove approved address to transfer the ownership of the NFT.855		///856		/// # Permissions857		///858		/// * Collection Owner859		/// * Collection Admin860		/// * Current NFT owner861		///862		/// # Arguments863		///864		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).865		///866		/// * collection_id.867		///868		/// * item_id: ID of the item.869		#[weight = <CommonWeights<T>>::approve()]870		#[transactional]871		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {872			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);873874			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))875		}876877		/// 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.878		///879		/// # Permissions880		/// * Collection Owner881		/// * Collection Admin882		/// * Current NFT owner883		/// * Address approved by current NFT owner884		///885		/// # Arguments886		///887		/// * from: Address that owns token.888		///889		/// * recipient: Address of token recipient.890		///891		/// * collection_id.892		///893		/// * item_id: ID of the item.894		///895		/// * value: Amount to transfer.896		#[weight = <CommonWeights<T>>::transfer_from()]897		#[transactional]898		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {899			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);900901			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))902		}903904		/// Set off-chain data schema.905		///906		/// # Permissions907		///908		/// * Collection Owner909		/// * Collection Admin910		///911		/// # Arguments912		///913		/// * collection_id.914		///915		/// * schema: String representing the offchain data schema.916		#[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]917		#[transactional]918		pub fn set_variable_meta_data (919			origin,920			collection_id: CollectionId,921			item_id: TokenId,922			data: Vec<u8>923		) -> DispatchResultWithPostInfo {924			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);925926			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))927		}928929		/// Set meta_update_permission value for particular collection930		///931		/// # Permissions932		///933		/// * Collection Owner.934		///935		/// # Arguments936		///937		/// * collection_id: ID of the collection.938		///939		/// * value: New flag value.940		#[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]941		#[transactional]942		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {943			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);944			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;945946			ensure!(947				target_collection.meta_update_permission != MetaUpdatePermission::None,948				<CommonError<T>>::MetadataFlagFrozen,949			);950			target_collection.check_is_owner(&sender)?;951952			target_collection.meta_update_permission = value;953954			target_collection.save()955		}956957		/// Set schema standard958		/// ImageURL959		/// Unique960		///961		/// # Permissions962		///963		/// * Collection Owner964		/// * Collection Admin965		///966		/// # Arguments967		///968		/// * collection_id.969		///970		/// * schema: SchemaVersion: enum971		#[weight = <SelfWeightOf<T>>::set_schema_version()]972		#[transactional]973		pub fn set_schema_version(974			origin,975			collection_id: CollectionId,976			version: SchemaVersion977		) -> DispatchResult {978			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);979			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;980			target_collection.check_is_owner_or_admin(&sender)?;981			target_collection.schema_version = version;982983			<Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(984				collection_id985			));986987			target_collection.save()988		}989990		/// Set off-chain data schema.991		///992		/// # Permissions993		///994		/// * Collection Owner995		/// * Collection Admin996		///997		/// # Arguments998		///999		/// * collection_id.1000		///1001		/// * schema: String representing the offchain data schema.1002		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1003		#[transactional]1004		pub fn set_offchain_schema(1005			origin,1006			collection_id: CollectionId,1007			schema: Vec<u8>1008		) -> DispatchResult {1009			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1010			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1011			target_collection.check_is_owner_or_admin(&sender)?;10121013			// check schema limit1014			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");10151016			target_collection.offchain_schema = schema;10171018			<Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1019				collection_id1020			));10211022			target_collection.save()1023		}10241025		/// Set const on-chain data schema.1026		///1027		/// # Permissions1028		///1029		/// * Collection Owner1030		/// * Collection Admin1031		///1032		/// # Arguments1033		///1034		/// * collection_id.1035		///1036		/// * schema: String representing the const on-chain data schema.1037		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1038		#[transactional]1039		pub fn set_const_on_chain_schema (1040			origin,1041			collection_id: CollectionId,1042			schema: Vec<u8>1043		) -> DispatchResult {1044			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1045			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1046			target_collection.check_is_owner_or_admin(&sender)?;10471048			// check schema limit1049			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");10501051			target_collection.const_on_chain_schema = schema;10521053			<Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1054				collection_id1055			));10561057			target_collection.save()1058		}10591060		/// Set variable on-chain data schema.1061		///1062		/// # Permissions1063		///1064		/// * Collection Owner1065		/// * Collection Admin1066		///1067		/// # Arguments1068		///1069		/// * collection_id.1070		///1071		/// * schema: String representing the variable on-chain data schema.1072		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1073		#[transactional]1074		pub fn set_variable_on_chain_schema (1075			origin,1076			collection_id: CollectionId,1077			schema: Vec<u8>1078		) -> DispatchResult {1079			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1080			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1081			target_collection.check_is_owner_or_admin(&sender)?;10821083			// check schema limit1084			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");10851086			target_collection.variable_on_chain_schema = schema;10871088			<Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1089				collection_id1090			));10911092			target_collection.save()1093		}10941095		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1096		#[transactional]1097		pub fn set_collection_limits(1098			origin,1099			collection_id: CollectionId,1100			new_limit: CollectionLimits,1101		) -> DispatchResult {1102			let mut new_limit = new_limit;1103			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1104			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1105			target_collection.check_is_owner(&sender)?;1106			let old_limit = &target_collection.limits;11071108			macro_rules! limit_default {1109				($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1110					$(1111						if let Some($new) = $new.$field {1112							let $old = $old.$field($($arg)?);1113							let _ = $new;1114							let _ = $old;1115							$check1116						} else {1117							$new.$field = $old.$field1118						}1119					)*1120				}};1121			}11221123			limit_default!(old_limit, new_limit,1124				account_token_ownership_limit => ensure!(1125					new_limit <= MAX_TOKEN_OWNERSHIP,1126					<Error<T>>::CollectionLimitBoundsExceeded,1127				),1128				sponsor_transfer_timeout(match target_collection.mode {1129					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1130					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1131					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1132				}) => ensure!(1133					new_limit <= MAX_SPONSOR_TIMEOUT,1134					<Error<T>>::CollectionLimitBoundsExceeded,1135				),1136				sponsored_data_size => ensure!(1137					new_limit <= CUSTOM_DATA_LIMIT,1138					<Error<T>>::CollectionLimitBoundsExceeded,1139				),1140				token_limit => ensure!(1141					old_limit >= new_limit && new_limit > 0,1142					<CommonError<T>>::CollectionTokenLimitExceeded1143				),1144				owner_can_transfer => ensure!(1145					old_limit || !new_limit,1146					<Error<T>>::OwnerPermissionsCantBeReverted,1147				),1148				owner_can_destroy => ensure!(1149					old_limit || !new_limit,1150					<Error<T>>::OwnerPermissionsCantBeReverted,1151				),1152				sponsored_data_rate_limit => {},1153				transfers_enabled => {},1154			);11551156			target_collection.limits = new_limit;11571158			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1159				collection_id1160			));11611162			target_collection.save()1163		}1164	}1165}