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

difftreelog

fix transfersubbundles

Daniel Shiposha2022-05-27parent: #101a189.patch.diff
in: master

2 files changed

modifiedpallets/structure/src/lib.rsdiffbeforeafterboth
--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -156,8 +156,8 @@
 		budget: &dyn Budget,
 	) -> Result<bool, DispatchError> {
 		let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {
-			Some((collection, token)) => Parent::Token(collection, token),
-			None => Parent::User(user),
+			Some((collection, token)) => Self::find_topmost_owner(collection, token, budget)?,
+			None => user,
 		};
 
 		// Tried to nest token in itself
@@ -172,7 +172,7 @@
 					return Err(<Error<T>>::OuroborosDetected.into())
 				}
 				// Found needed parent, token is indirecty owned
-				v if v == target_parent => return Ok(true),
+				Parent::User(user) if user == target_parent => return Ok(true),
 				// Token is owned by other user
 				Parent::User(_) => return Ok(false),
 				Parent::TokenNotFound => return Err(<Error<T>>::TokenNotFound.into()),
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	MAX_COLLECTION_NAME_LENGTH,39	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData,40	CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId, SponsorshipState,41	CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,42	PropertyKeyPermission,43};44use pallet_evm::account::CrossAccountId;45use pallet_common::{46	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_call,47	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		CollectionPermissionSet(CollectionId),166167		/// Mint permission	was set168		///169		/// # Arguments170		///171		/// * collection_id: Globally unique collection identifier.172		MintPermissionSet(CollectionId),173174		/// Offchain schema was set175		///176		/// # Arguments177		///178		/// * collection_id: Globally unique collection identifier.179		OffchainSchemaSet(CollectionId),180181		/// Public access mode was set182		///183		/// # Arguments184		///185		/// * collection_id: Globally unique collection identifier.186		///187		/// * mode: New access state.188		PublicAccessModeSet(CollectionId, AccessMode),189190		/// Schema version was set191		///192		/// # Arguments193		///194		/// * collection_id: Globally unique collection identifier.195		SchemaVersionSet(CollectionId),196	}197}198199type SelfWeightOf<T> = <T as Config>::WeightInfo;200201// # Used definitions202//203// ## User control levels204//205// chain-controlled - key is uncontrolled by user206//                    i.e autoincrementing index207//                    can use non-cryptographic hash208// real - key is controlled by user209//        but it is hard to generate enough colliding values, i.e owner of signed txs210//        can use non-cryptographic hash211// controlled - key is completly controlled by users212//              i.e maps with mutable keys213//              should use cryptographic hash214//215// ## User control level downgrade reasons216//217// ?1 - chain-controlled -> controlled218//      collections/tokens can be destroyed, resulting in massive holes219// ?2 - chain-controlled -> controlled220//      same as ?1, but can be only added, resulting in easier exploitation221// ?3 - real -> controlled222//      no confirmation required, so addresses can be easily generated223decl_storage! {224	trait Store for Module<T: Config> as Unique {225226		//#region Private members227		/// Used for migrations228		ChainVersion: u64;229		//#endregion230231		//#region Tokens transfer rate limit baskets232		/// (Collection id (controlled?2), who created (real))233		/// TODO: Off chain worker should remove from this map when collection gets removed234		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;235		/// Collection id (controlled?2), token id (controlled?2)236		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;237		/// Collection id (controlled?2), owning user (real)238		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;239		/// Collection id (controlled?2), token id (controlled?2)240		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>;241		//#endregion242243		/// Variable metadata sponsoring244		/// Collection id (controlled?2), token id (controlled?2)245		#[deprecated]246		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;247		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;248249		/// Approval sponsoring250		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;251		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;252		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>;253	}254}255256decl_module! {257	pub struct Module<T: Config> for enum Call258	where259		origin: T::Origin260	{261		type Error = Error<T>;262263		fn deposit_event() = default;264265		fn on_initialize(_now: T::BlockNumber) -> Weight {266			0267		}268269		fn on_runtime_upgrade() -> Weight {270			let limit = None;271272			<VariableMetaDataBasket<T>>::remove_all(limit);273274			0275		}276277		/// 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.278		///279		/// # Permissions280		///281		/// * Anyone.282		///283		/// # Arguments284		///285		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.286		///287		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.288		///289		/// * token_prefix: UTF-8 string with token prefix.290		///291		/// * mode: [CollectionMode] collection type and type dependent data.292		// returns collection ID293		#[weight = <SelfWeightOf<T>>::create_collection()]294		#[transactional]295		#[deprecated]296		pub fn create_collection(origin,297								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,298								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,299								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,300								 mode: CollectionMode) -> DispatchResult  {301			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {302				name: collection_name,303				description: collection_description,304				token_prefix,305				mode,306				..Default::default()307			};308			Self::create_collection_ex(origin, data)309		}310311		/// This method creates a collection312		///313		/// Prefer it to deprecated [`created_collection`] method314		#[weight = <SelfWeightOf<T>>::create_collection()]315		#[transactional]316		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {317			let sender = ensure_signed(origin)?;318319			// =========320321			T::CollectionDispatch::create(sender, data)?;322323			Ok(())324		}325326		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.327		///328		/// # Permissions329		///330		/// * Collection Owner.331		///332		/// # Arguments333		///334		/// * collection_id: collection to destroy.335		#[weight = <SelfWeightOf<T>>::destroy_collection()]336		#[transactional]337		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {338			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);339			let collection = <CollectionHandle<T>>::try_get(collection_id)?;340341			// =========342343			T::CollectionDispatch::destroy(sender, collection)?;344345			<NftTransferBasket<T>>::remove_prefix(collection_id, None);346			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);347			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);348349			<NftApproveBasket<T>>::remove_prefix(collection_id, None);350			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);351			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);352353			Ok(())354		}355356		/// Add an address to allow list.357		///358		/// # Permissions359		///360		/// * Collection Owner361		/// * Collection Admin362		///363		/// # Arguments364		///365		/// * collection_id.366		///367		/// * address.368		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]369		#[transactional]370		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{371372			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);373			let collection = <CollectionHandle<T>>::try_get(collection_id)?;374375			<PalletCommon<T>>::toggle_allowlist(376				&collection,377				&sender,378				&address,379				true,380			)?;381382			Self::deposit_event(Event::<T>::AllowListAddressAdded(383				collection_id,384				address385			));386387			Ok(())388		}389390		/// Remove an address from allow list.391		///392		/// # Permissions393		///394		/// * Collection Owner395		/// * Collection Admin396		///397		/// # Arguments398		///399		/// * collection_id.400		///401		/// * address.402		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]403		#[transactional]404		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{405406			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);407			let collection = <CollectionHandle<T>>::try_get(collection_id)?;408409			<PalletCommon<T>>::toggle_allowlist(410				&collection,411				&sender,412				&address,413				false,414			)?;415416			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(417				collection_id,418				address419			));420421			Ok(())422		}423424		/// Change the owner of the collection.425		///426		/// # Permissions427		///428		/// * Collection Owner.429		///430		/// # Arguments431		///432		/// * collection_id.433		///434		/// * new_owner.435		#[weight = <SelfWeightOf<T>>::change_collection_owner()]436		#[transactional]437		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {438439			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);440441			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;442			target_collection.check_is_owner(&sender)?;443444			target_collection.owner = new_owner.clone();445			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(446				collection_id,447				new_owner448			));449450			target_collection.save()451		}452453		/// Adds an admin of the Collection.454		/// 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.455		///456		/// # Permissions457		///458		/// * Collection Owner.459		/// * Collection Admin.460		///461		/// # Arguments462		///463		/// * collection_id: ID of the Collection to add admin for.464		///465		/// * new_admin_id: Address of new admin to add.466		#[weight = <SelfWeightOf<T>>::add_collection_admin()]467		#[transactional]468		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {469			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);470			let collection = <CollectionHandle<T>>::try_get(collection_id)?;471472			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(473				collection_id,474				new_admin_id.clone()475			));476477			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)478		}479480		/// 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.481		///482		/// # Permissions483		///484		/// * Collection Owner.485		/// * Collection Admin.486		///487		/// # Arguments488		///489		/// * collection_id: ID of the Collection to remove admin for.490		///491		/// * account_id: Address of admin to remove.492		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]493		#[transactional]494		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {495			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);496			let collection = <CollectionHandle<T>>::try_get(collection_id)?;497498			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(499				collection_id,500				account_id.clone()501			));502503			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)504		}505506		/// # Permissions507		///508		/// * Collection Owner509		///510		/// # Arguments511		///512		/// * collection_id.513		///514		/// * new_sponsor.515		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]516		#[transactional]517		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {518			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);519520			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;521			target_collection.check_is_owner(&sender)?;522523			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());524525			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(526				collection_id,527				new_sponsor528			));529530			target_collection.save()531		}532533		/// # Permissions534		///535		/// * Sponsor.536		///537		/// # Arguments538		///539		/// * collection_id.540		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]541		#[transactional]542		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {543			let sender = ensure_signed(origin)?;544545			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;546			ensure!(547				target_collection.sponsorship.pending_sponsor() == Some(&sender),548				Error::<T>::ConfirmUnsetSponsorFail549			);550551			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());552553			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(554				collection_id,555				sender556			));557558			target_collection.save()559		}560561		/// Switch back to pay-per-own-transaction model.562		///563		/// # Permissions564		///565		/// * Collection owner.566		///567		/// # Arguments568		///569		/// * collection_id.570		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]571		#[transactional]572		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {573			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);574575			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;576			target_collection.check_is_owner(&sender)?;577578			target_collection.sponsorship = SponsorshipState::Disabled;579580			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(581				collection_id582			));583			target_collection.save()584		}585586		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.587		///588		/// # Permissions589		///590		/// * Collection Owner.591		/// * Collection Admin.592		/// * Anyone if593		///     * Allow List is enabled, and594		///     * Address is added to allow list, and595		///     * MintPermission is enabled (see SetMintPermission method)596		///597		/// # Arguments598		///599		/// * collection_id: ID of the collection.600		///601		/// * owner: Address, initial owner of the NFT.602		///603		/// * data: Token data to store on chain.604		#[weight = T::CommonWeightInfo::create_item()]605		#[transactional]606		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {607			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);608			let budget = budget::Value::new(2);609610			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))611		}612613		/// This method creates multiple items in a collection created with CreateCollection method.614		///615		/// # Permissions616		///617		/// * Collection Owner.618		/// * Collection Admin.619		/// * Anyone if620		///     * Allow List is enabled, and621		///     * Address is added to allow list, and622		///     * MintPermission is enabled (see SetMintPermission method)623		///624		/// # Arguments625		///626		/// * collection_id: ID of the collection.627		///628		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].629		///630		/// * owner: Address, initial owner of the NFT.631		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]632		#[transactional]633		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {634			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);635			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);636			let budget = budget::Value::new(2);637638			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))639		}640641		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]642		#[transactional]643		pub fn set_collection_properties(644			origin,645			collection_id: CollectionId,646			properties: Vec<Property>647		) -> DispatchResultWithPostInfo {648			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);649650			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);651652			dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))653		}654655		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]656		#[transactional]657		pub fn delete_collection_properties(658			origin,659			collection_id: CollectionId,660			property_keys: Vec<PropertyKey>,661		) -> DispatchResultWithPostInfo {662			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);663664			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);665666			dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))667		}668669		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]670		#[transactional]671		pub fn set_token_properties(672			origin,673			collection_id: CollectionId,674			token_id: TokenId,675			properties: Vec<Property>676		) -> DispatchResultWithPostInfo {677			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);678679			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);680681			dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))682		}683684		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]685		#[transactional]686		pub fn delete_token_properties(687			origin,688			collection_id: CollectionId,689			token_id: TokenId,690			property_keys: Vec<PropertyKey>691		) -> DispatchResultWithPostInfo {692			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);693694			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);695696			dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))697		}698699		#[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]700		#[transactional]701		pub fn set_property_permissions(702			origin,703			collection_id: CollectionId,704			property_permissions: Vec<PropertyKeyPermission>,705		) -> DispatchResultWithPostInfo {706			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);707708			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);709710			dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))711		}712713		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]714		#[transactional]715		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {716			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);717			let budget = budget::Value::new(2);718719			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))720		}721722		// TODO! transaction weight723724		/// Set transfers_enabled value for particular collection725		///726		/// # Permissions727		///728		/// * Collection Owner.729		///730		/// # Arguments731		///732		/// * collection_id: ID of the collection.733		///734		/// * value: New flag value.735		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]736		#[transactional]737		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {738			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);739			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;740			target_collection.check_is_owner(&sender)?;741742			// =========743744			target_collection.limits.transfers_enabled = Some(value);745			target_collection.save()746		}747748		/// Destroys a concrete instance of NFT.749		///750		/// # Permissions751		///752		/// * Collection Owner.753		/// * Collection Admin.754		/// * Current NFT Owner.755		///756		/// # Arguments757		///758		/// * collection_id: ID of the collection.759		///760		/// * item_id: ID of NFT to burn.761		#[weight = T::CommonWeightInfo::burn_item()]762		#[transactional]763		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {764			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);765766			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;767			if value == 1 {768				<NftTransferBasket<T>>::remove(collection_id, item_id);769				<NftApproveBasket<T>>::remove(collection_id, item_id);770			}771			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?772			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());773			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));774			Ok(post_info)775		}776777		/// Destroys a concrete instance of NFT on behalf of the owner778		/// See also: [`approve`]779		///780		/// # Permissions781		///782		/// * Collection Owner.783		/// * Collection Admin.784		/// * Current NFT Owner.785		///786		/// # Arguments787		///788		/// * collection_id: ID of the collection.789		///790		/// * item_id: ID of NFT to burn.791		///792		/// * from: owner of item793		#[weight = T::CommonWeightInfo::burn_from()]794		#[transactional]795		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {796			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);797			let budget = budget::Value::new(2);798799			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))800		}801802		/// Change ownership of the token.803		///804		/// # Permissions805		///806		/// * Collection Owner807		/// * Collection Admin808		/// * Current NFT owner809		///810		/// # Arguments811		///812		/// * recipient: Address of token recipient.813		///814		/// * collection_id.815		///816		/// * item_id: ID of the item817		///     * Non-Fungible Mode: Required.818		///     * Fungible Mode: Ignored.819		///     * Re-Fungible Mode: Required.820		///821		/// * value: Amount to transfer.822		///     * Non-Fungible Mode: Ignored823		///     * Fungible Mode: Must specify transferred amount824		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)825		#[weight = T::CommonWeightInfo::transfer()]826		#[transactional]827		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {828			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);829			let budget = budget::Value::new(2);830831			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))832		}833834		/// Set, change, or remove approved address to transfer the ownership of the NFT.835		///836		/// # Permissions837		///838		/// * Collection Owner839		/// * Collection Admin840		/// * Current NFT owner841		///842		/// # Arguments843		///844		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).845		///846		/// * collection_id.847		///848		/// * item_id: ID of the item.849		#[weight = T::CommonWeightInfo::approve()]850		#[transactional]851		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {852			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);853854			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))855		}856857		/// 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.858		///859		/// # Permissions860		/// * Collection Owner861		/// * Collection Admin862		/// * Current NFT owner863		/// * Address approved by current NFT owner864		///865		/// # Arguments866		///867		/// * from: Address that owns token.868		///869		/// * recipient: Address of token recipient.870		///871		/// * collection_id.872		///873		/// * item_id: ID of the item.874		///875		/// * value: Amount to transfer.876		#[weight = T::CommonWeightInfo::transfer_from()]877		#[transactional]878		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {879			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);880			let budget = budget::Value::new(2);881882			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))883		}884885		#[weight = <SelfWeightOf<T>>::set_collection_limits()]886		#[transactional]887		pub fn set_collection_limits(888			origin,889			collection_id: CollectionId,890			new_limit: CollectionLimits,891		) -> DispatchResult {892			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);893			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;894			target_collection.check_is_owner(&sender)?;895			let old_limit = &target_collection.limits;896897			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;898899			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(900				collection_id901			));902903			target_collection.save()904		}905906		#[weight = <SelfWeightOf<T>>::set_collection_limits()]907		#[transactional]908		pub fn set_collection_permissions(909			origin,910			collection_id: CollectionId,911			new_limit: CollectionPermissions,912		) -> DispatchResult {913			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);914			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;915			target_collection.check_is_owner(&sender)?;916			let old_limit = &target_collection.permissions;917918			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;919920			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(921				collection_id922			));923924			target_collection.save()925		}926	}927}
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	MAX_COLLECTION_NAME_LENGTH,39	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData,40	CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId, SponsorshipState,41	CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,42	PropertyKeyPermission,43};44use pallet_evm::account::CrossAccountId;45use pallet_common::{46	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_call,47	dispatch::CollectionDispatch,48};4950#[cfg(feature = "runtime-benchmarks")]51mod benchmarking;52pub mod weights;53use weights::WeightInfo;5455const NESTING_BUDGET: u32 = 5;5657decl_error! {58	/// Error for non-fungible-token module.59	pub enum Error for Module<T: Config> {60		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.61		CollectionDecimalPointLimitExceeded,62		/// This address is not set as sponsor, use setCollectionSponsor first.63		ConfirmUnsetSponsorFail,64		/// Length of items properties must be greater than 0.65		EmptyArgument,66	}67}6869pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {70	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;7172	/// Weight information for extrinsics in this pallet.73	type WeightInfo: WeightInfo;74	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;75}7677decl_event! {78	pub enum Event<T>79	where80		<T as frame_system::Config>::AccountId,81		<T as pallet_evm::account::Config>::CrossAccountId,82	{83		/// Collection sponsor was removed84		///85		/// # Arguments86		///87		/// * collection_id: Globally unique collection identifier.88		CollectionSponsorRemoved(CollectionId),8990		/// Collection admin was added91		///92		/// # Arguments93		///94		/// * collection_id: Globally unique collection identifier.95		///96		/// * admin:  Admin address.97		CollectionAdminAdded(CollectionId, CrossAccountId),9899		/// Collection owned was change100		///101		/// # Arguments102		///103		/// * collection_id: Globally unique collection identifier.104		///105		/// * owner:  New owner address.106		CollectionOwnedChanged(CollectionId, AccountId),107108		/// Collection sponsor was set109		///110		/// # Arguments111		///112		/// * collection_id: Globally unique collection identifier.113		///114		/// * owner:  New sponsor address.115		CollectionSponsorSet(CollectionId, AccountId),116117		/// const on chain schema was set118		///119		/// # Arguments120		///121		/// * collection_id: Globally unique collection identifier.122		ConstOnChainSchemaSet(CollectionId),123124		/// New sponsor was confirm125		///126		/// # Arguments127		///128		/// * collection_id: Globally unique collection identifier.129		///130		/// * sponsor:  New sponsor address.131		SponsorshipConfirmed(CollectionId, AccountId),132133		/// Collection admin was removed134		///135		/// # Arguments136		///137		/// * collection_id: Globally unique collection identifier.138		///139		/// * admin:  Admin address.140		CollectionAdminRemoved(CollectionId, CrossAccountId),141142		/// Address was remove from allow list143		///144		/// # Arguments145		///146		/// * collection_id: Globally unique collection identifier.147		///148		/// * user:  Address.149		AllowListAddressRemoved(CollectionId, CrossAccountId),150151		/// Address was add to allow list152		///153		/// # Arguments154		///155		/// * collection_id: Globally unique collection identifier.156		///157		/// * user:  Address.158		AllowListAddressAdded(CollectionId, CrossAccountId),159160		/// Collection limits was set161		///162		/// # Arguments163		///164		/// * collection_id: Globally unique collection identifier.165		CollectionLimitSet(CollectionId),166167		CollectionPermissionSet(CollectionId),168169		/// Mint permission	was set170		///171		/// # Arguments172		///173		/// * collection_id: Globally unique collection identifier.174		MintPermissionSet(CollectionId),175176		/// Offchain schema was set177		///178		/// # Arguments179		///180		/// * collection_id: Globally unique collection identifier.181		OffchainSchemaSet(CollectionId),182183		/// Public access mode was set184		///185		/// # Arguments186		///187		/// * collection_id: Globally unique collection identifier.188		///189		/// * mode: New access state.190		PublicAccessModeSet(CollectionId, AccessMode),191192		/// Schema version was set193		///194		/// # Arguments195		///196		/// * collection_id: Globally unique collection identifier.197		SchemaVersionSet(CollectionId),198	}199}200201type SelfWeightOf<T> = <T as Config>::WeightInfo;202203// # Used definitions204//205// ## User control levels206//207// chain-controlled - key is uncontrolled by user208//                    i.e autoincrementing index209//                    can use non-cryptographic hash210// real - key is controlled by user211//        but it is hard to generate enough colliding values, i.e owner of signed txs212//        can use non-cryptographic hash213// controlled - key is completly controlled by users214//              i.e maps with mutable keys215//              should use cryptographic hash216//217// ## User control level downgrade reasons218//219// ?1 - chain-controlled -> controlled220//      collections/tokens can be destroyed, resulting in massive holes221// ?2 - chain-controlled -> controlled222//      same as ?1, but can be only added, resulting in easier exploitation223// ?3 - real -> controlled224//      no confirmation required, so addresses can be easily generated225decl_storage! {226	trait Store for Module<T: Config> as Unique {227228		//#region Private members229		/// Used for migrations230		ChainVersion: u64;231		//#endregion232233		//#region Tokens transfer rate limit baskets234		/// (Collection id (controlled?2), who created (real))235		/// TODO: Off chain worker should remove from this map when collection gets removed236		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;237		/// Collection id (controlled?2), token id (controlled?2)238		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;239		/// Collection id (controlled?2), owning user (real)240		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;241		/// Collection id (controlled?2), token id (controlled?2)242		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>;243		//#endregion244245		/// Variable metadata sponsoring246		/// Collection id (controlled?2), token id (controlled?2)247		#[deprecated]248		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;249		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;250251		/// Approval sponsoring252		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;253		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;254		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>;255	}256}257258decl_module! {259	pub struct Module<T: Config> for enum Call260	where261		origin: T::Origin262	{263		type Error = Error<T>;264265		fn deposit_event() = default;266267		fn on_initialize(_now: T::BlockNumber) -> Weight {268			0269		}270271		fn on_runtime_upgrade() -> Weight {272			let limit = None;273274			<VariableMetaDataBasket<T>>::remove_all(limit);275276			0277		}278279		/// 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.280		///281		/// # Permissions282		///283		/// * Anyone.284		///285		/// # Arguments286		///287		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.288		///289		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.290		///291		/// * token_prefix: UTF-8 string with token prefix.292		///293		/// * mode: [CollectionMode] collection type and type dependent data.294		// returns collection ID295		#[weight = <SelfWeightOf<T>>::create_collection()]296		#[transactional]297		#[deprecated]298		pub fn create_collection(origin,299								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,300								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,301								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,302								 mode: CollectionMode) -> DispatchResult  {303			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {304				name: collection_name,305				description: collection_description,306				token_prefix,307				mode,308				..Default::default()309			};310			Self::create_collection_ex(origin, data)311		}312313		/// This method creates a collection314		///315		/// Prefer it to deprecated [`created_collection`] method316		#[weight = <SelfWeightOf<T>>::create_collection()]317		#[transactional]318		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {319			let sender = ensure_signed(origin)?;320321			// =========322323			T::CollectionDispatch::create(sender, data)?;324325			Ok(())326		}327328		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.329		///330		/// # Permissions331		///332		/// * Collection Owner.333		///334		/// # Arguments335		///336		/// * collection_id: collection to destroy.337		#[weight = <SelfWeightOf<T>>::destroy_collection()]338		#[transactional]339		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {340			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);341			let collection = <CollectionHandle<T>>::try_get(collection_id)?;342343			// =========344345			T::CollectionDispatch::destroy(sender, collection)?;346347			<NftTransferBasket<T>>::remove_prefix(collection_id, None);348			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);349			<ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);350351			<NftApproveBasket<T>>::remove_prefix(collection_id, None);352			<FungibleApproveBasket<T>>::remove_prefix(collection_id, None);353			<RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);354355			Ok(())356		}357358		/// Add an address to allow list.359		///360		/// # Permissions361		///362		/// * Collection Owner363		/// * Collection Admin364		///365		/// # Arguments366		///367		/// * collection_id.368		///369		/// * address.370		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]371		#[transactional]372		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{373374			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);375			let collection = <CollectionHandle<T>>::try_get(collection_id)?;376377			<PalletCommon<T>>::toggle_allowlist(378				&collection,379				&sender,380				&address,381				true,382			)?;383384			Self::deposit_event(Event::<T>::AllowListAddressAdded(385				collection_id,386				address387			));388389			Ok(())390		}391392		/// Remove an address from allow list.393		///394		/// # Permissions395		///396		/// * Collection Owner397		/// * Collection Admin398		///399		/// # Arguments400		///401		/// * collection_id.402		///403		/// * address.404		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]405		#[transactional]406		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{407408			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);409			let collection = <CollectionHandle<T>>::try_get(collection_id)?;410411			<PalletCommon<T>>::toggle_allowlist(412				&collection,413				&sender,414				&address,415				false,416			)?;417418			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(419				collection_id,420				address421			));422423			Ok(())424		}425426		/// Change the owner of the collection.427		///428		/// # Permissions429		///430		/// * Collection Owner.431		///432		/// # Arguments433		///434		/// * collection_id.435		///436		/// * new_owner.437		#[weight = <SelfWeightOf<T>>::change_collection_owner()]438		#[transactional]439		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {440441			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);442443			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;444			target_collection.check_is_owner(&sender)?;445446			target_collection.owner = new_owner.clone();447			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(448				collection_id,449				new_owner450			));451452			target_collection.save()453		}454455		/// Adds an admin of the Collection.456		/// 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.457		///458		/// # Permissions459		///460		/// * Collection Owner.461		/// * Collection Admin.462		///463		/// # Arguments464		///465		/// * collection_id: ID of the Collection to add admin for.466		///467		/// * new_admin_id: Address of new admin to add.468		#[weight = <SelfWeightOf<T>>::add_collection_admin()]469		#[transactional]470		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {471			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);472			let collection = <CollectionHandle<T>>::try_get(collection_id)?;473474			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(475				collection_id,476				new_admin_id.clone()477			));478479			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)480		}481482		/// 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.483		///484		/// # Permissions485		///486		/// * Collection Owner.487		/// * Collection Admin.488		///489		/// # Arguments490		///491		/// * collection_id: ID of the Collection to remove admin for.492		///493		/// * account_id: Address of admin to remove.494		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]495		#[transactional]496		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {497			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);498			let collection = <CollectionHandle<T>>::try_get(collection_id)?;499500			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(501				collection_id,502				account_id.clone()503			));504505			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)506		}507508		/// # Permissions509		///510		/// * Collection Owner511		///512		/// # Arguments513		///514		/// * collection_id.515		///516		/// * new_sponsor.517		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]518		#[transactional]519		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {520			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);521522			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;523			target_collection.check_is_owner(&sender)?;524525			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());526527			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(528				collection_id,529				new_sponsor530			));531532			target_collection.save()533		}534535		/// # Permissions536		///537		/// * Sponsor.538		///539		/// # Arguments540		///541		/// * collection_id.542		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]543		#[transactional]544		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {545			let sender = ensure_signed(origin)?;546547			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;548			ensure!(549				target_collection.sponsorship.pending_sponsor() == Some(&sender),550				Error::<T>::ConfirmUnsetSponsorFail551			);552553			target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());554555			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(556				collection_id,557				sender558			));559560			target_collection.save()561		}562563		/// Switch back to pay-per-own-transaction model.564		///565		/// # Permissions566		///567		/// * Collection owner.568		///569		/// # Arguments570		///571		/// * collection_id.572		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]573		#[transactional]574		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {575			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);576577			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;578			target_collection.check_is_owner(&sender)?;579580			target_collection.sponsorship = SponsorshipState::Disabled;581582			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(583				collection_id584			));585			target_collection.save()586		}587588		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.589		///590		/// # Permissions591		///592		/// * Collection Owner.593		/// * Collection Admin.594		/// * Anyone if595		///     * Allow List is enabled, and596		///     * Address is added to allow list, and597		///     * MintPermission is enabled (see SetMintPermission method)598		///599		/// # Arguments600		///601		/// * collection_id: ID of the collection.602		///603		/// * owner: Address, initial owner of the NFT.604		///605		/// * data: Token data to store on chain.606		#[weight = T::CommonWeightInfo::create_item()]607		#[transactional]608		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {609			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);610			let budget = budget::Value::new(NESTING_BUDGET);611612			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))613		}614615		/// This method creates multiple items in a collection created with CreateCollection method.616		///617		/// # Permissions618		///619		/// * Collection Owner.620		/// * Collection Admin.621		/// * Anyone if622		///     * Allow List is enabled, and623		///     * Address is added to allow list, and624		///     * MintPermission is enabled (see SetMintPermission method)625		///626		/// # Arguments627		///628		/// * collection_id: ID of the collection.629		///630		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].631		///632		/// * owner: Address, initial owner of the NFT.633		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]634		#[transactional]635		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {636			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);637			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);638			let budget = budget::Value::new(NESTING_BUDGET);639640			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))641		}642643		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]644		#[transactional]645		pub fn set_collection_properties(646			origin,647			collection_id: CollectionId,648			properties: Vec<Property>649		) -> DispatchResultWithPostInfo {650			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);651652			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);653654			dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))655		}656657		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]658		#[transactional]659		pub fn delete_collection_properties(660			origin,661			collection_id: CollectionId,662			property_keys: Vec<PropertyKey>,663		) -> DispatchResultWithPostInfo {664			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);665666			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);667668			dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))669		}670671		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]672		#[transactional]673		pub fn set_token_properties(674			origin,675			collection_id: CollectionId,676			token_id: TokenId,677			properties: Vec<Property>678		) -> DispatchResultWithPostInfo {679			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);680681			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);682683			dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))684		}685686		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]687		#[transactional]688		pub fn delete_token_properties(689			origin,690			collection_id: CollectionId,691			token_id: TokenId,692			property_keys: Vec<PropertyKey>693		) -> DispatchResultWithPostInfo {694			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);695696			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);697698			dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))699		}700701		#[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]702		#[transactional]703		pub fn set_property_permissions(704			origin,705			collection_id: CollectionId,706			property_permissions: Vec<PropertyKeyPermission>,707		) -> DispatchResultWithPostInfo {708			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);709710			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);711712			dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))713		}714715		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]716		#[transactional]717		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {718			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);719			let budget = budget::Value::new(NESTING_BUDGET);720721			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))722		}723724		// TODO! transaction weight725726		/// Set transfers_enabled value for particular collection727		///728		/// # Permissions729		///730		/// * Collection Owner.731		///732		/// # Arguments733		///734		/// * collection_id: ID of the collection.735		///736		/// * value: New flag value.737		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]738		#[transactional]739		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {740			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);741			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;742			target_collection.check_is_owner(&sender)?;743744			// =========745746			target_collection.limits.transfers_enabled = Some(value);747			target_collection.save()748		}749750		/// Destroys a concrete instance of NFT.751		///752		/// # Permissions753		///754		/// * Collection Owner.755		/// * Collection Admin.756		/// * Current NFT Owner.757		///758		/// # Arguments759		///760		/// * collection_id: ID of the collection.761		///762		/// * item_id: ID of NFT to burn.763		#[weight = T::CommonWeightInfo::burn_item()]764		#[transactional]765		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {766			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);767768			let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;769			if value == 1 {770				<NftTransferBasket<T>>::remove(collection_id, item_id);771				<NftApproveBasket<T>>::remove(collection_id, item_id);772			}773			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?774			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());775			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));776			Ok(post_info)777		}778779		/// Destroys a concrete instance of NFT on behalf of the owner780		/// See also: [`approve`]781		///782		/// # Permissions783		///784		/// * Collection Owner.785		/// * Collection Admin.786		/// * Current NFT Owner.787		///788		/// # Arguments789		///790		/// * collection_id: ID of the collection.791		///792		/// * item_id: ID of NFT to burn.793		///794		/// * from: owner of item795		#[weight = T::CommonWeightInfo::burn_from()]796		#[transactional]797		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {798			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);799			let budget = budget::Value::new(NESTING_BUDGET);800801			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))802		}803804		/// Change ownership of the token.805		///806		/// # Permissions807		///808		/// * Collection Owner809		/// * Collection Admin810		/// * Current NFT owner811		///812		/// # Arguments813		///814		/// * recipient: Address of token recipient.815		///816		/// * collection_id.817		///818		/// * item_id: ID of the item819		///     * Non-Fungible Mode: Required.820		///     * Fungible Mode: Ignored.821		///     * Re-Fungible Mode: Required.822		///823		/// * value: Amount to transfer.824		///     * Non-Fungible Mode: Ignored825		///     * Fungible Mode: Must specify transferred amount826		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)827		#[weight = T::CommonWeightInfo::transfer()]828		#[transactional]829		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {830			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);831			let budget = budget::Value::new(NESTING_BUDGET);832833			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))834		}835836		/// Set, change, or remove approved address to transfer the ownership of the NFT.837		///838		/// # Permissions839		///840		/// * Collection Owner841		/// * Collection Admin842		/// * Current NFT owner843		///844		/// # Arguments845		///846		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).847		///848		/// * collection_id.849		///850		/// * item_id: ID of the item.851		#[weight = T::CommonWeightInfo::approve()]852		#[transactional]853		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {854			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);855856			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))857		}858859		/// 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.860		///861		/// # Permissions862		/// * Collection Owner863		/// * Collection Admin864		/// * Current NFT owner865		/// * Address approved by current NFT owner866		///867		/// # Arguments868		///869		/// * from: Address that owns token.870		///871		/// * recipient: Address of token recipient.872		///873		/// * collection_id.874		///875		/// * item_id: ID of the item.876		///877		/// * value: Amount to transfer.878		#[weight = T::CommonWeightInfo::transfer_from()]879		#[transactional]880		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {881			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);882			let budget = budget::Value::new(NESTING_BUDGET);883884			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))885		}886887		#[weight = <SelfWeightOf<T>>::set_collection_limits()]888		#[transactional]889		pub fn set_collection_limits(890			origin,891			collection_id: CollectionId,892			new_limit: CollectionLimits,893		) -> DispatchResult {894			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);895			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;896			target_collection.check_is_owner(&sender)?;897			let old_limit = &target_collection.limits;898899			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;900901			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(902				collection_id903			));904905			target_collection.save()906		}907908		#[weight = <SelfWeightOf<T>>::set_collection_limits()]909		#[transactional]910		pub fn set_collection_permissions(911			origin,912			collection_id: CollectionId,913			new_limit: CollectionPermissions,914		) -> DispatchResult {915			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);916			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;917			target_collection.check_is_owner(&sender)?;918			let old_limit = &target_collection.permissions;919920			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;921922			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(923				collection_id924			));925926			target_collection.save()927		}928	}929}