git.delta.rocks / unique-network / refs/commits / 98013fc81aa8

difftreelog

feat check nesting rule

Yaroslav Bolyukin2022-04-07parent: #c31b5ba.patch.diff
in: master

10 files changed

modifiedpallets/common/src/eth.rsdiffbeforeafterboth
--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-use up_data_structs::{CollectionId, TokenId};
+use up_data_structs::CollectionId;
 use sp_core::H160;
 
 // 0x17c4e6453Cc49AAAaEACA894e6D9683e00000001 - collection 1
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -345,6 +345,13 @@
 
 		/// Not sufficient founds to perform action
 		NotSufficientFounds,
+
+		/// Collection has nesting disabled
+		NestingIsDisabled,
+		/// Only owner may nest tokens under this collection
+		OnlyOwnerAllowedToNest,
+		/// Only tokens from specific collections may nest tokens under this
+		SourceCollectionIsNotAllowedToNest,
 	}
 
 	#[pallet::storage]
@@ -400,8 +407,6 @@
 	#[pallet::hooks]
 	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
 		fn on_runtime_upgrade() -> Weight {
-			let mut weight = 0;
-
 			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {
 				use up_data_structs::{CollectionVersion1, CollectionVersion2};
 				<CollectionById<T>>::translate_values::<CollectionVersion1<T::AccountId>, _>(|v| {
@@ -409,7 +414,7 @@
 				});
 			}
 
-			weight
+			0
 		}
 	}
 }
@@ -774,6 +779,13 @@
 		data: BoundedVec<u8, CustomDataLimit>,
 	) -> DispatchResultWithPostInfo;
 
+	fn nest_token(
+		&self,
+		sender: T::CrossAccountId,
+		from: (CollectionId, TokenId),
+		under: TokenId,
+	) -> DispatchResult;
+
 	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;
 	fn token_exists(&self, token: TokenId) -> bool;
 	fn last_token_id(&self) -> TokenId;
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -228,6 +228,15 @@
 		fail!(<Error<T>>::FungibleItemsDontHaveData)
 	}
 
+	fn nest_token(
+		&self,
+		_sender: <T>::CrossAccountId,
+		_from: (up_data_structs::CollectionId, TokenId),
+		_under: TokenId,
+	) -> sp_runtime::DispatchResult {
+		fail!(<Error<T>>::FungibleDisallowsNesting)
+	}
+
 	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {
 		if <Balance<T>>::get((self.id, account)) != 0 {
 			vec![TokenId::default()]
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -18,9 +18,14 @@
 
 use core::ops::Deref;
 use frame_support::{ensure};
-use up_data_structs::{AccessMode, CollectionId, TokenId, CreateCollectionData};
-use pallet_common::{Error as CommonError, Event as CommonEvent, Pallet as PalletCommon};
 use pallet_evm::account::CrossAccountId;
+use up_data_structs::{
+	AccessMode, CollectionId, TokenId, CreateCollectionData, mapping::TokenAddressMapping,
+};
+use pallet_common::{
+	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,
+	CollectionHandle, dispatch::CollectionDispatch,
+};
 use pallet_evm_coder_substrate::WithRecorder;
 use sp_core::H160;
 use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
@@ -52,6 +57,8 @@
 		FungibleItemsHaveNoId,
 		/// Tried to set data for fungible item
 		FungibleItemsDontHaveData,
+		/// Fungible token does not support nested
+		FungibleDisallowsNesting,
 	}
 
 	#[pallet::config]
@@ -207,7 +214,15 @@
 			None
 		};
 
-		// =========
+		if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {
+			let handle = <CollectionHandle<T>>::try_get(target.0)?;
+			let dispatch = T::CollectionDispatch::dispatch(handle);
+			let dispatch = dispatch.as_dyn();
+
+			// =========
+
+			dispatch.nest_token(from.clone(), (collection.id, TokenId::default()), target.1)?;
+		}
 
 		if let Some(balance_to) = balance_to {
 			// from != to
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -17,7 +17,7 @@
 use core::marker::PhantomData;
 
 use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, BoundedVec};
-use up_data_structs::{TokenId, CustomDataLimit, CreateItemExData};
+use up_data_structs::{TokenId, CustomDataLimit, CreateItemExData, CollectionId};
 use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
 use sp_runtime::DispatchError;
 use sp_std::vec::Vec;
@@ -237,6 +237,15 @@
 		)
 	}
 
+	fn nest_token(
+		&self,
+		sender: T::CrossAccountId,
+		(from, _): (CollectionId, TokenId),
+		under: TokenId,
+	) -> sp_runtime::DispatchResult {
+		<Pallet<T>>::nest_token(self, sender, from, under)
+	}
+
 	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {
 		<Owned<T>>::iter_prefix((self.id, account))
 			.map(|(id, _)| id)
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -17,12 +17,16 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
 use erc::ERC721Events;
-use frame_support::{BoundedVec, ensure};
+use frame_support::{BoundedVec, ensure, fail};
 use up_data_structs::{
 	AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,
+	mapping::TokenAddressMapping, NestingRule,
 };
-use pallet_common::{Error as CommonError, Pallet as PalletCommon, Event as CommonEvent};
 use pallet_evm::account::CrossAccountId;
+use pallet_common::{
+	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent,
+	CollectionHandle, dispatch::CollectionDispatch,
+};
 use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
 use sp_core::H160;
 use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
@@ -282,8 +286,16 @@
 			None
 		};
 
-		// =========
+		if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {
+			let handle = <CollectionHandle<T>>::try_get(target.0)?;
+			let dispatch = T::CollectionDispatch::dispatch(handle);
+			let dispatch = dispatch.as_dyn();
+
+			// =========
 
+			dispatch.nest_token(from.clone(), (collection.id, token), target.1)?;
+		}
+
 		<TokenData<T>>::insert(
 			(collection.id, token),
 			ItemData {
@@ -567,6 +579,40 @@
 		Ok(())
 	}
 
+	pub fn nest_token(
+		handle: &NonfungibleHandle<T>,
+		sender: T::CrossAccountId,
+		from: CollectionId,
+		under: TokenId,
+	) -> DispatchResult {
+		fn ensure_sender_allowed<T: Config>(
+			collection: CollectionId,
+			token: TokenId,
+			sender: T::CrossAccountId,
+		) -> DispatchResult {
+			ensure!(
+				<TokenData<T>>::get((collection, token))
+					.ok_or(<CommonError<T>>::TokenNotFound)?
+					.owner
+					.conv_eq(&sender),
+				<CommonError<T>>::OnlyOwnerAllowedToNest,
+			);
+			Ok(())
+		}
+		match handle.limits.nesting_rule() {
+			NestingRule::Disabled => fail!(<CommonError<T>>::NestingIsDisabled),
+			NestingRule::Owner => ensure_sender_allowed::<T>(from, under, sender)?,
+			NestingRule::OwnerRestricted(whitelist) => {
+				ensure!(
+					whitelist.contains(&from),
+					<CommonError<T>>::SourceCollectionIsNotAllowedToNest
+				);
+				ensure_sender_allowed::<T>(from, under, sender)?
+			}
+		}
+		Ok(())
+	}
+
 	/// Delegated to `create_multiple_items`
 	pub fn create_item(
 		collection: &NonfungibleHandle<T>,
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -243,6 +243,15 @@
 		)
 	}
 
+	fn nest_token(
+		&self,
+		_sender: <T>::CrossAccountId,
+		_from: (up_data_structs::CollectionId, TokenId),
+		_under: TokenId,
+	) -> sp_runtime::DispatchResult {
+		fail!(<Error<T>>::RefungibleDisallowsNesting)
+	}
+
 	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {
 		<Owned<T>>::iter_prefix((self.id, account))
 			.map(|(id, _)| id)
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
before · pallets/refungible/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#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{ensure, BoundedVec};20use up_data_structs::{21	AccessMode, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId,22	CreateCollectionData, CreateRefungibleExData,23};24use pallet_common::{Error as CommonError, Event as CommonEvent, Pallet as PalletCommon};25use pallet_evm::account::CrossAccountId;26use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};27use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};28use core::ops::Deref;29use codec::{Encode, Decode, MaxEncodedLen};30use scale_info::TypeInfo;3132pub use pallet::*;33#[cfg(feature = "runtime-benchmarks")]34pub mod benchmarking;35pub mod common;36pub mod erc;37pub mod weights;38pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;3940#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]41pub struct ItemData {42	pub const_data: BoundedVec<u8, CustomDataLimit>,43	pub variable_data: BoundedVec<u8, CustomDataLimit>,44}4546#[frame_support::pallet]47pub mod pallet {48	use super::*;49	use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};50	use up_data_structs::{CollectionId, TokenId};51	use super::weights::WeightInfo;5253	#[pallet::error]54	pub enum Error<T> {55		/// Not Refungible item data used to mint in Refungible collection.56		NotRefungibleDataUsedToMintFungibleCollectionToken,57		/// Maximum refungibility exceeded58		WrongRefungiblePieces,59	}6061	#[pallet::config]62	pub trait Config: frame_system::Config + pallet_common::Config {63		type WeightInfo: WeightInfo;64	}6566	#[pallet::pallet]67	#[pallet::generate_store(pub(super) trait Store)]68	pub struct Pallet<T>(_);6970	#[pallet::storage]71	pub type TokensMinted<T: Config> =72		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;73	#[pallet::storage]74	pub type TokensBurnt<T: Config> =75		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;7677	#[pallet::storage]78	pub type TokenData<T: Config> = StorageNMap<79		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),80		Value = ItemData,81		QueryKind = ValueQuery,82	>;8384	#[pallet::storage]85	pub type TotalSupply<T: Config> = StorageNMap<86		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),87		Value = u128,88		QueryKind = ValueQuery,89	>;9091	/// Used to enumerate tokens owned by account92	#[pallet::storage]93	pub type Owned<T: Config> = StorageNMap<94		Key = (95			Key<Twox64Concat, CollectionId>,96			Key<Blake2_128Concat, T::CrossAccountId>,97			Key<Twox64Concat, TokenId>,98		),99		Value = bool,100		QueryKind = ValueQuery,101	>;102103	#[pallet::storage]104	pub type AccountBalance<T: Config> = StorageNMap<105		Key = (106			Key<Twox64Concat, CollectionId>,107			// Owner108			Key<Blake2_128Concat, T::CrossAccountId>,109		),110		Value = u32,111		QueryKind = ValueQuery,112	>;113114	#[pallet::storage]115	pub type Balance<T: Config> = StorageNMap<116		Key = (117			Key<Twox64Concat, CollectionId>,118			Key<Twox64Concat, TokenId>,119			// Owner120			Key<Blake2_128Concat, T::CrossAccountId>,121		),122		Value = u128,123		QueryKind = ValueQuery,124	>;125126	#[pallet::storage]127	pub type Allowance<T: Config> = StorageNMap<128		Key = (129			Key<Twox64Concat, CollectionId>,130			Key<Twox64Concat, TokenId>,131			// Owner132			Key<Blake2_128, T::CrossAccountId>,133			// Spender134			Key<Blake2_128Concat, T::CrossAccountId>,135		),136		Value = u128,137		QueryKind = ValueQuery,138	>;139}140141pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);142impl<T: Config> RefungibleHandle<T> {143	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {144		Self(inner)145	}146	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {147		self.0148	}149}150impl<T: Config> Deref for RefungibleHandle<T> {151	type Target = pallet_common::CollectionHandle<T>;152153	fn deref(&self) -> &Self::Target {154		&self.0155	}156}157158impl<T: Config> Pallet<T> {159	pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {160		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)161	}162	pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {163		<TotalSupply<T>>::contains_key((collection.id, token))164	}165}166167// unchecked calls skips any permission checks168impl<T: Config> Pallet<T> {169	pub fn init_collection(170		owner: T::AccountId,171		data: CreateCollectionData<T::AccountId>,172	) -> Result<CollectionId, DispatchError> {173		<PalletCommon<T>>::init_collection(owner, data)174	}175	pub fn destroy_collection(176		collection: RefungibleHandle<T>,177		sender: &T::CrossAccountId,178	) -> DispatchResult {179		let id = collection.id;180181		// =========182183		PalletCommon::destroy_collection(collection.0, sender)?;184185		<TokensMinted<T>>::remove(id);186		<TokensBurnt<T>>::remove(id);187		<TokenData<T>>::remove_prefix((id,), None);188		<TotalSupply<T>>::remove_prefix((id,), None);189		<Balance<T>>::remove_prefix((id,), None);190		<Allowance<T>>::remove_prefix((id,), None);191		<Owned<T>>::remove_prefix((id,), None);192		<AccountBalance<T>>::remove_prefix((id,), None);193		Ok(())194	}195196	pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {197		let burnt = <TokensBurnt<T>>::get(collection.id)198			.checked_add(1)199			.ok_or(ArithmeticError::Overflow)?;200201		<TokensBurnt<T>>::insert(collection.id, burnt);202		<TokenData<T>>::remove((collection.id, token_id));203		<TotalSupply<T>>::remove((collection.id, token_id));204		<Balance<T>>::remove_prefix((collection.id, token_id), None);205		<Allowance<T>>::remove_prefix((collection.id, token_id), None);206		// TODO: ERC721 transfer event207		Ok(())208	}209210	pub fn burn(211		collection: &RefungibleHandle<T>,212		owner: &T::CrossAccountId,213		token: TokenId,214		amount: u128,215	) -> DispatchResult {216		let total_supply = <TotalSupply<T>>::get((collection.id, token))217			.checked_sub(amount)218			.ok_or(<CommonError<T>>::TokenValueTooLow)?;219220		// This was probally last owner of this token?221		if total_supply == 0 {222			// Ensure user actually owns this amount223			ensure!(224				<Balance<T>>::get((collection.id, token, owner)) == amount,225				<CommonError<T>>::TokenValueTooLow226			);227			let account_balance = <AccountBalance<T>>::get((collection.id, owner))228				.checked_sub(1)229				// Should not occur230				.ok_or(ArithmeticError::Underflow)?;231232			// =========233234			<Owned<T>>::remove((collection.id, owner, token));235			<AccountBalance<T>>::insert((collection.id, owner), account_balance);236			Self::burn_token(collection, token)?;237			<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(238				collection.id,239				token,240				owner.clone(),241				amount,242			));243			return Ok(());244		}245246		let balance = <Balance<T>>::get((collection.id, token, owner))247			.checked_sub(amount)248			.ok_or(<CommonError<T>>::TokenValueTooLow)?;249		let account_balance = if balance == 0 {250			<AccountBalance<T>>::get((collection.id, owner))251				.checked_sub(1)252				// Should not occur253				.ok_or(ArithmeticError::Underflow)?254		} else {255			0256		};257258		// =========259260		if balance == 0 {261			<Owned<T>>::remove((collection.id, owner, token));262			<Balance<T>>::remove((collection.id, token, owner));263			<AccountBalance<T>>::insert((collection.id, owner), account_balance);264		} else {265			<Balance<T>>::insert((collection.id, token, owner), balance);266		}267		<TotalSupply<T>>::insert((collection.id, token), total_supply);268		// TODO: ERC20 transfer event269		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(270			collection.id,271			token,272			owner.clone(),273			amount,274		));275		Ok(())276	}277278	pub fn transfer(279		collection: &RefungibleHandle<T>,280		from: &T::CrossAccountId,281		to: &T::CrossAccountId,282		token: TokenId,283		amount: u128,284	) -> DispatchResult {285		ensure!(286			collection.limits.transfers_enabled(),287			<CommonError<T>>::TransferNotAllowed288		);289290		if collection.access == AccessMode::AllowList {291			collection.check_allowlist(from)?;292			collection.check_allowlist(to)?;293		}294		<PalletCommon<T>>::ensure_correct_receiver(to)?;295296		let balance_from = <Balance<T>>::get((collection.id, token, from))297			.checked_sub(amount)298			.ok_or(<CommonError<T>>::TokenValueTooLow)?;299		let mut create_target = false;300		let from_to_differ = from != to;301		let balance_to = if from != to {302			let old_balance = <Balance<T>>::get((collection.id, token, to));303			if old_balance == 0 {304				create_target = true;305			}306			Some(307				old_balance308					.checked_add(amount)309					.ok_or(ArithmeticError::Overflow)?,310			)311		} else {312			None313		};314315		let account_balance_from = if balance_from == 0 {316			Some(317				<AccountBalance<T>>::get((collection.id, from))318					.checked_sub(1)319					// Should not occur320					.ok_or(ArithmeticError::Underflow)?,321			)322		} else {323			None324		};325		// Account data is created in token, AccountBalance should be increased326		// But only if from != to as we shouldn't check overflow in this case327		let account_balance_to = if create_target && from_to_differ {328			let account_balance_to = <AccountBalance<T>>::get((collection.id, to))329				.checked_add(1)330				.ok_or(ArithmeticError::Overflow)?;331			ensure!(332				account_balance_to < collection.limits.account_token_ownership_limit(),333				<CommonError<T>>::AccountTokenLimitExceeded,334			);335336			Some(account_balance_to)337		} else {338			None339		};340341		// =========342343		if let Some(balance_to) = balance_to {344			// from != to345			if balance_from == 0 {346				<Balance<T>>::remove((collection.id, token, from));347			} else {348				<Balance<T>>::insert((collection.id, token, from), balance_from);349			}350			<Balance<T>>::insert((collection.id, token, to), balance_to);351			if let Some(account_balance_from) = account_balance_from {352				<AccountBalance<T>>::insert((collection.id, from), account_balance_from);353				<Owned<T>>::remove((collection.id, from, token));354			}355			if let Some(account_balance_to) = account_balance_to {356				<AccountBalance<T>>::insert((collection.id, to), account_balance_to);357				<Owned<T>>::insert((collection.id, to, token), true);358			}359		}360361		// TODO: ERC20 transfer event362		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(363			collection.id,364			token,365			from.clone(),366			to.clone(),367			amount,368		));369		Ok(())370	}371372	pub fn create_multiple_items(373		collection: &RefungibleHandle<T>,374		sender: &T::CrossAccountId,375		data: Vec<CreateRefungibleExData<T::CrossAccountId>>,376	) -> DispatchResult {377		if !collection.is_owner_or_admin(sender) {378			ensure!(379				collection.mint_mode,380				<CommonError<T>>::PublicMintingNotAllowed381			);382			collection.check_allowlist(sender)?;383384			for item in data.iter() {385				for user in item.users.keys() {386					collection.check_allowlist(user)?;387				}388			}389		}390391		for item in data.iter() {392			for (owner, _) in item.users.iter() {393				<PalletCommon<T>>::ensure_correct_receiver(owner)?;394			}395		}396397		// Total pieces per tokens398		let totals = data399			.iter()400			.map(|data| {401				Ok(data402					.users403					.iter()404					.map(|u| u.1)405					.try_fold(0u128, |acc, v| acc.checked_add(*v))406					.ok_or(ArithmeticError::Overflow)?)407			})408			.collect::<Result<Vec<_>, DispatchError>>()?;409		for total in &totals {410			ensure!(411				*total <= MAX_REFUNGIBLE_PIECES,412				<Error<T>>::WrongRefungiblePieces413			);414		}415416		let first_token_id = <TokensMinted<T>>::get(collection.id);417		let tokens_minted = first_token_id418			.checked_add(data.len() as u32)419			.ok_or(ArithmeticError::Overflow)?;420		ensure!(421			tokens_minted < collection.limits.token_limit(),422			<CommonError<T>>::CollectionTokenLimitExceeded423		);424425		let mut balances = BTreeMap::new();426		for data in &data {427			for owner in data.users.keys() {428				let balance = balances429					.entry(owner)430					.or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));431				*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;432433				ensure!(434					*balance <= collection.limits.account_token_ownership_limit(),435					<CommonError<T>>::AccountTokenLimitExceeded,436				);437			}438		}439440		// =========441442		<TokensMinted<T>>::insert(collection.id, tokens_minted);443		for (account, balance) in balances {444			<AccountBalance<T>>::insert((collection.id, account), balance);445		}446		for (i, token) in data.into_iter().enumerate() {447			let token_id = first_token_id + i as u32 + 1;448			<TotalSupply<T>>::insert((collection.id, token_id), totals[i]);449450			<TokenData<T>>::insert(451				(collection.id, token_id),452				ItemData {453					const_data: token.const_data,454					variable_data: token.variable_data,455				},456			);457			for (user, amount) in token.users.into_iter() {458				if amount == 0 {459					continue;460				}461				<Balance<T>>::insert((collection.id, token_id, &user), amount);462				<Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);463				// TODO: ERC20 transfer event464				<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(465					collection.id,466					TokenId(token_id),467					user,468					amount,469				));470			}471		}472		Ok(())473	}474475	pub fn set_allowance_unchecked(476		collection: &RefungibleHandle<T>,477		sender: &T::CrossAccountId,478		spender: &T::CrossAccountId,479		token: TokenId,480		amount: u128,481	) {482		if amount == 0 {483			<Allowance<T>>::remove((collection.id, token, sender, spender));484		} else {485			<Allowance<T>>::insert((collection.id, token, sender, spender), amount);486		}487		// TODO: ERC20 approval event488		<PalletCommon<T>>::deposit_event(CommonEvent::Approved(489			collection.id,490			token,491			sender.clone(),492			spender.clone(),493			amount,494		))495	}496497	pub fn set_allowance(498		collection: &RefungibleHandle<T>,499		sender: &T::CrossAccountId,500		spender: &T::CrossAccountId,501		token: TokenId,502		amount: u128,503	) -> DispatchResult {504		if collection.access == AccessMode::AllowList {505			collection.check_allowlist(sender)?;506			collection.check_allowlist(spender)?;507		}508509		<PalletCommon<T>>::ensure_correct_receiver(spender)?;510511		if <Balance<T>>::get((collection.id, token, sender)) < amount {512			ensure!(513				collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),514				<CommonError<T>>::CantApproveMoreThanOwned515			);516		}517518		// =========519520		Self::set_allowance_unchecked(collection, sender, spender, token, amount);521		Ok(())522	}523524	pub fn transfer_from(525		collection: &RefungibleHandle<T>,526		spender: &T::CrossAccountId,527		from: &T::CrossAccountId,528		to: &T::CrossAccountId,529		token: TokenId,530		amount: u128,531	) -> DispatchResult {532		if spender.conv_eq(from) {533			return Self::transfer(collection, from, to, token, amount);534		}535		if collection.access == AccessMode::AllowList {536			// `from`, `to` checked in [`transfer`]537			collection.check_allowlist(spender)?;538		}539540		let allowance =541			<Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);542		if allowance.is_none() {543			ensure!(544				collection.ignores_allowance(spender),545				<CommonError<T>>::ApprovedValueTooLow546			);547		}548549		// =========550551		Self::transfer(collection, from, to, token, amount)?;552		if let Some(allowance) = allowance {553			Self::set_allowance_unchecked(collection, from, spender, token, allowance);554		}555		Ok(())556	}557558	pub fn burn_from(559		collection: &RefungibleHandle<T>,560		spender: &T::CrossAccountId,561		from: &T::CrossAccountId,562		token: TokenId,563		amount: u128,564	) -> DispatchResult {565		if spender.conv_eq(from) {566			return Self::burn(collection, from, token, amount);567		}568		if collection.access == AccessMode::AllowList {569			// `from` checked in [`burn`]570			collection.check_allowlist(spender)?;571		}572573		let allowance =574			<Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);575		if allowance.is_none() {576			ensure!(577				collection.ignores_allowance(spender),578				<CommonError<T>>::ApprovedValueTooLow579			);580		}581582		// =========583584		Self::burn(collection, from, token, amount)?;585		if let Some(allowance) = allowance {586			Self::set_allowance_unchecked(collection, from, spender, token, allowance);587		}588		Ok(())589	}590591	pub fn set_variable_metadata(592		collection: &RefungibleHandle<T>,593		sender: &T::CrossAccountId,594		token: TokenId,595		data: BoundedVec<u8, CustomDataLimit>,596	) -> DispatchResult {597		collection.check_can_update_meta(598			sender,599			&T::CrossAccountId::from_sub(collection.owner.clone()),600		)?;601602		let token_data = <TokenData<T>>::get((collection.id, token));603604		// =========605606		<TokenData<T>>::insert(607			(collection.id, token),608			ItemData {609				variable_data: data,610				..token_data611			},612		);613		Ok(())614	}615616	/// Delegated to `create_multiple_items`617	pub fn create_item(618		collection: &RefungibleHandle<T>,619		sender: &T::CrossAccountId,620		data: CreateRefungibleExData<T::CrossAccountId>,621	) -> DispatchResult {622		Self::create_multiple_items(collection, sender, vec![data])623	}624}
after · pallets/refungible/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#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{ensure, BoundedVec};20use up_data_structs::{21	AccessMode, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId,22	CreateCollectionData, CreateRefungibleExData, mapping::TokenAddressMapping,23};24use pallet_evm::account::CrossAccountId;25use pallet_common::{26	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,27	CollectionHandle, dispatch::CollectionDispatch,28};29use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};30use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};31use core::ops::Deref;32use codec::{Encode, Decode, MaxEncodedLen};33use scale_info::TypeInfo;3435pub use pallet::*;36#[cfg(feature = "runtime-benchmarks")]37pub mod benchmarking;38pub mod common;39pub mod erc;40pub mod weights;41pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;4243#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]44pub struct ItemData {45	pub const_data: BoundedVec<u8, CustomDataLimit>,46	pub variable_data: BoundedVec<u8, CustomDataLimit>,47}4849#[frame_support::pallet]50pub mod pallet {51	use super::*;52	use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};53	use up_data_structs::{CollectionId, TokenId};54	use super::weights::WeightInfo;5556	#[pallet::error]57	pub enum Error<T> {58		/// Not Refungible item data used to mint in Refungible collection.59		NotRefungibleDataUsedToMintFungibleCollectionToken,60		/// Maximum refungibility exceeded61		WrongRefungiblePieces,62		/// Refungible token can't nest other tokens63		RefungibleDisallowsNesting,64	}6566	#[pallet::config]67	pub trait Config: frame_system::Config + pallet_common::Config {68		type WeightInfo: WeightInfo;69	}7071	#[pallet::pallet]72	#[pallet::generate_store(pub(super) trait Store)]73	pub struct Pallet<T>(_);7475	#[pallet::storage]76	pub type TokensMinted<T: Config> =77		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;78	#[pallet::storage]79	pub type TokensBurnt<T: Config> =80		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;8182	#[pallet::storage]83	pub type TokenData<T: Config> = StorageNMap<84		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),85		Value = ItemData,86		QueryKind = ValueQuery,87	>;8889	#[pallet::storage]90	pub type TotalSupply<T: Config> = StorageNMap<91		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),92		Value = u128,93		QueryKind = ValueQuery,94	>;9596	/// Used to enumerate tokens owned by account97	#[pallet::storage]98	pub type Owned<T: Config> = StorageNMap<99		Key = (100			Key<Twox64Concat, CollectionId>,101			Key<Blake2_128Concat, T::CrossAccountId>,102			Key<Twox64Concat, TokenId>,103		),104		Value = bool,105		QueryKind = ValueQuery,106	>;107108	#[pallet::storage]109	pub type AccountBalance<T: Config> = StorageNMap<110		Key = (111			Key<Twox64Concat, CollectionId>,112			// Owner113			Key<Blake2_128Concat, T::CrossAccountId>,114		),115		Value = u32,116		QueryKind = ValueQuery,117	>;118119	#[pallet::storage]120	pub type Balance<T: Config> = StorageNMap<121		Key = (122			Key<Twox64Concat, CollectionId>,123			Key<Twox64Concat, TokenId>,124			// Owner125			Key<Blake2_128Concat, T::CrossAccountId>,126		),127		Value = u128,128		QueryKind = ValueQuery,129	>;130131	#[pallet::storage]132	pub type Allowance<T: Config> = StorageNMap<133		Key = (134			Key<Twox64Concat, CollectionId>,135			Key<Twox64Concat, TokenId>,136			// Owner137			Key<Blake2_128, T::CrossAccountId>,138			// Spender139			Key<Blake2_128Concat, T::CrossAccountId>,140		),141		Value = u128,142		QueryKind = ValueQuery,143	>;144}145146pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);147impl<T: Config> RefungibleHandle<T> {148	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {149		Self(inner)150	}151	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {152		self.0153	}154}155impl<T: Config> Deref for RefungibleHandle<T> {156	type Target = pallet_common::CollectionHandle<T>;157158	fn deref(&self) -> &Self::Target {159		&self.0160	}161}162163impl<T: Config> Pallet<T> {164	pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {165		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)166	}167	pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {168		<TotalSupply<T>>::contains_key((collection.id, token))169	}170}171172// unchecked calls skips any permission checks173impl<T: Config> Pallet<T> {174	pub fn init_collection(175		owner: T::AccountId,176		data: CreateCollectionData<T::AccountId>,177	) -> Result<CollectionId, DispatchError> {178		<PalletCommon<T>>::init_collection(owner, data)179	}180	pub fn destroy_collection(181		collection: RefungibleHandle<T>,182		sender: &T::CrossAccountId,183	) -> DispatchResult {184		let id = collection.id;185186		// =========187188		PalletCommon::destroy_collection(collection.0, sender)?;189190		<TokensMinted<T>>::remove(id);191		<TokensBurnt<T>>::remove(id);192		<TokenData<T>>::remove_prefix((id,), None);193		<TotalSupply<T>>::remove_prefix((id,), None);194		<Balance<T>>::remove_prefix((id,), None);195		<Allowance<T>>::remove_prefix((id,), None);196		<Owned<T>>::remove_prefix((id,), None);197		<AccountBalance<T>>::remove_prefix((id,), None);198		Ok(())199	}200201	pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {202		let burnt = <TokensBurnt<T>>::get(collection.id)203			.checked_add(1)204			.ok_or(ArithmeticError::Overflow)?;205206		<TokensBurnt<T>>::insert(collection.id, burnt);207		<TokenData<T>>::remove((collection.id, token_id));208		<TotalSupply<T>>::remove((collection.id, token_id));209		<Balance<T>>::remove_prefix((collection.id, token_id), None);210		<Allowance<T>>::remove_prefix((collection.id, token_id), None);211		// TODO: ERC721 transfer event212		Ok(())213	}214215	pub fn burn(216		collection: &RefungibleHandle<T>,217		owner: &T::CrossAccountId,218		token: TokenId,219		amount: u128,220	) -> DispatchResult {221		let total_supply = <TotalSupply<T>>::get((collection.id, token))222			.checked_sub(amount)223			.ok_or(<CommonError<T>>::TokenValueTooLow)?;224225		// This was probally last owner of this token?226		if total_supply == 0 {227			// Ensure user actually owns this amount228			ensure!(229				<Balance<T>>::get((collection.id, token, owner)) == amount,230				<CommonError<T>>::TokenValueTooLow231			);232			let account_balance = <AccountBalance<T>>::get((collection.id, owner))233				.checked_sub(1)234				// Should not occur235				.ok_or(ArithmeticError::Underflow)?;236237			// =========238239			<Owned<T>>::remove((collection.id, owner, token));240			<AccountBalance<T>>::insert((collection.id, owner), account_balance);241			Self::burn_token(collection, token)?;242			<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(243				collection.id,244				token,245				owner.clone(),246				amount,247			));248			return Ok(());249		}250251		let balance = <Balance<T>>::get((collection.id, token, owner))252			.checked_sub(amount)253			.ok_or(<CommonError<T>>::TokenValueTooLow)?;254		let account_balance = if balance == 0 {255			<AccountBalance<T>>::get((collection.id, owner))256				.checked_sub(1)257				// Should not occur258				.ok_or(ArithmeticError::Underflow)?259		} else {260			0261		};262263		// =========264265		if balance == 0 {266			<Owned<T>>::remove((collection.id, owner, token));267			<Balance<T>>::remove((collection.id, token, owner));268			<AccountBalance<T>>::insert((collection.id, owner), account_balance);269		} else {270			<Balance<T>>::insert((collection.id, token, owner), balance);271		}272		<TotalSupply<T>>::insert((collection.id, token), total_supply);273		// TODO: ERC20 transfer event274		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(275			collection.id,276			token,277			owner.clone(),278			amount,279		));280		Ok(())281	}282283	pub fn transfer(284		collection: &RefungibleHandle<T>,285		from: &T::CrossAccountId,286		to: &T::CrossAccountId,287		token: TokenId,288		amount: u128,289	) -> DispatchResult {290		ensure!(291			collection.limits.transfers_enabled(),292			<CommonError<T>>::TransferNotAllowed293		);294295		if collection.access == AccessMode::AllowList {296			collection.check_allowlist(from)?;297			collection.check_allowlist(to)?;298		}299		<PalletCommon<T>>::ensure_correct_receiver(to)?;300301		let balance_from = <Balance<T>>::get((collection.id, token, from))302			.checked_sub(amount)303			.ok_or(<CommonError<T>>::TokenValueTooLow)?;304		let mut create_target = false;305		let from_to_differ = from != to;306		let balance_to = if from != to {307			let old_balance = <Balance<T>>::get((collection.id, token, to));308			if old_balance == 0 {309				create_target = true;310			}311			Some(312				old_balance313					.checked_add(amount)314					.ok_or(ArithmeticError::Overflow)?,315			)316		} else {317			None318		};319320		let account_balance_from = if balance_from == 0 {321			Some(322				<AccountBalance<T>>::get((collection.id, from))323					.checked_sub(1)324					// Should not occur325					.ok_or(ArithmeticError::Underflow)?,326			)327		} else {328			None329		};330		// Account data is created in token, AccountBalance should be increased331		// But only if from != to as we shouldn't check overflow in this case332		let account_balance_to = if create_target && from_to_differ {333			let account_balance_to = <AccountBalance<T>>::get((collection.id, to))334				.checked_add(1)335				.ok_or(ArithmeticError::Overflow)?;336			ensure!(337				account_balance_to < collection.limits.account_token_ownership_limit(),338				<CommonError<T>>::AccountTokenLimitExceeded,339			);340341			Some(account_balance_to)342		} else {343			None344		};345346		if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {347			let handle = <CollectionHandle<T>>::try_get(target.0)?;348			let dispatch = T::CollectionDispatch::dispatch(handle);349			let dispatch = dispatch.as_dyn();350351			// =========352353			dispatch.nest_token(from.clone(), (collection.id, token), target.1)?;354		}355356		if let Some(balance_to) = balance_to {357			// from != to358			if balance_from == 0 {359				<Balance<T>>::remove((collection.id, token, from));360			} else {361				<Balance<T>>::insert((collection.id, token, from), balance_from);362			}363			<Balance<T>>::insert((collection.id, token, to), balance_to);364			if let Some(account_balance_from) = account_balance_from {365				<AccountBalance<T>>::insert((collection.id, from), account_balance_from);366				<Owned<T>>::remove((collection.id, from, token));367			}368			if let Some(account_balance_to) = account_balance_to {369				<AccountBalance<T>>::insert((collection.id, to), account_balance_to);370				<Owned<T>>::insert((collection.id, to, token), true);371			}372		}373374		// TODO: ERC20 transfer event375		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(376			collection.id,377			token,378			from.clone(),379			to.clone(),380			amount,381		));382		Ok(())383	}384385	pub fn create_multiple_items(386		collection: &RefungibleHandle<T>,387		sender: &T::CrossAccountId,388		data: Vec<CreateRefungibleExData<T::CrossAccountId>>,389	) -> DispatchResult {390		if !collection.is_owner_or_admin(sender) {391			ensure!(392				collection.mint_mode,393				<CommonError<T>>::PublicMintingNotAllowed394			);395			collection.check_allowlist(sender)?;396397			for item in data.iter() {398				for user in item.users.keys() {399					collection.check_allowlist(user)?;400				}401			}402		}403404		for item in data.iter() {405			for (owner, _) in item.users.iter() {406				<PalletCommon<T>>::ensure_correct_receiver(owner)?;407			}408		}409410		// Total pieces per tokens411		let totals = data412			.iter()413			.map(|data| {414				Ok(data415					.users416					.iter()417					.map(|u| u.1)418					.try_fold(0u128, |acc, v| acc.checked_add(*v))419					.ok_or(ArithmeticError::Overflow)?)420			})421			.collect::<Result<Vec<_>, DispatchError>>()?;422		for total in &totals {423			ensure!(424				*total <= MAX_REFUNGIBLE_PIECES,425				<Error<T>>::WrongRefungiblePieces426			);427		}428429		let first_token_id = <TokensMinted<T>>::get(collection.id);430		let tokens_minted = first_token_id431			.checked_add(data.len() as u32)432			.ok_or(ArithmeticError::Overflow)?;433		ensure!(434			tokens_minted < collection.limits.token_limit(),435			<CommonError<T>>::CollectionTokenLimitExceeded436		);437438		let mut balances = BTreeMap::new();439		for data in &data {440			for owner in data.users.keys() {441				let balance = balances442					.entry(owner)443					.or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));444				*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;445446				ensure!(447					*balance <= collection.limits.account_token_ownership_limit(),448					<CommonError<T>>::AccountTokenLimitExceeded,449				);450			}451		}452453		// =========454455		<TokensMinted<T>>::insert(collection.id, tokens_minted);456		for (account, balance) in balances {457			<AccountBalance<T>>::insert((collection.id, account), balance);458		}459		for (i, token) in data.into_iter().enumerate() {460			let token_id = first_token_id + i as u32 + 1;461			<TotalSupply<T>>::insert((collection.id, token_id), totals[i]);462463			<TokenData<T>>::insert(464				(collection.id, token_id),465				ItemData {466					const_data: token.const_data,467					variable_data: token.variable_data,468				},469			);470			for (user, amount) in token.users.into_iter() {471				if amount == 0 {472					continue;473				}474				<Balance<T>>::insert((collection.id, token_id, &user), amount);475				<Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);476				// TODO: ERC20 transfer event477				<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(478					collection.id,479					TokenId(token_id),480					user,481					amount,482				));483			}484		}485		Ok(())486	}487488	pub fn set_allowance_unchecked(489		collection: &RefungibleHandle<T>,490		sender: &T::CrossAccountId,491		spender: &T::CrossAccountId,492		token: TokenId,493		amount: u128,494	) {495		if amount == 0 {496			<Allowance<T>>::remove((collection.id, token, sender, spender));497		} else {498			<Allowance<T>>::insert((collection.id, token, sender, spender), amount);499		}500		// TODO: ERC20 approval event501		<PalletCommon<T>>::deposit_event(CommonEvent::Approved(502			collection.id,503			token,504			sender.clone(),505			spender.clone(),506			amount,507		))508	}509510	pub fn set_allowance(511		collection: &RefungibleHandle<T>,512		sender: &T::CrossAccountId,513		spender: &T::CrossAccountId,514		token: TokenId,515		amount: u128,516	) -> DispatchResult {517		if collection.access == AccessMode::AllowList {518			collection.check_allowlist(sender)?;519			collection.check_allowlist(spender)?;520		}521522		<PalletCommon<T>>::ensure_correct_receiver(spender)?;523524		if <Balance<T>>::get((collection.id, token, sender)) < amount {525			ensure!(526				collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),527				<CommonError<T>>::CantApproveMoreThanOwned528			);529		}530531		// =========532533		Self::set_allowance_unchecked(collection, sender, spender, token, amount);534		Ok(())535	}536537	pub fn transfer_from(538		collection: &RefungibleHandle<T>,539		spender: &T::CrossAccountId,540		from: &T::CrossAccountId,541		to: &T::CrossAccountId,542		token: TokenId,543		amount: u128,544	) -> DispatchResult {545		if spender.conv_eq(from) {546			return Self::transfer(collection, from, to, token, amount);547		}548		if collection.access == AccessMode::AllowList {549			// `from`, `to` checked in [`transfer`]550			collection.check_allowlist(spender)?;551		}552553		let allowance =554			<Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);555		if allowance.is_none() {556			ensure!(557				collection.ignores_allowance(spender),558				<CommonError<T>>::ApprovedValueTooLow559			);560		}561562		// =========563564		Self::transfer(collection, from, to, token, amount)?;565		if let Some(allowance) = allowance {566			Self::set_allowance_unchecked(collection, from, spender, token, allowance);567		}568		Ok(())569	}570571	pub fn burn_from(572		collection: &RefungibleHandle<T>,573		spender: &T::CrossAccountId,574		from: &T::CrossAccountId,575		token: TokenId,576		amount: u128,577	) -> DispatchResult {578		if spender.conv_eq(from) {579			return Self::burn(collection, from, token, amount);580		}581		if collection.access == AccessMode::AllowList {582			// `from` checked in [`burn`]583			collection.check_allowlist(spender)?;584		}585586		let allowance =587			<Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);588		if allowance.is_none() {589			ensure!(590				collection.ignores_allowance(spender),591				<CommonError<T>>::ApprovedValueTooLow592			);593		}594595		// =========596597		Self::burn(collection, from, token, amount)?;598		if let Some(allowance) = allowance {599			Self::set_allowance_unchecked(collection, from, spender, token, allowance);600		}601		Ok(())602	}603604	pub fn set_variable_metadata(605		collection: &RefungibleHandle<T>,606		sender: &T::CrossAccountId,607		token: TokenId,608		data: BoundedVec<u8, CustomDataLimit>,609	) -> DispatchResult {610		collection.check_can_update_meta(611			sender,612			&T::CrossAccountId::from_sub(collection.owner.clone()),613		)?;614615		let token_data = <TokenData<T>>::get((collection.id, token));616617		// =========618619		<TokenData<T>>::insert(620			(collection.id, token),621			ItemData {622				variable_data: data,623				..token_data624			},625		);626		Ok(())627	}628629	/// Delegated to `create_multiple_items`630	pub fn create_item(631		collection: &RefungibleHandle<T>,632		sender: &T::CrossAccountId,633		data: CreateRefungibleExData<T::CrossAccountId>,634	) -> DispatchResult {635		Self::create_multiple_items(collection, sender, vec![data])636	}637}
addedprimitives/data-structs/src/bounded.rsdiffbeforeafterboth
--- /dev/null
+++ b/primitives/data-structs/src/bounded.rs
@@ -0,0 +1,138 @@
+use core::fmt;
+use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
+use sp_std::vec::Vec;
+
+use frame_support::{
+	BoundedVec,
+	storage::{bounded_btree_map::BoundedBTreeMap, bounded_btree_set::BoundedBTreeSet},
+};
+
+/// BoundedVec doesn't supports serde
+#[cfg(feature = "serde1")]
+pub mod vec_serde {
+	use core::convert::TryFrom;
+	use frame_support::{BoundedVec, traits::Get};
+	use serde::{
+		ser::{self, Serialize},
+		de::{self, Deserialize, Error},
+	};
+	use sp_std::vec::Vec;
+
+	pub fn serialize<D, V, S>(value: &BoundedVec<V, S>, serializer: D) -> Result<D::Ok, D::Error>
+	where
+		D: ser::Serializer,
+		V: Serialize,
+	{
+		(value as &Vec<_>).serialize(serializer)
+	}
+
+	pub fn deserialize<'de, D, V, S>(deserializer: D) -> Result<BoundedVec<V, S>, D::Error>
+	where
+		D: de::Deserializer<'de>,
+		V: de::Deserialize<'de>,
+		S: Get<u32>,
+	{
+		// TODO: Implement custom visitor, which will limit vec size at parse time? Will serde only be used by chainspec?
+		let vec = <Vec<V>>::deserialize(deserializer)?;
+		let len = vec.len();
+		TryFrom::try_from(vec).map_err(|_| D::Error::invalid_length(len, &"lesser size"))
+	}
+}
+
+pub fn vec_debug<V, S>(v: &BoundedVec<V, S>, f: &mut fmt::Formatter) -> Result<(), fmt::Error>
+where
+	V: fmt::Debug,
+{
+	use core::fmt::Debug;
+	(&v as &Vec<V>).fmt(f)
+}
+
+#[cfg(feature = "serde1")]
+#[allow(dead_code)]
+pub mod map_serde {
+	use core::convert::TryFrom;
+	use sp_std::collections::btree_map::BTreeMap;
+	use frame_support::{traits::Get, storage::bounded_btree_map::BoundedBTreeMap};
+	use serde::{
+		ser::{self, Serialize},
+		de::{self, Deserialize, Error},
+	};
+	pub fn serialize<D, K, V, S>(
+		value: &BoundedBTreeMap<K, V, S>,
+		serializer: D,
+	) -> Result<D::Ok, D::Error>
+	where
+		D: ser::Serializer,
+		K: Serialize + Ord,
+		V: Serialize,
+	{
+		(value as &BTreeMap<_, _>).serialize(serializer)
+	}
+
+	pub fn deserialize<'de, D, K, V, S>(
+		deserializer: D,
+	) -> Result<BoundedBTreeMap<K, V, S>, D::Error>
+	where
+		D: de::Deserializer<'de>,
+		K: de::Deserialize<'de> + Ord,
+		V: de::Deserialize<'de>,
+		S: Get<u32>,
+	{
+		let map = <BTreeMap<K, V>>::deserialize(deserializer)?;
+		let len = map.len();
+		TryFrom::try_from(map).map_err(|_| D::Error::invalid_length(len, &"lesser size"))
+	}
+}
+
+pub fn map_debug<K, V, S>(
+	v: &BoundedBTreeMap<K, V, S>,
+	f: &mut fmt::Formatter,
+) -> Result<(), fmt::Error>
+where
+	K: fmt::Debug + Ord,
+	V: fmt::Debug,
+{
+	use core::fmt::Debug;
+	(&v as &BTreeMap<K, V>).fmt(f)
+}
+
+#[cfg(feature = "serde1")]
+#[allow(dead_code)]
+pub mod set_serde {
+	use core::convert::TryFrom;
+	use sp_std::collections::btree_set::BTreeSet;
+	use frame_support::{traits::Get, storage::bounded_btree_set::BoundedBTreeSet};
+	use serde::{
+		ser::{self, Serialize},
+		de::{self, Deserialize, Error},
+	};
+	pub fn serialize<D, K, S>(
+		value: &BoundedBTreeSet<K, S>,
+		serializer: D,
+	) -> Result<D::Ok, D::Error>
+	where
+		D: ser::Serializer,
+		K: Serialize + Ord,
+	{
+		(value as &BTreeSet<_>).serialize(serializer)
+	}
+
+	pub fn deserialize<'de, D, K, S>(deserializer: D) -> Result<BoundedBTreeSet<K, S>, D::Error>
+	where
+		D: de::Deserializer<'de>,
+		K: de::Deserialize<'de> + Ord,
+		S: Get<u32>,
+	{
+		let map = <BTreeSet<K>>::deserialize(deserializer)?;
+		let len = map.len();
+		TryFrom::try_from(map).map_err(|_| D::Error::invalid_length(len, &"lesser size"))
+	}
+}
+
+pub fn set_debug<K, S>(v: &BoundedBTreeSet<K, S>, f: &mut fmt::Formatter) -> Result<(), fmt::Error>
+where
+	K: fmt::Debug + Ord,
+{
+	use core::fmt::Debug;
+	(&v as &BTreeSet<K>).fmt(f)
+}
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -22,9 +22,7 @@
 };
 use frame_support::{
 	storage::{bounded_btree_map::BoundedBTreeMap, bounded_btree_set::BoundedBTreeSet},
-	traits::ConstU16,
 };
-use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
 
 #[cfg(feature = "serde")]
 use serde::{Serialize, Deserialize};
@@ -36,6 +34,7 @@
 use derivative::Derivative;
 use scale_info::TypeInfo;
 
+mod bounded;
 pub mod mapping;
 mod migration;
 
@@ -255,14 +254,14 @@
 	pub owner: AccountId,
 	pub mode: CollectionMode,
 	pub access: AccessMode,
-	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,
-	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,
-	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,
 	pub mint_mode: bool,
-	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,
 	pub schema_version: SchemaVersion,
 	pub sponsorship: SponsorshipState<AccountId>,
@@ -272,9 +271,9 @@
 	#[version(2.., upper(limits.into()))]
 	pub limits: CollectionLimitsVersion2,
 
-	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,
-	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	pub const_on_chain_schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>,
 	pub meta_update_permission: MetaUpdatePermission,
 }
@@ -286,20 +285,20 @@
 	#[derivative(Default(value = "CollectionMode::NFT"))]
 	pub mode: CollectionMode,
 	pub access: Option<AccessMode>,
-	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,
-	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,
-	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,
-	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,
 	pub schema_version: Option<SchemaVersion>,
 	pub pending_sponsor: Option<AccountId>,
 	pub limits: Option<CollectionLimits>,
-	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,
-	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
 	pub const_on_chain_schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>,
 	pub meta_update_permission: Option<MetaUpdatePermission>,
 }
@@ -410,8 +409,8 @@
 	Owner,
 	/// Owner can nest tokens from specified collections
 	OwnerRestricted(
-		#[cfg_attr(feature = "serde1", serde(with = "bounded_set_serde"))]
-		#[derivative(Debug(format_with = "bounded_set_debug"))]
+		#[cfg_attr(feature = "serde1", serde(with = "bounded::set_serde"))]
+		#[derivative(Debug(format_with = "bounded::set_debug"))]
 		BoundedBTreeSet<CollectionId, ConstU32<16>>,
 	),
 }
@@ -421,150 +420,17 @@
 pub enum SponsoringRateLimit {
 	SponsoringDisabled,
 	Blocks(u32),
-}
-
-/// BoundedVec doesn't supports serde
-#[cfg(feature = "serde1")]
-mod bounded_serde {
-	use core::convert::TryFrom;
-	use frame_support::{BoundedVec, traits::Get};
-	use serde::{
-		ser::{self, Serialize},
-		de::{self, Deserialize, Error},
-	};
-	use sp_std::vec::Vec;
-
-	pub fn serialize<D, V, S>(value: &BoundedVec<V, S>, serializer: D) -> Result<D::Ok, D::Error>
-	where
-		D: ser::Serializer,
-		V: Serialize,
-	{
-		(value as &Vec<_>).serialize(serializer)
-	}
-
-	pub fn deserialize<'de, D, V, S>(deserializer: D) -> Result<BoundedVec<V, S>, D::Error>
-	where
-		D: de::Deserializer<'de>,
-		V: de::Deserialize<'de>,
-		S: Get<u32>,
-	{
-		// TODO: Implement custom visitor, which will limit vec size at parse time? Will serde only be used by chainspec?
-		let vec = <Vec<V>>::deserialize(deserializer)?;
-		let len = vec.len();
-		TryFrom::try_from(vec).map_err(|_| D::Error::invalid_length(len, &"lesser size"))
-	}
-}
-
-fn bounded_debug<V, S>(v: &BoundedVec<V, S>, f: &mut fmt::Formatter) -> Result<(), fmt::Error>
-where
-	V: fmt::Debug,
-{
-	use core::fmt::Debug;
-	(&v as &Vec<V>).fmt(f)
-}
-
-#[cfg(feature = "serde1")]
-#[allow(dead_code)]
-mod bounded_map_serde {
-	use core::convert::TryFrom;
-	use sp_std::collections::btree_map::BTreeMap;
-	use frame_support::{traits::Get, storage::bounded_btree_map::BoundedBTreeMap};
-	use serde::{
-		ser::{self, Serialize},
-		de::{self, Deserialize, Error},
-	};
-	pub fn serialize<D, K, V, S>(
-		value: &BoundedBTreeMap<K, V, S>,
-		serializer: D,
-	) -> Result<D::Ok, D::Error>
-	where
-		D: ser::Serializer,
-		K: Serialize + Ord,
-		V: Serialize,
-	{
-		(value as &BTreeMap<_, _>).serialize(serializer)
-	}
-
-	pub fn deserialize<'de, D, K, V, S>(
-		deserializer: D,
-	) -> Result<BoundedBTreeMap<K, V, S>, D::Error>
-	where
-		D: de::Deserializer<'de>,
-		K: de::Deserialize<'de> + Ord,
-		V: de::Deserialize<'de>,
-		S: Get<u32>,
-	{
-		let map = <BTreeMap<K, V>>::deserialize(deserializer)?;
-		let len = map.len();
-		TryFrom::try_from(map).map_err(|_| D::Error::invalid_length(len, &"lesser size"))
-	}
-}
-
-fn bounded_map_debug<K, V, S>(
-	v: &BoundedBTreeMap<K, V, S>,
-	f: &mut fmt::Formatter,
-) -> Result<(), fmt::Error>
-where
-	K: fmt::Debug + Ord,
-	V: fmt::Debug,
-{
-	use core::fmt::Debug;
-	(&v as &BTreeMap<K, V>).fmt(f)
-}
-
-#[cfg(feature = "serde1")]
-#[allow(dead_code)]
-mod bounded_set_serde {
-	use core::convert::TryFrom;
-	use sp_std::collections::btree_set::BTreeSet;
-	use frame_support::{traits::Get, storage::bounded_btree_set::BoundedBTreeSet};
-	use serde::{
-		ser::{self, Serialize},
-		de::{self, Deserialize, Error},
-	};
-	pub fn serialize<D, K, S>(
-		value: &BoundedBTreeSet<K, S>,
-		serializer: D,
-	) -> Result<D::Ok, D::Error>
-	where
-		D: ser::Serializer,
-		K: Serialize + Ord,
-	{
-		(value as &BTreeSet<_>).serialize(serializer)
-	}
-
-	pub fn deserialize<'de, D, K, S>(deserializer: D) -> Result<BoundedBTreeSet<K, S>, D::Error>
-	where
-		D: de::Deserializer<'de>,
-		K: de::Deserialize<'de> + Ord,
-		S: Get<u32>,
-	{
-		let map = <BTreeSet<K>>::deserialize(deserializer)?;
-		let len = map.len();
-		TryFrom::try_from(map).map_err(|_| D::Error::invalid_length(len, &"lesser size"))
-	}
 }
 
-fn bounded_set_debug<K, S>(
-	v: &BoundedBTreeSet<K, S>,
-	f: &mut fmt::Formatter,
-) -> Result<(), fmt::Error>
-where
-	K: fmt::Debug + Ord,
-{
-	use core::fmt::Debug;
-	(&v as &BTreeSet<K>).fmt(f)
-}
-
 #[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 #[derivative(Debug)]
 pub struct CreateNftData {
-	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
-	#[derivative(Debug(format_with = "bounded_debug"))]
+	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
+	#[derivative(Debug(format_with = "bounded::vec_debug"))]
 	pub const_data: BoundedVec<u8, CustomDataLimit>,
-	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
-	#[derivative(Debug(format_with = "bounded_debug"))]
+	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
+	#[derivative(Debug(format_with = "bounded::vec_debug"))]
 	pub variable_data: BoundedVec<u8, CustomDataLimit>,
 }
 
@@ -578,11 +444,11 @@
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 #[derivative(Debug)]
 pub struct CreateReFungibleData {
-	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
-	#[derivative(Debug(format_with = "bounded_debug"))]
+	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
+	#[derivative(Debug(format_with = "bounded::vec_debug"))]
 	pub const_data: BoundedVec<u8, CustomDataLimit>,
-	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
-	#[derivative(Debug(format_with = "bounded_debug"))]
+	#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
+	#[derivative(Debug(format_with = "bounded::vec_debug"))]
 	pub variable_data: BoundedVec<u8, CustomDataLimit>,
 	pub pieces: u128,
 }
@@ -612,9 +478,9 @@
 #[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
 #[derivative(Debug)]
 pub struct CreateNftExData<CrossAccountId> {
-	#[derivative(Debug(format_with = "bounded_debug"))]
+	#[derivative(Debug(format_with = "bounded::vec_debug"))]
 	pub const_data: BoundedVec<u8, CustomDataLimit>,
-	#[derivative(Debug(format_with = "bounded_debug"))]
+	#[derivative(Debug(format_with = "bounded::vec_debug"))]
 	pub variable_data: BoundedVec<u8, CustomDataLimit>,
 	pub owner: CrossAccountId,
 }
@@ -622,11 +488,11 @@
 #[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
 #[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]
 pub struct CreateRefungibleExData<CrossAccountId> {
-	#[derivative(Debug(format_with = "bounded_debug"))]
+	#[derivative(Debug(format_with = "bounded::vec_debug"))]
 	pub const_data: BoundedVec<u8, CustomDataLimit>,
-	#[derivative(Debug(format_with = "bounded_debug"))]
+	#[derivative(Debug(format_with = "bounded::vec_debug"))]
 	pub variable_data: BoundedVec<u8, CustomDataLimit>,
-	#[derivative(Debug(format_with = "bounded_map_debug"))]
+	#[derivative(Debug(format_with = "bounded::map_debug"))]
 	pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,
 }
 
@@ -634,16 +500,16 @@
 #[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]
 pub enum CreateItemExData<CrossAccountId> {
 	NFT(
-		#[derivative(Debug(format_with = "bounded_debug"))]
+		#[derivative(Debug(format_with = "bounded::vec_debug"))]
 		BoundedVec<CreateNftExData<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,
 	),
 	Fungible(
-		#[derivative(Debug(format_with = "bounded_map_debug"))]
+		#[derivative(Debug(format_with = "bounded::map_debug"))]
 		BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,
 	),
 	/// Many tokens, each may have only one owner
 	RefungibleMultipleItems(
-		#[derivative(Debug(format_with = "bounded_debug"))]
+		#[derivative(Debug(format_with = "bounded::vec_debug"))]
 		BoundedVec<CreateRefungibleExData<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,
 	),
 	/// Single token, which may have many owners