git.delta.rocks / unique-network / refs/commits / 8cb720a7cf01

difftreelog

fix clear VariableMetaDataBasket

Daniel Shiposha2022-05-16parent: #7676807.patch.diff
in: master

1 file changed

modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
before · pallets/unique/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20	clippy::too_many_arguments,21	clippy::unnecessary_mut_passed,22	clippy::unused_unit23)]2425use frame_support::{26	decl_module, decl_storage, decl_error, decl_event,27	dispatch::DispatchResult,28	ensure,29	weights::{Weight},30	transactional,31	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},32	BoundedVec,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	CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,39	MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,40	AccessMode, CreateItemData, CollectionLimits, CollectionId, CollectionMode, TokenId,41	SchemaVersion, SponsorshipState, CreateCollectionData,42	CreateItemExData, budget, CollectionField, Property, PropertyKey, PropertyKeyPermission,43};44use pallet_evm::account::CrossAccountId;45use pallet_common::{46	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo,47	dispatch::dispatch_call, dispatch::CollectionDispatch,48};4950#[cfg(feature = "runtime-benchmarks")]51mod benchmarking;52pub mod weights;53use weights::WeightInfo;5455decl_error! {56	/// Error for non-fungible-token module.57	pub enum Error for Module<T: Config> {58		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.59		CollectionDecimalPointLimitExceeded,60		/// This address is not set as sponsor, use setCollectionSponsor first.61		ConfirmUnsetSponsorFail,62		/// Length of items properties must be greater than 0.63		EmptyArgument,64	}65}6667pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {68	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;6970	/// Weight information for extrinsics in this pallet.71	type WeightInfo: WeightInfo;72	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;73}7475decl_event! {76	pub enum Event<T>77	where78		<T as frame_system::Config>::AccountId,79		<T as pallet_evm::account::Config>::CrossAccountId,80	{81		/// Collection sponsor was removed82		///83		/// # Arguments84		///85		/// * collection_id: Globally unique collection identifier.86		CollectionSponsorRemoved(CollectionId),8788		/// Collection admin was added89		///90		/// # Arguments91		///92		/// * collection_id: Globally unique collection identifier.93		///94		/// * admin:  Admin address.95		CollectionAdminAdded(CollectionId, CrossAccountId),9697		/// Collection owned was change98		///99		/// # Arguments100		///101		/// * collection_id: Globally unique collection identifier.102		///103		/// * owner:  New owner address.104		CollectionOwnedChanged(CollectionId, AccountId),105106		/// Collection sponsor was set107		///108		/// # Arguments109		///110		/// * collection_id: Globally unique collection identifier.111		///112		/// * owner:  New sponsor address.113		CollectionSponsorSet(CollectionId, AccountId),114115		/// const on chain schema was set116		///117		/// # Arguments118		///119		/// * collection_id: Globally unique collection identifier.120		ConstOnChainSchemaSet(CollectionId),121122		/// New sponsor was confirm123		///124		/// # Arguments125		///126		/// * collection_id: Globally unique collection identifier.127		///128		/// * sponsor:  New sponsor address.129		SponsorshipConfirmed(CollectionId, AccountId),130131		/// Collection admin was removed132		///133		/// # Arguments134		///135		/// * collection_id: Globally unique collection identifier.136		///137		/// * admin:  Admin address.138		CollectionAdminRemoved(CollectionId, CrossAccountId),139140		/// Address was remove from allow list141		///142		/// # Arguments143		///144		/// * collection_id: Globally unique collection identifier.145		///146		/// * user:  Address.147		AllowListAddressRemoved(CollectionId, CrossAccountId),148149		/// Address was add to allow list150		///151		/// # Arguments152		///153		/// * collection_id: Globally unique collection identifier.154		///155		/// * user:  Address.156		AllowListAddressAdded(CollectionId, CrossAccountId),157158		/// Collection limits was set159		///160		/// # Arguments161		///162		/// * collection_id: Globally unique collection identifier.163		CollectionLimitSet(CollectionId),164165		/// Mint permission	was set166		///167		/// # Arguments168		///169		/// * collection_id: Globally unique collection identifier.170		MintPermissionSet(CollectionId),171172		/// Offchain schema was set173		///174		/// # Arguments175		///176		/// * collection_id: Globally unique collection identifier.177		OffchainSchemaSet(CollectionId),178179		/// Public access mode was set180		///181		/// # Arguments182		///183		/// * collection_id: Globally unique collection identifier.184		///185		/// * mode: New access state.186		PublicAccessModeSet(CollectionId, AccessMode),187188		/// Schema version was set189		///190		/// # Arguments191		///192		/// * collection_id: Globally unique collection identifier.193		SchemaVersionSet(CollectionId),194	}195}196197type SelfWeightOf<T> = <T as Config>::WeightInfo;198199// # Used definitions200//201// ## User control levels202//203// chain-controlled - key is uncontrolled by user204//                    i.e autoincrementing index205//                    can use non-cryptographic hash206// real - key is controlled by user207//        but it is hard to generate enough colliding values, i.e owner of signed txs208//        can use non-cryptographic hash209// controlled - key is completly controlled by users210//              i.e maps with mutable keys211//              should use cryptographic hash212//213// ## User control level downgrade reasons214//215// ?1 - chain-controlled -> controlled216//      collections/tokens can be destroyed, resulting in massive holes217// ?2 - chain-controlled -> controlled218//      same as ?1, but can be only added, resulting in easier exploitation219// ?3 - real -> controlled220//      no confirmation required, so addresses can be easily generated221decl_storage! {222	trait Store for Module<T: Config> as Unique {223224		//#region Private members225		/// Used for migrations226		ChainVersion: u64;227		//#endregion228229		//#region Tokens transfer rate limit baskets230		/// (Collection id (controlled?2), who created (real))231		/// TODO: Off chain worker should remove from this map when collection gets removed232		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;233		/// Collection id (controlled?2), token id (controlled?2)234		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;235		/// Collection id (controlled?2), owning user (real)236		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;237		/// Collection id (controlled?2), token id (controlled?2)238		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>;239		//#endregion240241		/// Approval sponsoring242		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;243		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;244		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>;245	}246}247248decl_module! {249	pub struct Module<T: Config> for enum Call250	where251		origin: T::Origin252	{253		type Error = Error<T>;254255		fn deposit_event() = default;256257		fn on_initialize(_now: T::BlockNumber) -> Weight {258			0259		}260261		/// 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.262		///263		/// # Permissions264		///265		/// * Anyone.266		///267		/// # Arguments268		///269		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.270		///271		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.272		///273		/// * token_prefix: UTF-8 string with token prefix.274		///275		/// * mode: [CollectionMode] collection type and type dependent data.276		// returns collection ID277		#[weight = <SelfWeightOf<T>>::create_collection()]278		#[transactional]279		#[deprecated]280		pub fn create_collection(origin,281								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,282								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,283								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,284								 mode: CollectionMode) -> DispatchResult  {285			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {286				name: collection_name,287				description: collection_description,288				token_prefix,289				mode,290				..Default::default()291			};292			Self::create_collection_ex(origin, data)293		}294295		/// This method creates a collection296		///297		/// Prefer it to deprecated [`created_collection`] method298		#[weight = <SelfWeightOf<T>>::create_collection()]299		#[transactional]300		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {301			let sender = ensure_signed(origin)?;302303			// =========304305			T::CollectionDispatch::create(sender, data)?;306307			Ok(())308		}309310		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.311		///312		/// # Permissions313		///314		/// * Collection Owner.315		///316		/// # Arguments317		///318		/// * collection_id: collection to destroy.319		#[weight = <SelfWeightOf<T>>::destroy_collection()]320		#[transactional]321		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {322			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);323			let collection = <CollectionHandle<T>>::try_get(collection_id)?;324325			// =========326327			T::CollectionDispatch::destroy(sender, collection)?;328329			<NftTransferBasket<T>>::remove_prefix(collection_id, None);330			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);331			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);332333			<NftApproveBasket<T>>::remove_prefix(collection_id, None);334			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);335			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);336337			Ok(())338		}339340		/// Add an address to allow list.341		///342		/// # Permissions343		///344		/// * Collection Owner345		/// * Collection Admin346		///347		/// # Arguments348		///349		/// * collection_id.350		///351		/// * address.352		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]353		#[transactional]354		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{355356			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);357			let collection = <CollectionHandle<T>>::try_get(collection_id)?;358359			<PalletCommon<T>>::toggle_allowlist(360				&collection,361				&sender,362				&address,363				true,364			)?;365366			Self::deposit_event(Event::<T>::AllowListAddressAdded(367				collection_id,368				address369			));370371			Ok(())372		}373374		/// Remove an address from allow list.375		///376		/// # Permissions377		///378		/// * Collection Owner379		/// * Collection Admin380		///381		/// # Arguments382		///383		/// * collection_id.384		///385		/// * address.386		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]387		#[transactional]388		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{389390			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);391			let collection = <CollectionHandle<T>>::try_get(collection_id)?;392393			<PalletCommon<T>>::toggle_allowlist(394				&collection,395				&sender,396				&address,397				false,398			)?;399400			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(401				collection_id,402				address403			));404405			Ok(())406		}407408		/// Toggle between normal and allow list access for the methods with access for `Anyone`.409		///410		/// # Permissions411		///412		/// * Collection Owner.413		///414		/// # Arguments415		///416		/// * collection_id.417		///418		/// * mode: [AccessMode]419		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]420		#[transactional]421		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult422		{423			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);424425			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;426			target_collection.check_is_owner(&sender)?;427428			target_collection.access = mode.clone();429430			<Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(431				collection_id,432				mode433			));434435			target_collection.save()436		}437438		/// Allows Anyone to create tokens if:439		/// * Allow List is enabled, and440		/// * Address is added to allow list, and441		/// * This method was called with True parameter442		///443		/// # Permissions444		/// * Collection Owner445		///446		/// # Arguments447		///448		/// * collection_id.449		///450		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.451		#[weight = <SelfWeightOf<T>>::set_mint_permission()]452		#[transactional]453		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult454		{455			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);456457			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;458			target_collection.check_is_owner(&sender)?;459460			target_collection.mint_mode = mint_permission;461462			<Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(463				collection_id464			));465466			target_collection.save()467		}468469		/// Change the owner of the collection.470		///471		/// # Permissions472		///473		/// * Collection Owner.474		///475		/// # Arguments476		///477		/// * collection_id.478		///479		/// * new_owner.480		#[weight = <SelfWeightOf<T>>::change_collection_owner()]481		#[transactional]482		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {483484			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);485486			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;487			target_collection.check_is_owner(&sender)?;488489			target_collection.owner = new_owner.clone();490			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(491				collection_id,492				new_owner493			));494495			target_collection.save()496		}497498		/// Adds an admin of the Collection.499		/// 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.500		///501		/// # Permissions502		///503		/// * Collection Owner.504		/// * Collection Admin.505		///506		/// # Arguments507		///508		/// * collection_id: ID of the Collection to add admin for.509		///510		/// * new_admin_id: Address of new admin to add.511		#[weight = <SelfWeightOf<T>>::add_collection_admin()]512		#[transactional]513		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {514			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);515			let collection = <CollectionHandle<T>>::try_get(collection_id)?;516517			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(518				collection_id,519				new_admin_id.clone()520			));521522			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)523		}524525		/// 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.526		///527		/// # Permissions528		///529		/// * Collection Owner.530		/// * Collection Admin.531		///532		/// # Arguments533		///534		/// * collection_id: ID of the Collection to remove admin for.535		///536		/// * account_id: Address of admin to remove.537		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]538		#[transactional]539		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {540			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);541			let collection = <CollectionHandle<T>>::try_get(collection_id)?;542543			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(544				collection_id,545				account_id.clone()546			));547548			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)549		}550551		/// # Permissions552		///553		/// * Collection Owner554		///555		/// # Arguments556		///557		/// * collection_id.558		///559		/// * new_sponsor.560		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]561		#[transactional]562		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {563			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);564565			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;566			target_collection.check_is_owner(&sender)?;567568			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());569570			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(571				collection_id,572				new_sponsor573			));574575			target_collection.save()576		}577578		/// # Permissions579		///580		/// * Sponsor.581		///582		/// # Arguments583		///584		/// * collection_id.585		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]586		#[transactional]587		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {588			let sender = ensure_signed(origin)?;589590			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;591			ensure!(592				target_collection.sponsorship.pending_sponsor() == Some(&sender),593				Error::<T>::ConfirmUnsetSponsorFail594			);595596			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());597598			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(599				collection_id,600				sender601			));602603			target_collection.save()604		}605606		/// Switch back to pay-per-own-transaction model.607		///608		/// # Permissions609		///610		/// * Collection owner.611		///612		/// # Arguments613		///614		/// * collection_id.615		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]616		#[transactional]617		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {618			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);619620			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;621			target_collection.check_is_owner(&sender)?;622623			target_collection.sponsorship = SponsorshipState::Disabled;624625			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(626				collection_id627			));628			target_collection.save()629		}630631		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.632		///633		/// # Permissions634		///635		/// * Collection Owner.636		/// * Collection Admin.637		/// * Anyone if638		///     * Allow List is enabled, and639		///     * Address is added to allow list, and640		///     * MintPermission is enabled (see SetMintPermission method)641		///642		/// # Arguments643		///644		/// * collection_id: ID of the collection.645		///646		/// * owner: Address, initial owner of the NFT.647		///648		/// * data: Token data to store on chain.649		#[weight = T::CommonWeightInfo::create_item()]650		#[transactional]651		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {652			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);653			let budget = budget::Value::new(2);654655			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))656		}657658		/// This method creates multiple items in a collection created with CreateCollection method.659		///660		/// # Permissions661		///662		/// * Collection Owner.663		/// * Collection Admin.664		/// * Anyone if665		///     * Allow List is enabled, and666		///     * Address is added to allow list, and667		///     * MintPermission is enabled (see SetMintPermission method)668		///669		/// # Arguments670		///671		/// * collection_id: ID of the collection.672		///673		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].674		///675		/// * owner: Address, initial owner of the NFT.676		#[weight = T::CommonWeightInfo::create_multiple_items(items_data.len() as u32)]677		#[transactional]678		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {679			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);680			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);681			let budget = budget::Value::new(2);682683			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))684		}685686		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]687		#[transactional]688		pub fn set_collection_properties(689			origin,690			collection_id: CollectionId,691			properties: Vec<Property>692		) -> DispatchResultWithPostInfo {693			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);694695			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);696697			dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))698		}699700		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]701		#[transactional]702		pub fn delete_collection_properties(703			origin,704			collection_id: CollectionId,705			property_keys: Vec<PropertyKey>,706		) -> DispatchResultWithPostInfo {707			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);708709			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);710711			dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))712		}713714		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]715		#[transactional]716		pub fn set_token_properties(717			origin,718			collection_id: CollectionId,719			token_id: TokenId,720			properties: Vec<Property>721		) -> DispatchResultWithPostInfo {722			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);723724			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);725726			dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))727		}728729		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]730		#[transactional]731		pub fn delete_token_properties(732			origin,733			collection_id: CollectionId,734			token_id: TokenId,735			property_keys: Vec<PropertyKey>736		) -> DispatchResultWithPostInfo {737			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);738739			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);740741			dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))742		}743744		#[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]745		#[transactional]746		pub fn set_property_permissions(747			origin,748			collection_id: CollectionId,749			property_permissions: Vec<PropertyKeyPermission>,750		) -> DispatchResultWithPostInfo {751			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);752753			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);754755			dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))756		}757758		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]759		#[transactional]760		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {761			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);762			let budget = budget::Value::new(2);763764			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))765		}766767		// TODO! transaction weight768769		/// Set transfers_enabled value for particular collection770		///771		/// # Permissions772		///773		/// * Collection Owner.774		///775		/// # Arguments776		///777		/// * collection_id: ID of the collection.778		///779		/// * value: New flag value.780		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]781		#[transactional]782		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {783			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);784			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;785			target_collection.check_is_owner(&sender)?;786787			// =========788789			target_collection.limits.transfers_enabled = Some(value);790			target_collection.save()791		}792793		/// Destroys a concrete instance of NFT.794		///795		/// # Permissions796		///797		/// * Collection Owner.798		/// * Collection Admin.799		/// * Current NFT Owner.800		///801		/// # Arguments802		///803		/// * collection_id: ID of the collection.804		///805		/// * item_id: ID of NFT to burn.806		#[weight = T::CommonWeightInfo::burn_item()]807		#[transactional]808		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {809			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);810811			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;812			if value == 1 {813				<NftTransferBasket<T>>::remove(collection_id, item_id);814				<NftApproveBasket<T>>::remove(collection_id, item_id);815			}816			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?817			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());818			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));819			Ok(post_info)820		}821822		/// Destroys a concrete instance of NFT on behalf of the owner823		/// See also: [`approve`]824		///825		/// # Permissions826		///827		/// * Collection Owner.828		/// * Collection Admin.829		/// * Current NFT Owner.830		///831		/// # Arguments832		///833		/// * collection_id: ID of the collection.834		///835		/// * item_id: ID of NFT to burn.836		///837		/// * from: owner of item838		#[weight = T::CommonWeightInfo::burn_from()]839		#[transactional]840		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {841			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);842			let budget = budget::Value::new(2);843844			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))845		}846847		/// Change ownership of the token.848		///849		/// # Permissions850		///851		/// * Collection Owner852		/// * Collection Admin853		/// * Current NFT owner854		///855		/// # Arguments856		///857		/// * recipient: Address of token recipient.858		///859		/// * collection_id.860		///861		/// * item_id: ID of the item862		///     * Non-Fungible Mode: Required.863		///     * Fungible Mode: Ignored.864		///     * Re-Fungible Mode: Required.865		///866		/// * value: Amount to transfer.867		///     * Non-Fungible Mode: Ignored868		///     * Fungible Mode: Must specify transferred amount869		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)870		#[weight = T::CommonWeightInfo::transfer()]871		#[transactional]872		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {873			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);874			let budget = budget::Value::new(2);875876			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))877		}878879		/// Set, change, or remove approved address to transfer the ownership of the NFT.880		///881		/// # Permissions882		///883		/// * Collection Owner884		/// * Collection Admin885		/// * Current NFT owner886		///887		/// # Arguments888		///889		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).890		///891		/// * collection_id.892		///893		/// * item_id: ID of the item.894		#[weight = T::CommonWeightInfo::approve()]895		#[transactional]896		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {897			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);898899			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))900		}901902		/// 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.903		///904		/// # Permissions905		/// * Collection Owner906		/// * Collection Admin907		/// * Current NFT owner908		/// * Address approved by current NFT owner909		///910		/// # Arguments911		///912		/// * from: Address that owns token.913		///914		/// * recipient: Address of token recipient.915		///916		/// * collection_id.917		///918		/// * item_id: ID of the item.919		///920		/// * value: Amount to transfer.921		#[weight = T::CommonWeightInfo::transfer_from()]922		#[transactional]923		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {924			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);925			let budget = budget::Value::new(2);926927			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))928		}929930		/// Set schema standard931		/// ImageURL932		/// Unique933		///934		/// # Permissions935		///936		/// * Collection Owner937		/// * Collection Admin938		///939		/// # Arguments940		///941		/// * collection_id.942		///943		/// * schema: SchemaVersion: enum944		#[weight = <SelfWeightOf<T>>::set_schema_version()]945		#[transactional]946		pub fn set_schema_version(947			origin,948			collection_id: CollectionId,949			version: SchemaVersion950		) -> DispatchResult {951			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);952			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;953			target_collection.check_is_owner_or_admin(&sender)?;954			target_collection.schema_version = version;955956			<Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(957				collection_id958			));959960			target_collection.save()961		}962963		/// Set off-chain data schema.964		///965		/// # Permissions966		///967		/// * Collection Owner968		/// * Collection Admin969		///970		/// # Arguments971		///972		/// * collection_id.973		///974		/// * schema: String representing the offchain data schema.975		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]976		#[transactional]977		pub fn set_offchain_schema(978			origin,979			collection_id: CollectionId,980			schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,981		) -> DispatchResult {982			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);983			let collection = <CollectionHandle<T>>::try_get(collection_id)?;984985			// =========986987			<PalletCommon<T>>::set_field(&collection, &sender, CollectionField::OffchainSchema, schema.into_inner())?;988989			<Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(990				collection_id991			));992			Ok(())993		}994995		/// Set const on-chain data schema.996		///997		/// # Permissions998		///999		/// * Collection Owner1000		/// * Collection Admin1001		///1002		/// # Arguments1003		///1004		/// * collection_id.1005		///1006		/// * schema: String representing the const on-chain data schema.1007		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1008		#[transactional]1009		pub fn set_const_on_chain_schema (1010			origin,1011			collection_id: CollectionId,1012			schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1013		) -> DispatchResult {1014			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1015			let collection = <CollectionHandle<T>>::try_get(collection_id)?;10161017			// =========10181019			<PalletCommon<T>>::set_field(&collection, &sender, CollectionField::ConstOnChainSchema, schema.into_inner())?;10201021			<Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1022				collection_id1023			));1024			Ok(())1025		}10261027		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1028		#[transactional]1029		pub fn set_collection_limits(1030			origin,1031			collection_id: CollectionId,1032			new_limit: CollectionLimits,1033		) -> DispatchResult {1034			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1035			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1036			target_collection.check_is_owner(&sender)?;1037			let old_limit = &target_collection.limits;10381039			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10401041			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1042				collection_id1043			));10441045			target_collection.save()1046		}1047	}1048}
after · pallets/unique/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20	clippy::too_many_arguments,21	clippy::unnecessary_mut_passed,22	clippy::unused_unit23)]2425use frame_support::{26	decl_module, decl_storage, decl_error, decl_event,27	dispatch::DispatchResult,28	ensure,29	weights::{Weight},30	transactional,31	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},32	BoundedVec,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	CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,39	MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,40	AccessMode, CreateItemData, CollectionLimits, CollectionId, CollectionMode, TokenId,41	SchemaVersion, SponsorshipState, CreateCollectionData,42	CreateItemExData, budget, CollectionField, Property, PropertyKey, PropertyKeyPermission,43};44use pallet_evm::account::CrossAccountId;45use pallet_common::{46	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo,47	dispatch::dispatch_call, dispatch::CollectionDispatch,48};4950#[cfg(feature = "runtime-benchmarks")]51mod benchmarking;52pub mod weights;53use weights::WeightInfo;5455decl_error! {56	/// Error for non-fungible-token module.57	pub enum Error for Module<T: Config> {58		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.59		CollectionDecimalPointLimitExceeded,60		/// This address is not set as sponsor, use setCollectionSponsor first.61		ConfirmUnsetSponsorFail,62		/// Length of items properties must be greater than 0.63		EmptyArgument,64	}65}6667pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {68	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;6970	/// Weight information for extrinsics in this pallet.71	type WeightInfo: WeightInfo;72	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;73}7475decl_event! {76	pub enum Event<T>77	where78		<T as frame_system::Config>::AccountId,79		<T as pallet_evm::account::Config>::CrossAccountId,80	{81		/// Collection sponsor was removed82		///83		/// # Arguments84		///85		/// * collection_id: Globally unique collection identifier.86		CollectionSponsorRemoved(CollectionId),8788		/// Collection admin was added89		///90		/// # Arguments91		///92		/// * collection_id: Globally unique collection identifier.93		///94		/// * admin:  Admin address.95		CollectionAdminAdded(CollectionId, CrossAccountId),9697		/// Collection owned was change98		///99		/// # Arguments100		///101		/// * collection_id: Globally unique collection identifier.102		///103		/// * owner:  New owner address.104		CollectionOwnedChanged(CollectionId, AccountId),105106		/// Collection sponsor was set107		///108		/// # Arguments109		///110		/// * collection_id: Globally unique collection identifier.111		///112		/// * owner:  New sponsor address.113		CollectionSponsorSet(CollectionId, AccountId),114115		/// const on chain schema was set116		///117		/// # Arguments118		///119		/// * collection_id: Globally unique collection identifier.120		ConstOnChainSchemaSet(CollectionId),121122		/// New sponsor was confirm123		///124		/// # Arguments125		///126		/// * collection_id: Globally unique collection identifier.127		///128		/// * sponsor:  New sponsor address.129		SponsorshipConfirmed(CollectionId, AccountId),130131		/// Collection admin was removed132		///133		/// # Arguments134		///135		/// * collection_id: Globally unique collection identifier.136		///137		/// * admin:  Admin address.138		CollectionAdminRemoved(CollectionId, CrossAccountId),139140		/// Address was remove from allow list141		///142		/// # Arguments143		///144		/// * collection_id: Globally unique collection identifier.145		///146		/// * user:  Address.147		AllowListAddressRemoved(CollectionId, CrossAccountId),148149		/// Address was add to allow list150		///151		/// # Arguments152		///153		/// * collection_id: Globally unique collection identifier.154		///155		/// * user:  Address.156		AllowListAddressAdded(CollectionId, CrossAccountId),157158		/// Collection limits was set159		///160		/// # Arguments161		///162		/// * collection_id: Globally unique collection identifier.163		CollectionLimitSet(CollectionId),164165		/// Mint permission	was set166		///167		/// # Arguments168		///169		/// * collection_id: Globally unique collection identifier.170		MintPermissionSet(CollectionId),171172		/// Offchain schema was set173		///174		/// # Arguments175		///176		/// * collection_id: Globally unique collection identifier.177		OffchainSchemaSet(CollectionId),178179		/// Public access mode was set180		///181		/// # Arguments182		///183		/// * collection_id: Globally unique collection identifier.184		///185		/// * mode: New access state.186		PublicAccessModeSet(CollectionId, AccessMode),187188		/// Schema version was set189		///190		/// # Arguments191		///192		/// * collection_id: Globally unique collection identifier.193		SchemaVersionSet(CollectionId),194	}195}196197type SelfWeightOf<T> = <T as Config>::WeightInfo;198199// # Used definitions200//201// ## User control levels202//203// chain-controlled - key is uncontrolled by user204//                    i.e autoincrementing index205//                    can use non-cryptographic hash206// real - key is controlled by user207//        but it is hard to generate enough colliding values, i.e owner of signed txs208//        can use non-cryptographic hash209// controlled - key is completly controlled by users210//              i.e maps with mutable keys211//              should use cryptographic hash212//213// ## User control level downgrade reasons214//215// ?1 - chain-controlled -> controlled216//      collections/tokens can be destroyed, resulting in massive holes217// ?2 - chain-controlled -> controlled218//      same as ?1, but can be only added, resulting in easier exploitation219// ?3 - real -> controlled220//      no confirmation required, so addresses can be easily generated221decl_storage! {222	trait Store for Module<T: Config> as Unique {223224		//#region Private members225		/// Used for migrations226		ChainVersion: u64;227		//#endregion228229		//#region Tokens transfer rate limit baskets230		/// (Collection id (controlled?2), who created (real))231		/// TODO: Off chain worker should remove from this map when collection gets removed232		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;233		/// Collection id (controlled?2), token id (controlled?2)234		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;235		/// Collection id (controlled?2), owning user (real)236		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;237		/// Collection id (controlled?2), token id (controlled?2)238		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>;239		//#endregion240241		/// Variable metadata sponsoring242		/// Collection id (controlled?2), token id (controlled?2)243		#[deprecated]244		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;245246		/// Approval sponsoring247		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;248		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;249		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>;250	}251}252253decl_module! {254	pub struct Module<T: Config> for enum Call255	where256		origin: T::Origin257	{258		type Error = Error<T>;259260		fn deposit_event() = default;261262		fn on_initialize(_now: T::BlockNumber) -> Weight {263			0264		}265266		fn on_runtime_upgrade() -> Weight {267			let limit = None;268269			<VariableMetaDataBasket<T>>::remove_all(limit);270271			0272		}273274		/// 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.275		///276		/// # Permissions277		///278		/// * Anyone.279		///280		/// # Arguments281		///282		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.283		///284		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.285		///286		/// * token_prefix: UTF-8 string with token prefix.287		///288		/// * mode: [CollectionMode] collection type and type dependent data.289		// returns collection ID290		#[weight = <SelfWeightOf<T>>::create_collection()]291		#[transactional]292		#[deprecated]293		pub fn create_collection(origin,294								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,295								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,296								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,297								 mode: CollectionMode) -> DispatchResult  {298			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {299				name: collection_name,300				description: collection_description,301				token_prefix,302				mode,303				..Default::default()304			};305			Self::create_collection_ex(origin, data)306		}307308		/// This method creates a collection309		///310		/// Prefer it to deprecated [`created_collection`] method311		#[weight = <SelfWeightOf<T>>::create_collection()]312		#[transactional]313		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {314			let sender = ensure_signed(origin)?;315316			// =========317318			T::CollectionDispatch::create(sender, data)?;319320			Ok(())321		}322323		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.324		///325		/// # Permissions326		///327		/// * Collection Owner.328		///329		/// # Arguments330		///331		/// * collection_id: collection to destroy.332		#[weight = <SelfWeightOf<T>>::destroy_collection()]333		#[transactional]334		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {335			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);336			let collection = <CollectionHandle<T>>::try_get(collection_id)?;337338			// =========339340			T::CollectionDispatch::destroy(sender, collection)?;341342			<NftTransferBasket<T>>::remove_prefix(collection_id, None);343			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);344			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);345346			<NftApproveBasket<T>>::remove_prefix(collection_id, None);347			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);348			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);349350			Ok(())351		}352353		/// Add an address to allow list.354		///355		/// # Permissions356		///357		/// * Collection Owner358		/// * Collection Admin359		///360		/// # Arguments361		///362		/// * collection_id.363		///364		/// * address.365		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]366		#[transactional]367		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{368369			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);370			let collection = <CollectionHandle<T>>::try_get(collection_id)?;371372			<PalletCommon<T>>::toggle_allowlist(373				&collection,374				&sender,375				&address,376				true,377			)?;378379			Self::deposit_event(Event::<T>::AllowListAddressAdded(380				collection_id,381				address382			));383384			Ok(())385		}386387		/// Remove an address from allow list.388		///389		/// # Permissions390		///391		/// * Collection Owner392		/// * Collection Admin393		///394		/// # Arguments395		///396		/// * collection_id.397		///398		/// * address.399		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]400		#[transactional]401		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{402403			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);404			let collection = <CollectionHandle<T>>::try_get(collection_id)?;405406			<PalletCommon<T>>::toggle_allowlist(407				&collection,408				&sender,409				&address,410				false,411			)?;412413			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(414				collection_id,415				address416			));417418			Ok(())419		}420421		/// Toggle between normal and allow list access for the methods with access for `Anyone`.422		///423		/// # Permissions424		///425		/// * Collection Owner.426		///427		/// # Arguments428		///429		/// * collection_id.430		///431		/// * mode: [AccessMode]432		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]433		#[transactional]434		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult435		{436			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);437438			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;439			target_collection.check_is_owner(&sender)?;440441			target_collection.access = mode.clone();442443			<Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(444				collection_id,445				mode446			));447448			target_collection.save()449		}450451		/// Allows Anyone to create tokens if:452		/// * Allow List is enabled, and453		/// * Address is added to allow list, and454		/// * This method was called with True parameter455		///456		/// # Permissions457		/// * Collection Owner458		///459		/// # Arguments460		///461		/// * collection_id.462		///463		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.464		#[weight = <SelfWeightOf<T>>::set_mint_permission()]465		#[transactional]466		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult467		{468			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);469470			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;471			target_collection.check_is_owner(&sender)?;472473			target_collection.mint_mode = mint_permission;474475			<Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(476				collection_id477			));478479			target_collection.save()480		}481482		/// Change the owner of the collection.483		///484		/// # Permissions485		///486		/// * Collection Owner.487		///488		/// # Arguments489		///490		/// * collection_id.491		///492		/// * new_owner.493		#[weight = <SelfWeightOf<T>>::change_collection_owner()]494		#[transactional]495		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {496497			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);498499			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;500			target_collection.check_is_owner(&sender)?;501502			target_collection.owner = new_owner.clone();503			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(504				collection_id,505				new_owner506			));507508			target_collection.save()509		}510511		/// Adds an admin of the Collection.512		/// 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.513		///514		/// # Permissions515		///516		/// * Collection Owner.517		/// * Collection Admin.518		///519		/// # Arguments520		///521		/// * collection_id: ID of the Collection to add admin for.522		///523		/// * new_admin_id: Address of new admin to add.524		#[weight = <SelfWeightOf<T>>::add_collection_admin()]525		#[transactional]526		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {527			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);528			let collection = <CollectionHandle<T>>::try_get(collection_id)?;529530			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(531				collection_id,532				new_admin_id.clone()533			));534535			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)536		}537538		/// 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.539		///540		/// # Permissions541		///542		/// * Collection Owner.543		/// * Collection Admin.544		///545		/// # Arguments546		///547		/// * collection_id: ID of the Collection to remove admin for.548		///549		/// * account_id: Address of admin to remove.550		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]551		#[transactional]552		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {553			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);554			let collection = <CollectionHandle<T>>::try_get(collection_id)?;555556			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(557				collection_id,558				account_id.clone()559			));560561			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)562		}563564		/// # Permissions565		///566		/// * Collection Owner567		///568		/// # Arguments569		///570		/// * collection_id.571		///572		/// * new_sponsor.573		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]574		#[transactional]575		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {576			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);577578			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;579			target_collection.check_is_owner(&sender)?;580581			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());582583			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(584				collection_id,585				new_sponsor586			));587588			target_collection.save()589		}590591		/// # Permissions592		///593		/// * Sponsor.594		///595		/// # Arguments596		///597		/// * collection_id.598		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]599		#[transactional]600		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {601			let sender = ensure_signed(origin)?;602603			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;604			ensure!(605				target_collection.sponsorship.pending_sponsor() == Some(&sender),606				Error::<T>::ConfirmUnsetSponsorFail607			);608609			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());610611			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(612				collection_id,613				sender614			));615616			target_collection.save()617		}618619		/// Switch back to pay-per-own-transaction model.620		///621		/// # Permissions622		///623		/// * Collection owner.624		///625		/// # Arguments626		///627		/// * collection_id.628		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]629		#[transactional]630		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {631			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);632633			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;634			target_collection.check_is_owner(&sender)?;635636			target_collection.sponsorship = SponsorshipState::Disabled;637638			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(639				collection_id640			));641			target_collection.save()642		}643644		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.645		///646		/// # Permissions647		///648		/// * Collection Owner.649		/// * Collection Admin.650		/// * Anyone if651		///     * Allow List is enabled, and652		///     * Address is added to allow list, and653		///     * MintPermission is enabled (see SetMintPermission method)654		///655		/// # Arguments656		///657		/// * collection_id: ID of the collection.658		///659		/// * owner: Address, initial owner of the NFT.660		///661		/// * data: Token data to store on chain.662		#[weight = T::CommonWeightInfo::create_item()]663		#[transactional]664		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {665			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);666			let budget = budget::Value::new(2);667668			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))669		}670671		/// This method creates multiple items in a collection created with CreateCollection method.672		///673		/// # Permissions674		///675		/// * Collection Owner.676		/// * Collection Admin.677		/// * Anyone if678		///     * Allow List is enabled, and679		///     * Address is added to allow list, and680		///     * MintPermission is enabled (see SetMintPermission method)681		///682		/// # Arguments683		///684		/// * collection_id: ID of the collection.685		///686		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].687		///688		/// * owner: Address, initial owner of the NFT.689		#[weight = T::CommonWeightInfo::create_multiple_items(items_data.len() as u32)]690		#[transactional]691		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {692			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);693			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);694			let budget = budget::Value::new(2);695696			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))697		}698699		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]700		#[transactional]701		pub fn set_collection_properties(702			origin,703			collection_id: CollectionId,704			properties: Vec<Property>705		) -> DispatchResultWithPostInfo {706			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);707708			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);709710			dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))711		}712713		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]714		#[transactional]715		pub fn delete_collection_properties(716			origin,717			collection_id: CollectionId,718			property_keys: Vec<PropertyKey>,719		) -> DispatchResultWithPostInfo {720			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);721722			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);723724			dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))725		}726727		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]728		#[transactional]729		pub fn set_token_properties(730			origin,731			collection_id: CollectionId,732			token_id: TokenId,733			properties: Vec<Property>734		) -> DispatchResultWithPostInfo {735			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);736737			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);738739			dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))740		}741742		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]743		#[transactional]744		pub fn delete_token_properties(745			origin,746			collection_id: CollectionId,747			token_id: TokenId,748			property_keys: Vec<PropertyKey>749		) -> DispatchResultWithPostInfo {750			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);751752			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);753754			dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))755		}756757		#[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]758		#[transactional]759		pub fn set_property_permissions(760			origin,761			collection_id: CollectionId,762			property_permissions: Vec<PropertyKeyPermission>,763		) -> DispatchResultWithPostInfo {764			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);765766			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);767768			dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))769		}770771		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]772		#[transactional]773		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {774			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);775			let budget = budget::Value::new(2);776777			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))778		}779780		// TODO! transaction weight781782		/// Set transfers_enabled value for particular collection783		///784		/// # Permissions785		///786		/// * Collection Owner.787		///788		/// # Arguments789		///790		/// * collection_id: ID of the collection.791		///792		/// * value: New flag value.793		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]794		#[transactional]795		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {796			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);797			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;798			target_collection.check_is_owner(&sender)?;799800			// =========801802			target_collection.limits.transfers_enabled = Some(value);803			target_collection.save()804		}805806		/// Destroys a concrete instance of NFT.807		///808		/// # Permissions809		///810		/// * Collection Owner.811		/// * Collection Admin.812		/// * Current NFT Owner.813		///814		/// # Arguments815		///816		/// * collection_id: ID of the collection.817		///818		/// * item_id: ID of NFT to burn.819		#[weight = T::CommonWeightInfo::burn_item()]820		#[transactional]821		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {822			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);823824			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;825			if value == 1 {826				<NftTransferBasket<T>>::remove(collection_id, item_id);827				<NftApproveBasket<T>>::remove(collection_id, item_id);828			}829			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?830			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());831			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));832			Ok(post_info)833		}834835		/// Destroys a concrete instance of NFT on behalf of the owner836		/// See also: [`approve`]837		///838		/// # Permissions839		///840		/// * Collection Owner.841		/// * Collection Admin.842		/// * Current NFT Owner.843		///844		/// # Arguments845		///846		/// * collection_id: ID of the collection.847		///848		/// * item_id: ID of NFT to burn.849		///850		/// * from: owner of item851		#[weight = T::CommonWeightInfo::burn_from()]852		#[transactional]853		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {854			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);855			let budget = budget::Value::new(2);856857			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))858		}859860		/// Change ownership of the token.861		///862		/// # Permissions863		///864		/// * Collection Owner865		/// * Collection Admin866		/// * Current NFT owner867		///868		/// # Arguments869		///870		/// * recipient: Address of token recipient.871		///872		/// * collection_id.873		///874		/// * item_id: ID of the item875		///     * Non-Fungible Mode: Required.876		///     * Fungible Mode: Ignored.877		///     * Re-Fungible Mode: Required.878		///879		/// * value: Amount to transfer.880		///     * Non-Fungible Mode: Ignored881		///     * Fungible Mode: Must specify transferred amount882		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)883		#[weight = T::CommonWeightInfo::transfer()]884		#[transactional]885		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {886			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);887			let budget = budget::Value::new(2);888889			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))890		}891892		/// Set, change, or remove approved address to transfer the ownership of the NFT.893		///894		/// # Permissions895		///896		/// * Collection Owner897		/// * Collection Admin898		/// * Current NFT owner899		///900		/// # Arguments901		///902		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).903		///904		/// * collection_id.905		///906		/// * item_id: ID of the item.907		#[weight = T::CommonWeightInfo::approve()]908		#[transactional]909		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {910			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);911912			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))913		}914915		/// 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.916		///917		/// # Permissions918		/// * Collection Owner919		/// * Collection Admin920		/// * Current NFT owner921		/// * Address approved by current NFT owner922		///923		/// # Arguments924		///925		/// * from: Address that owns token.926		///927		/// * recipient: Address of token recipient.928		///929		/// * collection_id.930		///931		/// * item_id: ID of the item.932		///933		/// * value: Amount to transfer.934		#[weight = T::CommonWeightInfo::transfer_from()]935		#[transactional]936		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {937			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);938			let budget = budget::Value::new(2);939940			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))941		}942943		/// Set schema standard944		/// ImageURL945		/// Unique946		///947		/// # Permissions948		///949		/// * Collection Owner950		/// * Collection Admin951		///952		/// # Arguments953		///954		/// * collection_id.955		///956		/// * schema: SchemaVersion: enum957		#[weight = <SelfWeightOf<T>>::set_schema_version()]958		#[transactional]959		pub fn set_schema_version(960			origin,961			collection_id: CollectionId,962			version: SchemaVersion963		) -> DispatchResult {964			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);965			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;966			target_collection.check_is_owner_or_admin(&sender)?;967			target_collection.schema_version = version;968969			<Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(970				collection_id971			));972973			target_collection.save()974		}975976		/// Set off-chain data schema.977		///978		/// # Permissions979		///980		/// * Collection Owner981		/// * Collection Admin982		///983		/// # Arguments984		///985		/// * collection_id.986		///987		/// * schema: String representing the offchain data schema.988		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]989		#[transactional]990		pub fn set_offchain_schema(991			origin,992			collection_id: CollectionId,993			schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,994		) -> DispatchResult {995			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);996			let collection = <CollectionHandle<T>>::try_get(collection_id)?;997998			// =========9991000			<PalletCommon<T>>::set_field(&collection, &sender, CollectionField::OffchainSchema, schema.into_inner())?;10011002			<Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1003				collection_id1004			));1005			Ok(())1006		}10071008		/// Set const on-chain data schema.1009		///1010		/// # Permissions1011		///1012		/// * Collection Owner1013		/// * Collection Admin1014		///1015		/// # Arguments1016		///1017		/// * collection_id.1018		///1019		/// * schema: String representing the const on-chain data schema.1020		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1021		#[transactional]1022		pub fn set_const_on_chain_schema (1023			origin,1024			collection_id: CollectionId,1025			schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1026		) -> DispatchResult {1027			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1028			let collection = <CollectionHandle<T>>::try_get(collection_id)?;10291030			// =========10311032			<PalletCommon<T>>::set_field(&collection, &sender, CollectionField::ConstOnChainSchema, schema.into_inner())?;10331034			<Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1035				collection_id1036			));1037			Ok(())1038		}10391040		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1041		#[transactional]1042		pub fn set_collection_limits(1043			origin,1044			collection_id: CollectionId,1045			new_limit: CollectionLimits,1046		) -> DispatchResult {1047			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1048			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1049			target_collection.check_is_owner(&sender)?;1050			let old_limit = &target_collection.limits;10511052			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10531054			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1055				collection_id1056			));10571058			target_collection.save()1059		}1060	}1061}