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

difftreelog

source

pallets/unique/src/lib.rs33.2 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, VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT,39	OFFCHAIN_SCHEMA_LIMIT, AccessMode, CreateItemData, CollectionLimits, CollectionId,40	CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,41	CreateCollectionData,42};43use pallet_common::{44	account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,45	CommonWeightInfo,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::UniqueSponsorshipHandler;60pub use eth::sponsoring::UniqueEthSponsorshipHandler;6162pub use eth::UniqueErcSupport;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	}84}8586pub trait Config:87	system::Config88	+ pallet_evm_coder_substrate::Config89	+ pallet_common::Config90	+ pallet_nonfungible::Config91	+ pallet_refungible::Config92	+ pallet_fungible::Config93	+ Sized94	+ TypeInfo95{96	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;9798	/// Weight information for extrinsics in this pallet.99	type WeightInfo: WeightInfo;100}101102decl_event! {103	pub enum Event<T>104	where105		<T as frame_system::Config>::AccountId,106		<T as pallet_common::Config>::CrossAccountId,107	{108		/// Collection sponsor was removed109		///110		/// # Arguments111		///112		/// * collection_id: Globally unique collection identifier.113		CollectionSponsorRemoved(CollectionId),114115		/// Collection admin was added116		///117		/// # Arguments118		///119		/// * collection_id: Globally unique collection identifier.120		///121		/// * admin:  Admin address.122		CollectionAdminAdded(CollectionId, CrossAccountId),123124		/// Collection owned was change125		///126		/// # Arguments127		///128		/// * collection_id: Globally unique collection identifier.129		///130		/// * owner:  New owner address.131		CollectionOwnedChanged(CollectionId, AccountId),132133		/// Collection sponsor was set134		///135		/// # Arguments136		///137		/// * collection_id: Globally unique collection identifier.138		///139		/// * owner:  New sponsor address.140		CollectionSponsorSet(CollectionId, AccountId),141142		/// const on chain schema was set143		///144		/// # Arguments145		///146		/// * collection_id: Globally unique collection identifier.147		ConstOnChainSchemaSet(CollectionId),148149		/// New sponsor was confirm150		///151		/// # Arguments152		///153		/// * collection_id: Globally unique collection identifier.154		///155		/// * sponsor:  New sponsor address.156		SponsorshipConfirmed(CollectionId, AccountId),157158		/// Collection admin was removed159		///160		/// # Arguments161		///162		/// * collection_id: Globally unique collection identifier.163		///164		/// * admin:  Admin address.165		CollectionAdminRemoved(CollectionId, CrossAccountId),166167		/// Address was remove from allow list168		///169		/// # Arguments170		///171		/// * collection_id: Globally unique collection identifier.172		///173		/// * user:  Address.174		AllowListAddressRemoved(CollectionId, CrossAccountId),175176		/// Address was add to allow list177		///178		/// # Arguments179		///180		/// * collection_id: Globally unique collection identifier.181		///182		/// * user:  Address.183		AllowListAddressAdded(CollectionId, CrossAccountId),184185		/// Collection limits was set186		///187		/// # Arguments188		///189		/// * collection_id: Globally unique collection identifier.190		CollectionLimitSet(CollectionId),191192		/// Mint permission	was set193		///194		/// # Arguments195		///196		/// * collection_id: Globally unique collection identifier.197		MintPermissionSet(CollectionId),198199		/// Offchain schema was set200		///201		/// # Arguments202		///203		/// * collection_id: Globally unique collection identifier.204		OffchainSchemaSet(CollectionId),205206		/// Public access mode was set207		///208		/// # Arguments209		///210		/// * collection_id: Globally unique collection identifier.211		///212		/// * mode: New access state.213		PublicAccessModeSet(CollectionId, AccessMode),214215		/// Schema version was set216		///217		/// # Arguments218		///219		/// * collection_id: Globally unique collection identifier.220		SchemaVersionSet(CollectionId),221222		/// Variable on chain schema was set223		///224		/// # Arguments225		///226		/// * collection_id: Globally unique collection identifier.227		VariableOnChainSchemaSet(CollectionId),228	}229}230231type SelfWeightOf<T> = <T as Config>::WeightInfo;232233// # Used definitions234//235// ## User control levels236//237// chain-controlled - key is uncontrolled by user238//                    i.e autoincrementing index239//                    can use non-cryptographic hash240// real - key is controlled by user241//        but it is hard to generate enough colliding values, i.e owner of signed txs242//        can use non-cryptographic hash243// controlled - key is completly controlled by users244//              i.e maps with mutable keys245//              should use cryptographic hash246//247// ## User control level downgrade reasons248//249// ?1 - chain-controlled -> controlled250//      collections/tokens can be destroyed, resulting in massive holes251// ?2 - chain-controlled -> controlled252//      same as ?1, but can be only added, resulting in easier exploitation253// ?3 - real -> controlled254//      no confirmation required, so addresses can be easily generated255decl_storage! {256	trait Store for Module<T: Config> as Unique {257258		//#region Private members259		/// Used for migrations260		ChainVersion: u64;261		//#endregion262263		//#region Tokens transfer rate limit baskets264		/// (Collection id (controlled?2), who created (real))265		/// TODO: Off chain worker should remove from this map when collection gets removed266		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;267		/// Collection id (controlled?2), token id (controlled?2)268		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;269		/// Collection id (controlled?2), owning user (real)270		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;271		/// Collection id (controlled?2), token id (controlled?2)272		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>;273		//#endregion274275		/// Variable metadata sponsoring276		/// Collection id (controlled?2), token id (controlled?2)277		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;278		/// Approval sponsoring279		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;280		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;281		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>;282	}283}284285decl_module! {286	pub struct Module<T: Config> for enum Call287	where288		origin: T::Origin289	{290		type Error = Error<T>;291292		fn deposit_event() = default;293294		fn on_initialize(_now: T::BlockNumber) -> Weight {295			0296		}297298		/// 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.299		///300		/// # Permissions301		///302		/// * Anyone.303		///304		/// # Arguments305		///306		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.307		///308		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.309		///310		/// * token_prefix: UTF-8 string with token prefix.311		///312		/// * mode: [CollectionMode] collection type and type dependent data.313		// returns collection ID314		#[weight = <SelfWeightOf<T>>::create_collection()]315		#[transactional]316		#[deprecated]317		pub fn create_collection(origin,318								 collection_name: Vec<u16>,319								 collection_description: Vec<u16>,320								 token_prefix: Vec<u8>,321								 mode: CollectionMode) -> DispatchResult {322			Self::create_collection_ex(origin, CreateCollectionData {323				name: collection_name,324				description: collection_description,325				token_prefix,326				mode,327				..Default::default()328			})329		}330331		/// This method creates a collection332		///333		/// Prefer it to deprecated [`created_collection`] method334		#[weight = <SelfWeightOf<T>>::create_collection()]335		#[transactional]336		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {337			let owner = ensure_signed(origin)?;338339			let _id = match data.mode {340				CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(owner, data)?},341				CollectionMode::Fungible(decimal_points) => {342					// check params343					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);344					<PalletFungible<T>>::init_collection(owner, data)?345				}346				CollectionMode::ReFungible => {347					<PalletRefungible<T>>::init_collection(owner, data)?348				}349			};350351			Ok(())352		}353354		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.355		///356		/// # Permissions357		///358		/// * Collection Owner.359		///360		/// # Arguments361		///362		/// * collection_id: collection to destroy.363		#[weight = <SelfWeightOf<T>>::destroy_collection()]364		#[transactional]365		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {366			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);367368			let collection = <CollectionHandle<T>>::try_get(collection_id)?;369			collection.check_is_owner(&sender)?;370371			// =========372373			match collection.mode {374				CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,375				CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,376				CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,377			}378379			<NftTransferBasket<T>>::remove_prefix(collection_id, None);380			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);381			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);382383			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);384			<NftApproveBasket<T>>::remove_prefix(collection_id, None);385			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);386			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);387388			Ok(())389		}390391		/// Add an address to allow list.392		///393		/// # Permissions394		///395		/// * Collection Owner396		/// * Collection Admin397		///398		/// # Arguments399		///400		/// * collection_id.401		///402		/// * address.403		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]404		#[transactional]405		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{406407			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);408			let collection = <CollectionHandle<T>>::try_get(collection_id)?;409410			<PalletCommon<T>>::toggle_allowlist(411				&collection,412				&sender,413				&address,414				true,415			)?;416417			Self::deposit_event(Event::<T>::AllowListAddressAdded(418				collection_id,419				address420			));421422			Ok(())423		}424425		/// Remove an address from allow list.426		///427		/// # Permissions428		///429		/// * Collection Owner430		/// * Collection Admin431		///432		/// # Arguments433		///434		/// * collection_id.435		///436		/// * address.437		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]438		#[transactional]439		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{440441			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);442			let collection = <CollectionHandle<T>>::try_get(collection_id)?;443444			<PalletCommon<T>>::toggle_allowlist(445				&collection,446				&sender,447				&address,448				false,449			)?;450451			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(452				collection_id,453				address454			));455456			Ok(())457		}458459		/// Toggle between normal and allow list access for the methods with access for `Anyone`.460		///461		/// # Permissions462		///463		/// * Collection Owner.464		///465		/// # Arguments466		///467		/// * collection_id.468		///469		/// * mode: [AccessMode]470		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]471		#[transactional]472		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult473		{474			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);475476			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;477			target_collection.check_is_owner(&sender)?;478479			target_collection.access = mode.clone();480481			<Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(482				collection_id,483				mode484			));485486			target_collection.save()487		}488489		/// Allows Anyone to create tokens if:490		/// * Allow List is enabled, and491		/// * Address is added to allow list, and492		/// * This method was called with True parameter493		///494		/// # Permissions495		/// * Collection Owner496		///497		/// # Arguments498		///499		/// * collection_id.500		///501		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.502		#[weight = <SelfWeightOf<T>>::set_mint_permission()]503		#[transactional]504		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult505		{506			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);507508			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;509			target_collection.check_is_owner(&sender)?;510511			target_collection.mint_mode = mint_permission;512513			<Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(514				collection_id515			));516517			target_collection.save()518		}519520		/// Change the owner of the collection.521		///522		/// # Permissions523		///524		/// * Collection Owner.525		///526		/// # Arguments527		///528		/// * collection_id.529		///530		/// * new_owner.531		#[weight = <SelfWeightOf<T>>::change_collection_owner()]532		#[transactional]533		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {534535			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);536537			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;538			target_collection.check_is_owner(&sender)?;539540			target_collection.owner = new_owner.clone();541			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(542				collection_id,543				new_owner544			));545546			target_collection.save()547		}548549		/// Adds an admin of the Collection.550		/// 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.551		///552		/// # Permissions553		///554		/// * Collection Owner.555		/// * Collection Admin.556		///557		/// # Arguments558		///559		/// * collection_id: ID of the Collection to add admin for.560		///561		/// * new_admin_id: Address of new admin to add.562		#[weight = <SelfWeightOf<T>>::add_collection_admin()]563		#[transactional]564		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {565			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);566			let collection = <CollectionHandle<T>>::try_get(collection_id)?;567568			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(569				collection_id,570				new_admin_id.clone()571			));572573			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)574		}575576		/// 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.577		///578		/// # Permissions579		///580		/// * Collection Owner.581		/// * Collection Admin.582		///583		/// # Arguments584		///585		/// * collection_id: ID of the Collection to remove admin for.586		///587		/// * account_id: Address of admin to remove.588		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]589		#[transactional]590		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {591			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);592			let collection = <CollectionHandle<T>>::try_get(collection_id)?;593594			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(595				collection_id,596				account_id.clone()597			));598599			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)600		}601602		/// # Permissions603		///604		/// * Collection Owner605		///606		/// # Arguments607		///608		/// * collection_id.609		///610		/// * new_sponsor.611		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]612		#[transactional]613		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {614			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);615616			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;617			target_collection.check_is_owner(&sender)?;618619			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());620621			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(622				collection_id,623				new_sponsor624			));625626			target_collection.save()627		}628629		/// # Permissions630		///631		/// * Sponsor.632		///633		/// # Arguments634		///635		/// * collection_id.636		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]637		#[transactional]638		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {639			let sender = ensure_signed(origin)?;640641			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;642			ensure!(643				target_collection.sponsorship.pending_sponsor() == Some(&sender),644				Error::<T>::ConfirmUnsetSponsorFail645			);646647			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());648649			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(650				collection_id,651				sender652			));653654			target_collection.save()655		}656657		/// Switch back to pay-per-own-transaction model.658		///659		/// # Permissions660		///661		/// * Collection owner.662		///663		/// # Arguments664		///665		/// * collection_id.666		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]667		#[transactional]668		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {669			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);670671			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;672			target_collection.check_is_owner(&sender)?;673674			target_collection.sponsorship = SponsorshipState::Disabled;675676			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(677				collection_id678			));679			target_collection.save()680		}681682		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.683		///684		/// # Permissions685		///686		/// * Collection Owner.687		/// * Collection Admin.688		/// * Anyone if689		///     * Allow List is enabled, and690		///     * Address is added to allow list, and691		///     * MintPermission is enabled (see SetMintPermission method)692		///693		/// # Arguments694		///695		/// * collection_id: ID of the collection.696		///697		/// * owner: Address, initial owner of the NFT.698		///699		/// * data: Token data to store on chain.700		#[weight = <CommonWeights<T>>::create_item()]701		#[transactional]702		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {703			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);704705			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))706		}707708		/// This method creates multiple items in a collection created with CreateCollection method.709		///710		/// # Permissions711		///712		/// * Collection Owner.713		/// * Collection Admin.714		/// * Anyone if715		///     * Allow List is enabled, and716		///     * Address is added to allow list, and717		///     * MintPermission is enabled (see SetMintPermission method)718		///719		/// # Arguments720		///721		/// * collection_id: ID of the collection.722		///723		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].724		///725		/// * owner: Address, initial owner of the NFT.726		#[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]727		#[transactional]728		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {729			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);730			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);731732			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))733		}734735		// TODO! transaction weight736737		/// Set transfers_enabled value for particular collection738		///739		/// # Permissions740		///741		/// * Collection Owner.742		///743		/// # Arguments744		///745		/// * collection_id: ID of the collection.746		///747		/// * value: New flag value.748		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]749		#[transactional]750		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {751			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);752			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;753			target_collection.check_is_owner(&sender)?;754755			// =========756757			target_collection.limits.transfers_enabled = Some(value);758			target_collection.save()759		}760761		/// Destroys a concrete instance of NFT.762		///763		/// # Permissions764		///765		/// * Collection Owner.766		/// * Collection Admin.767		/// * Current NFT Owner.768		///769		/// # Arguments770		///771		/// * collection_id: ID of the collection.772		///773		/// * item_id: ID of NFT to burn.774		#[weight = <CommonWeights<T>>::burn_item()]775		#[transactional]776		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {777			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);778779			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;780			if value == 1 {781				<NftTransferBasket<T>>::remove(collection_id, item_id);782				<NftApproveBasket<T>>::remove(collection_id, item_id);783			}784			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?785			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());786			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));787			Ok(post_info)788		}789790		/// Destroys a concrete instance of NFT on behalf of the owner791		/// See also: [`approve`]792		///793		/// # Permissions794		///795		/// * Collection Owner.796		/// * Collection Admin.797		/// * Current NFT Owner.798		///799		/// # Arguments800		///801		/// * collection_id: ID of the collection.802		///803		/// * item_id: ID of NFT to burn.804		///805		/// * from: owner of item806		#[weight = <CommonWeights<T>>::burn_from()]807		#[transactional]808		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {809			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);810811			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))812		}813814		/// Change ownership of the token.815		///816		/// # Permissions817		///818		/// * Collection Owner819		/// * Collection Admin820		/// * Current NFT owner821		///822		/// # Arguments823		///824		/// * recipient: Address of token recipient.825		///826		/// * collection_id.827		///828		/// * item_id: ID of the item829		///     * Non-Fungible Mode: Required.830		///     * Fungible Mode: Ignored.831		///     * Re-Fungible Mode: Required.832		///833		/// * value: Amount to transfer.834		///     * Non-Fungible Mode: Ignored835		///     * Fungible Mode: Must specify transferred amount836		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)837		#[weight = <CommonWeights<T>>::transfer()]838		#[transactional]839		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {840			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);841842			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))843		}844845		/// Set, change, or remove approved address to transfer the ownership of the NFT.846		///847		/// # Permissions848		///849		/// * Collection Owner850		/// * Collection Admin851		/// * Current NFT owner852		///853		/// # Arguments854		///855		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).856		///857		/// * collection_id.858		///859		/// * item_id: ID of the item.860		#[weight = <CommonWeights<T>>::approve()]861		#[transactional]862		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {863			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);864865			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))866		}867868		/// 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.869		///870		/// # Permissions871		/// * Collection Owner872		/// * Collection Admin873		/// * Current NFT owner874		/// * Address approved by current NFT owner875		///876		/// # Arguments877		///878		/// * from: Address that owns token.879		///880		/// * recipient: Address of token recipient.881		///882		/// * collection_id.883		///884		/// * item_id: ID of the item.885		///886		/// * value: Amount to transfer.887		#[weight = <CommonWeights<T>>::transfer_from()]888		#[transactional]889		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {890			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);891892			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))893		}894895		/// Set off-chain data schema.896		///897		/// # Permissions898		///899		/// * Collection Owner900		/// * Collection Admin901		///902		/// # Arguments903		///904		/// * collection_id.905		///906		/// * schema: String representing the offchain data schema.907		#[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]908		#[transactional]909		pub fn set_variable_meta_data (910			origin,911			collection_id: CollectionId,912			item_id: TokenId,913			data: Vec<u8>914		) -> DispatchResultWithPostInfo {915			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);916917			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))918		}919920		/// Set meta_update_permission value for particular collection921		///922		/// # Permissions923		///924		/// * Collection Owner.925		///926		/// # Arguments927		///928		/// * collection_id: ID of the collection.929		///930		/// * value: New flag value.931		#[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]932		#[transactional]933		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {934			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);935			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;936937			ensure!(938				target_collection.meta_update_permission != MetaUpdatePermission::None,939				<CommonError<T>>::MetadataFlagFrozen,940			);941			target_collection.check_is_owner(&sender)?;942943			target_collection.meta_update_permission = value;944945			target_collection.save()946		}947948		/// Set schema standard949		/// ImageURL950		/// Unique951		///952		/// # Permissions953		///954		/// * Collection Owner955		/// * Collection Admin956		///957		/// # Arguments958		///959		/// * collection_id.960		///961		/// * schema: SchemaVersion: enum962		#[weight = <SelfWeightOf<T>>::set_schema_version()]963		#[transactional]964		pub fn set_schema_version(965			origin,966			collection_id: CollectionId,967			version: SchemaVersion968		) -> DispatchResult {969			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);970			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;971			target_collection.check_is_owner_or_admin(&sender)?;972			target_collection.schema_version = version;973974			<Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(975				collection_id976			));977978			target_collection.save()979		}980981		/// Set off-chain data schema.982		///983		/// # Permissions984		///985		/// * Collection Owner986		/// * Collection Admin987		///988		/// # Arguments989		///990		/// * collection_id.991		///992		/// * schema: String representing the offchain data schema.993		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]994		#[transactional]995		pub fn set_offchain_schema(996			origin,997			collection_id: CollectionId,998			schema: Vec<u8>999		) -> DispatchResult {1000			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1001			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1002			target_collection.check_is_owner_or_admin(&sender)?;10031004			// check schema limit1005			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");10061007			target_collection.offchain_schema = schema;10081009			<Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1010				collection_id1011			));10121013			target_collection.save()1014		}10151016		/// Set const on-chain data schema.1017		///1018		/// # Permissions1019		///1020		/// * Collection Owner1021		/// * Collection Admin1022		///1023		/// # Arguments1024		///1025		/// * collection_id.1026		///1027		/// * schema: String representing the const on-chain data schema.1028		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1029		#[transactional]1030		pub fn set_const_on_chain_schema (1031			origin,1032			collection_id: CollectionId,1033			schema: Vec<u8>1034		) -> DispatchResult {1035			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1036			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1037			target_collection.check_is_owner_or_admin(&sender)?;10381039			// check schema limit1040			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");10411042			target_collection.const_on_chain_schema = schema;10431044			<Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1045				collection_id1046			));10471048			target_collection.save()1049		}10501051		/// Set variable on-chain data schema.1052		///1053		/// # Permissions1054		///1055		/// * Collection Owner1056		/// * Collection Admin1057		///1058		/// # Arguments1059		///1060		/// * collection_id.1061		///1062		/// * schema: String representing the variable on-chain data schema.1063		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1064		#[transactional]1065		pub fn set_variable_on_chain_schema (1066			origin,1067			collection_id: CollectionId,1068			schema: Vec<u8>1069		) -> DispatchResult {1070			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1071			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1072			target_collection.check_is_owner_or_admin(&sender)?;10731074			// check schema limit1075			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");10761077			target_collection.variable_on_chain_schema = schema;10781079			<Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1080				collection_id1081			));10821083			target_collection.save()1084		}10851086		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1087		#[transactional]1088		pub fn set_collection_limits(1089			origin,1090			collection_id: CollectionId,1091			new_limit: CollectionLimits,1092		) -> DispatchResult {1093			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1094			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1095			target_collection.check_is_owner(&sender)?;1096			let old_limit = &target_collection.limits;10971098			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10991100			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1101				collection_id1102			));11031104			target_collection.save()1105		}1106	}1107}