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

difftreelog

added erc20 api for refungible tokens

Grigoriy Simonov2022-07-22parent: #5ffdcf0.patch.diff
in: master

7 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6322,11 +6322,14 @@
 name = "pallet-refungible"
 version = "0.1.1"
 dependencies = [
+ "ethereum",
+ "evm-coder",
  "frame-benchmarking",
  "frame-support",
  "frame-system",
  "pallet-common",
  "pallet-evm",
+ "pallet-evm-coder-substrate",
  "pallet-structure",
  "parity-scale-codec 3.1.5",
  "scale-info",
modifiedpallets/refungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/refungible/Cargo.toml
+++ b/pallets/refungible/Cargo.toml
@@ -11,36 +11,43 @@
 version = '3.1.2'
 
 [dependencies]
-frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
-frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
-sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
-sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
-sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
-pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }
+evm-coder = { default-features = false, path = '../../crates/evm-coder' }
+pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' }
 pallet-common = { default-features = false, path = '../common' }
 pallet-structure = { default-features = false, path = '../structure' }
+struct-versioning = { path = "../../crates/struct-versioning" }
 up-data-structs = { default-features = false, path = '../../primitives/data-structs' }
+
+ethereum = { version = "0.12.0", default-features = false }
+scale-info = { version = "2.0.1", default-features = false, features = ["derive",] }
+
 frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
-scale-info = { version = "2.0.1", default-features = false, features = [
-    "derive",
-] }
-struct-versioning = { path = "../../crates/struct-versioning" }
+frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
+frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
+pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }
+sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
+sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
+sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
 
 [features]
 default = ["std"]
 std = [
+    "ethereum/std",
+    "evm-coder/std",
+    'frame-benchmarking/std',
     "frame-support/std",
     "frame-system/std",
+    "pallet-common/std",
+    "pallet-evm/std",
+    "pallet-evm-coder-substrate/std",
+    "pallet-structure/std",
     "sp-runtime/std",
     "sp-std/std",
     "up-data-structs/std",
-    "pallet-common/std",
-    "pallet-structure/std",
-    'frame-benchmarking/std',
-    "pallet-evm/std",
 ]
 runtime-benchmarks = [
     'frame-benchmarking',
     'frame-support/runtime-benchmarks',
     'frame-system/runtime-benchmarks',
+    'up-data-structs/runtime-benchmarks',
 ]
deletedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// 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::TokenId;
-use pallet_common::erc::CommonEvmHandler;
-use pallet_evm::PrecompileHandle;
-
-use crate::{Config, RefungibleHandle};
-
-impl<T: Config> CommonEvmHandler for RefungibleHandle<T> {
-	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");
-
-	fn call(
-		self,
-		_handle: &mut impl PrecompileHandle,
-	) -> Option<pallet_common::erc::PrecompileResult> {
-		// TODO: Implement RFT variant of ERC721
-		None
-	}
-}
-
-pub struct RefungibleTokenHandle<T: Config>(pub RefungibleHandle<T>, pub TokenId);
-
-impl<T: Config> CommonEvmHandler for RefungibleTokenHandle<T> {
-	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungibleToken.raw");
-
-	fn call(
-		self,
-		_handle: &mut impl PrecompileHandle,
-	) -> Option<pallet_common::erc::PrecompileResult> {
-		// TODO: Implement RFT variant of ERC20
-		None
-	}
-}
addedpallets/refungible/src/erc20.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/refungible/src/erc20.rs
@@ -0,0 +1,204 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+extern crate alloc;
+use core::{
+	char::{REPLACEMENT_CHARACTER, decode_utf16},
+	convert::TryInto,
+	ops::Deref,
+};
+use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight};
+use pallet_common::{
+	CommonWeightInfo,
+	erc::{CommonEvmHandler, PrecompileResult},
+};
+use pallet_evm::{account::CrossAccountId, PrecompileHandle};
+use pallet_evm_coder_substrate::{call, dispatch_to_evm, WithRecorder};
+use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};
+use sp_std::vec::Vec;
+use up_data_structs::{CollectionMode, TokenId};
+
+use crate::{
+	Allowance, Balance, common::CommonWeights, Config, erc721::UniqueRFTCall, Pallet,
+	RefungibleHandle, SelfWeightOf, weights::WeightInfo, TotalSupply,
+};
+
+pub struct RefungibleTokenHandle<T: Config>(pub RefungibleHandle<T>, pub TokenId);
+
+#[derive(ToLog)]
+pub enum ERC20Events {
+	Transfer {
+		#[indexed]
+		from: address,
+		#[indexed]
+		to: address,
+		value: uint256,
+	},
+	Approval {
+		#[indexed]
+		owner: address,
+		#[indexed]
+		spender: address,
+		value: uint256,
+	},
+}
+
+#[solidity_interface(name = "ERC20", events(ERC20Events))]
+impl<T: Config> RefungibleTokenHandle<T> {
+	fn name(&self) -> Result<string> {
+		Ok(decode_utf16(self.name.iter().copied())
+			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
+			.collect::<string>())
+	}
+	fn symbol(&self) -> Result<string> {
+		Ok(string::from_utf8_lossy(&self.token_prefix).into())
+	}
+	fn total_supply(&self) -> Result<uint256> {
+		self.consume_store_reads(1)?;
+		Ok(<TotalSupply<T>>::get((self.id, self.1)).into())
+	}
+
+	fn decimals(&self) -> Result<uint8> {
+		Ok(if let CollectionMode::Fungible(decimals) = &self.mode {
+			*decimals
+		} else {
+			unreachable!()
+		})
+	}
+	fn balance_of(&self, owner: address) -> Result<uint256> {
+		self.consume_store_reads(1)?;
+		let owner = T::CrossAccountId::from_eth(owner);
+		let balance = <Balance<T>>::get((self.id, self.1, owner));
+		Ok(balance.into())
+	}
+	#[weight(<CommonWeights<T>>::transfer())]
+	fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = T::CrossAccountId::from_eth(to);
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		<Pallet<T>>::transfer(self, &caller, &to, self.1, amount, &budget)
+			.map_err(|_| "transfer error")?;
+		Ok(true)
+	}
+	#[weight(<CommonWeights<T>>::transfer_from())]
+	fn transfer_from(
+		&mut self,
+		caller: caller,
+		from: address,
+		to: address,
+		amount: uint256,
+	) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let from = T::CrossAccountId::from_eth(from);
+		let to = T::CrossAccountId::from_eth(to);
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		<Pallet<T>>::transfer_from(self, &caller, &from, &to, self.1, amount, &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
+	#[weight(<SelfWeightOf<T>>::approve())]
+	fn approve(&mut self, caller: caller, spender: address, amount: uint256) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let spender = T::CrossAccountId::from_eth(spender);
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+
+		<Pallet<T>>::set_allowance(self, &caller, &spender, self.1, amount)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
+	fn allowance(&self, owner: address, spender: address) -> Result<uint256> {
+		self.consume_store_reads(1)?;
+		let owner = T::CrossAccountId::from_eth(owner);
+		let spender = T::CrossAccountId::from_eth(spender);
+
+		Ok(<Allowance<T>>::get((self.id, self.1, owner, spender)).into())
+	}
+}
+
+#[solidity_interface(name = "ERC20UniqueExtensions")]
+impl<T: Config> RefungibleTokenHandle<T> {
+	#[weight(<SelfWeightOf<T>>::burn_from())]
+	fn burn_from(&mut self, caller: caller, from: address, amount: uint256) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let from = T::CrossAccountId::from_eth(from);
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		<Pallet<T>>::burn_from(self, &caller, &from, self.1, amount, &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
+}
+
+impl<T: Config> RefungibleTokenHandle<T> {
+	pub fn into_inner(self) -> RefungibleHandle<T> {
+		self.0
+	}
+	pub fn common_mut(&mut self) -> &mut RefungibleHandle<T> {
+		&mut self.0
+	}
+}
+
+impl<T: Config> WithRecorder<T> for RefungibleTokenHandle<T> {
+	fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {
+		self.0.recorder()
+	}
+	fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {
+		self.0.into_recorder()
+	}
+}
+
+impl<T: Config> Deref for RefungibleTokenHandle<T> {
+	type Target = RefungibleHandle<T>;
+
+	fn deref(&self) -> &Self::Target {
+		&self.0
+	}
+}
+
+#[solidity_interface(
+	name = "UniqueRefungibleToken",
+	is(
+		ERC20,
+		ERC20UniqueExtensions,
+		via("RefungibleHandle<T>", common_mut, UniqueRFT)
+	)
+)]
+impl<T: Config> RefungibleTokenHandle<T> where T::AccountId: From<[u8; 32]> {}
+
+generate_stubgen!(gen_impl, UniqueRefungibleTokenCall<()>, true);
+generate_stubgen!(gen_iface, UniqueRefungibleTokenCall<()>, false);
+
+impl<T: Config> CommonEvmHandler for RefungibleTokenHandle<T>
+where
+	T::AccountId: From<[u8; 32]>,
+{
+	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungibleToken.raw");
+
+	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
+		call::<T, UniqueRefungibleTokenCall<T>, _, _>(handle, self)
+	}
+}
addedpallets/refungible/src/erc721.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/refungible/src/erc721.rs
@@ -0,0 +1,32 @@
+extern crate alloc;
+use evm_coder::{generate_stubgen, solidity_interface, types::*};
+
+use pallet_common::{CollectionHandle, erc::CollectionCall, erc::CommonEvmHandler};
+
+use pallet_evm::PrecompileHandle;
+use pallet_evm_coder_substrate::call;
+
+use crate::{Config, RefungibleHandle};
+
+#[solidity_interface(
+	name = "UniqueRFT",
+	is(via("CollectionHandle<T>", common_mut, Collection),)
+)]
+impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> {}
+
+// Not a tests, but code generators
+generate_stubgen!(gen_impl, UniqueRFTCall<()>, true);
+generate_stubgen!(gen_iface, UniqueRFTCall<()>, false);
+
+impl<T: Config> CommonEvmHandler for RefungibleHandle<T>
+where
+	T::AccountId: From<[u8; 32]>,
+{
+	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");
+	fn call(
+		self,
+		handle: &mut impl PrecompileHandle,
+	) -> Option<pallet_common::erc::PrecompileResult> {
+		call::<T, UniqueRFTCall<T>, _, _>(handle, self)
+	}
+}
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//! # Refungible Pallet18//!19//! The Refungible pallet provides functionality for handling refungible collections and tokens.20//!21//! - [`Config`]22//! - [`RefungibleHandle`]23//! - [`Pallet`]24//! - [`CommonWeights`]25//!26//! ## Overview27//!28//! The Refungible pallet provides functions for:29//!30//! - RFT collection creation and removal31//! - Minting and burning of RFT tokens32//! - Partition and repartition of RFT tokens33//! - Retrieving number of pieces of RFT token34//! - Retrieving account balances35//! - Transfering RFT token pieces36//! - Burning RFT token pieces37//! - Setting and checking allowance for RFT tokens38//!39//! ### Terminology40//!41//! - **RFT token:** Non fungible token that was partitioned to pieces. If an account owns all42//!   of the RFT token pieces than it owns the RFT token and can repartition it.43//!44//! - **RFT Collection:** A collection of RFT tokens. All RFT tokens are part of a collection.45//!   Each collection has its own settings and set of permissions.46//!47//! - **RFT token piece:** A fungible part of an RFT token.48//!49//! - **Balance:** RFT token pieces owned by an account50//!51//! - **Allowance:** Maximum number of RFT token pieces that one account is allowed to52//!   transfer from the balance of another account53//!54//! - **Burning:** The process of “deleting” a token from a collection or removing token pieces from55//!   an account balance.56//!57//! ### Implementations58//!59//! The Refungible pallet provides implementations for the following traits. If these traits provide60//! the functionality that you need, then you can avoid coupling with the Refungible pallet.61//!62//! - [`CommonWeightInfo`](pallet_common::CommonWeightInfo): Functions for retrieval of transaction weight63//! - [`CommonCollectionOperations`](pallet_common::CommonCollectionOperations): Functions for dealing64//!   with collections65//! - [`RefungibleExtensions`](pallet_common::RefungibleExtensions): Functions specific for refungible66//!   collection67//!68//! ## Interface69//!70//! ### Dispatchable Functions71//!72//! - `init_collection` - Create RFT collection. RFT collection can be configured to allow or deny access for73//!   some accounts.74//! - `destroy_collection` - Destroy exising RFT collection. There should be no tokens in the collection.75//! - `burn` - Burn some amount of RFT token pieces owned by account. Burns the RFT token if no pieces left.76//! - `transfer` - Transfer some amount of RFT token pieces. Transfers should be enabled for RFT collection.77//!   Nests the RFT token if RFT token pieces are sent to another token.78//! - `create_item` - Mint RFT token in collection. Sender should have permission to mint tokens.79//! - `set_allowance` - Set allowance for another account to transfer balance from sender's account.80//! - `repartition` - Repartition token to selected number of pieces. Sender should own all existing pieces.81//!82//! ## Assumptions83//!84//! * Total number of pieces for one token shouldn't exceed `up_data_structs::MAX_REFUNGIBLE_PIECES`.85//! * Total number of tokens of all types shouldn't be greater than `up_data_structs::MAX_TOKEN_PREFIX_LENGTH`.86//! * Sender should be in collection's allow list to perform operations on tokens.8788#![cfg_attr(not(feature = "std"), no_std)]8990use frame_support::{ensure, fail, BoundedVec, transactional, storage::with_transaction};91use up_data_structs::{92	AccessMode, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId,93	CreateCollectionData, CreateRefungibleExData, mapping::TokenAddressMapping, budget::Budget,94	Property, PropertyScope, TrySetProperty, PropertyKey, PropertyValue, PropertyPermission,95	PropertyKeyPermission,96};97use pallet_evm::account::CrossAccountId;98use pallet_common::{99	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,100	CommonCollectionOperations as _,101};102use pallet_structure::Pallet as PalletStructure;103use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};104use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};105use core::ops::Deref;106use codec::{Encode, Decode, MaxEncodedLen};107use scale_info::TypeInfo;108109pub use pallet::*;110#[cfg(feature = "runtime-benchmarks")]111pub mod benchmarking;112pub mod common;113pub mod erc;114pub mod weights;115pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;116117#[struct_versioning::versioned(version = 2, upper)]118#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]119pub struct ItemData {120	pub const_data: BoundedVec<u8, CustomDataLimit>,121122	#[version(..2)]123	pub variable_data: BoundedVec<u8, CustomDataLimit>,124}125126#[frame_support::pallet]127pub mod pallet {128	use super::*;129	use frame_support::{130		Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,131		traits::StorageVersion,132	};133	use frame_system::pallet_prelude::*;134	use up_data_structs::{CollectionId, TokenId};135	use super::weights::WeightInfo;136137	#[pallet::error]138	pub enum Error<T> {139		/// Not Refungible item data used to mint in Refungible collection.140		NotRefungibleDataUsedToMintFungibleCollectionToken,141		/// Maximum refungibility exceeded142		WrongRefungiblePieces,143		/// Refungible token can't be repartitioned by user who isn't owns all pieces144		RepartitionWhileNotOwningAllPieces,145		/// Refungible token can't nest other tokens146		RefungibleDisallowsNesting,147		/// Setting item properties is not allowed148		SettingPropertiesNotAllowed,149	}150151	#[pallet::config]152	pub trait Config:153		frame_system::Config + pallet_common::Config + pallet_structure::Config154	{155		type WeightInfo: WeightInfo;156	}157158	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);159160	#[pallet::pallet]161	#[pallet::storage_version(STORAGE_VERSION)]162	#[pallet::generate_store(pub(super) trait Store)]163	pub struct Pallet<T>(_);164165	/// Amount of tokens minted for collection166	#[pallet::storage]167	pub type TokensMinted<T: Config> =168		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;169170	/// Amount of burnt tokens for collection171	#[pallet::storage]172	pub type TokensBurnt<T: Config> =173		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;174175	/// Custom data serialized to bytes for token176	#[pallet::storage]177	pub type TokenData<T: Config> = StorageNMap<178		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),179		Value = ItemData,180		QueryKind = ValueQuery,181	>;182183	#[pallet::storage]184	#[pallet::getter(fn token_properties)]185	pub type TokenProperties<T: Config> = StorageNMap<186		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),187		Value = up_data_structs::Properties,188		QueryKind = ValueQuery,189		OnEmpty = up_data_structs::TokenProperties,190	>;191192	/// Total amount of pieces for token193	#[pallet::storage]194	pub type TotalSupply<T: Config> = StorageNMap<195		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),196		Value = u128,197		QueryKind = ValueQuery,198	>;199200	/// Used to enumerate tokens owned by account201	#[pallet::storage]202	pub type Owned<T: Config> = StorageNMap<203		Key = (204			Key<Twox64Concat, CollectionId>,205			Key<Blake2_128Concat, T::CrossAccountId>,206			Key<Twox64Concat, TokenId>,207		),208		Value = bool,209		QueryKind = ValueQuery,210	>;211212	/// Amount of tokens owned by account213	#[pallet::storage]214	pub type AccountBalance<T: Config> = StorageNMap<215		Key = (216			Key<Twox64Concat, CollectionId>,217			// Owner218			Key<Blake2_128Concat, T::CrossAccountId>,219		),220		Value = u32,221		QueryKind = ValueQuery,222	>;223224	/// Amount of token pieces owned by account225	#[pallet::storage]226	pub type Balance<T: Config> = StorageNMap<227		Key = (228			Key<Twox64Concat, CollectionId>,229			Key<Twox64Concat, TokenId>,230			// Owner231			Key<Blake2_128Concat, T::CrossAccountId>,232		),233		Value = u128,234		QueryKind = ValueQuery,235	>;236237	/// Allowance set by an owner for a spender for a token238	#[pallet::storage]239	pub type Allowance<T: Config> = StorageNMap<240		Key = (241			Key<Twox64Concat, CollectionId>,242			Key<Twox64Concat, TokenId>,243			// Owner244			Key<Blake2_128, T::CrossAccountId>,245			// Spender246			Key<Blake2_128Concat, T::CrossAccountId>,247		),248		Value = u128,249		QueryKind = ValueQuery,250	>;251252	#[pallet::hooks]253	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {254		fn on_runtime_upgrade() -> Weight {255			if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {256				<TokenData<T>>::translate_values::<ItemDataVersion1, _>(|v| {257					Some(<ItemDataVersion2>::from(v))258				})259			}260261			0262		}263	}264}265266pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);267impl<T: Config> RefungibleHandle<T> {268	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {269		Self(inner)270	}271	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {272		self.0273	}274}275impl<T: Config> Deref for RefungibleHandle<T> {276	type Target = pallet_common::CollectionHandle<T>;277278	fn deref(&self) -> &Self::Target {279		&self.0280	}281}282283impl<T: Config> Pallet<T> {284	/// Get number of RFT tokens in collection285	pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {286		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)287	}288289	/// Check that RFT token exists290	///291	/// - `token`: Token ID.292	pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {293		<TotalSupply<T>>::contains_key((collection.id, token))294	}295296	pub fn set_scoped_token_property(297		collection_id: CollectionId,298		token_id: TokenId,299		scope: PropertyScope,300		property: Property,301	) -> DispatchResult {302		TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {303			properties.try_scoped_set(scope, property.key, property.value)304		})305		.map_err(<CommonError<T>>::from)?;306307		Ok(())308	}309310	pub fn set_scoped_token_properties(311		collection_id: CollectionId,312		token_id: TokenId,313		scope: PropertyScope,314		properties: impl Iterator<Item = Property>,315	) -> DispatchResult {316		TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {317			stored_properties.try_scoped_set_from_iter(scope, properties)318		})319		.map_err(<CommonError<T>>::from)?;320321		Ok(())322	}323}324325// unchecked calls skips any permission checks326impl<T: Config> Pallet<T> {327	/// Create RFT collection328	///329	/// `init_collection` will take non-refundable deposit for collection creation.330	///331	/// - `data`: Contains settings for collection limits and permissions.332	pub fn init_collection(333		owner: T::CrossAccountId,334		data: CreateCollectionData<T::AccountId>,335	) -> Result<CollectionId, DispatchError> {336		<PalletCommon<T>>::init_collection(owner, data, false)337	}338339	/// Destroy RFT collection340	///341	/// `destroy_collection` will throw error if collection contains any tokens.342	/// Only owner can destroy collection.343	pub fn destroy_collection(344		collection: RefungibleHandle<T>,345		sender: &T::CrossAccountId,346	) -> DispatchResult {347		let id = collection.id;348349		if Self::collection_has_tokens(id) {350			return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());351		}352353		// =========354355		PalletCommon::destroy_collection(collection.0, sender)?;356357		<TokensMinted<T>>::remove(id);358		<TokensBurnt<T>>::remove(id);359		<TokenData<T>>::remove_prefix((id,), None);360		<TotalSupply<T>>::remove_prefix((id,), None);361		<Balance<T>>::remove_prefix((id,), None);362		<Allowance<T>>::remove_prefix((id,), None);363		<Owned<T>>::remove_prefix((id,), None);364		<AccountBalance<T>>::remove_prefix((id,), None);365		Ok(())366	}367368	fn collection_has_tokens(collection_id: CollectionId) -> bool {369		<TokenData<T>>::iter_prefix((collection_id,))370			.next()371			.is_some()372	}373374	pub fn burn_token_unchecked(375		collection: &RefungibleHandle<T>,376		token_id: TokenId,377	) -> DispatchResult {378		let burnt = <TokensBurnt<T>>::get(collection.id)379			.checked_add(1)380			.ok_or(ArithmeticError::Overflow)?;381382		<TokensBurnt<T>>::insert(collection.id, burnt);383		<TokenData<T>>::remove((collection.id, token_id));384		<TokenProperties<T>>::remove((collection.id, token_id));385		<TotalSupply<T>>::remove((collection.id, token_id));386		<Balance<T>>::remove_prefix((collection.id, token_id), None);387		<Allowance<T>>::remove_prefix((collection.id, token_id), None);388		// TODO: ERC721 transfer event389		Ok(())390	}391392	/// Burn RFT token pieces393	///394	/// `burn` will decrease total amount of token pieces and amount owned by sender.395	/// `burn` can be called even if there are multiple owners of the RFT token.396	/// If sender wouldn't have any pieces left after `burn` than she will stop being397	/// one of the owners of the token. If there is no account that owns any pieces of398	/// the token than token will be burned too.399	///400	/// - `amount`: Amount of token pieces to burn.401	/// - `token`: Token who's pieces should be burned402	/// - `collection`: Collection that contains the token403	pub fn burn(404		collection: &RefungibleHandle<T>,405		owner: &T::CrossAccountId,406		token: TokenId,407		amount: u128,408	) -> DispatchResult {409		let total_supply = <TotalSupply<T>>::get((collection.id, token))410			.checked_sub(amount)411			.ok_or(<CommonError<T>>::TokenValueTooLow)?;412413		// This was probally last owner of this token?414		if total_supply == 0 {415			// Ensure user actually owns this amount416			ensure!(417				<Balance<T>>::get((collection.id, token, owner)) == amount,418				<CommonError<T>>::TokenValueTooLow419			);420			let account_balance = <AccountBalance<T>>::get((collection.id, owner))421				.checked_sub(1)422				// Should not occur423				.ok_or(ArithmeticError::Underflow)?;424425			// =========426427			<Owned<T>>::remove((collection.id, owner, token));428			<PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);429			<AccountBalance<T>>::insert((collection.id, owner), account_balance);430			Self::burn_token_unchecked(collection, token)?;431			<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(432				collection.id,433				token,434				owner.clone(),435				amount,436			));437			return Ok(());438		}439440		let balance = <Balance<T>>::get((collection.id, token, owner))441			.checked_sub(amount)442			.ok_or(<CommonError<T>>::TokenValueTooLow)?;443		let account_balance = if balance == 0 {444			<AccountBalance<T>>::get((collection.id, owner))445				.checked_sub(1)446				// Should not occur447				.ok_or(ArithmeticError::Underflow)?448		} else {449			0450		};451452		// =========453454		if balance == 0 {455			<Owned<T>>::remove((collection.id, owner, token));456			<PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);457			<Balance<T>>::remove((collection.id, token, owner));458			<AccountBalance<T>>::insert((collection.id, owner), account_balance);459		} else {460			<Balance<T>>::insert((collection.id, token, owner), balance);461		}462		<TotalSupply<T>>::insert((collection.id, token), total_supply);463		// TODO: ERC20 transfer event464		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(465			collection.id,466			token,467			owner.clone(),468			amount,469		));470		Ok(())471	}472473	#[transactional]474	fn modify_token_properties(475		collection: &RefungibleHandle<T>,476		sender: &T::CrossAccountId,477		token_id: TokenId,478		properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,479		is_token_create: bool,480		nesting_budget: &dyn Budget,481	) -> DispatchResult {482		let is_collection_admin = || collection.is_owner_or_admin(sender);483		let is_token_owner = || -> Result<bool, DispatchError> {484			let balance = collection.balance(sender.clone(), token_id);485			let total_pieces: u128 =486				Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);487			if balance != total_pieces {488				return Ok(false);489			}490491			let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(492				sender.clone(),493				collection.id,494				token_id,495				None,496				nesting_budget,497			)?;498499			Ok(is_bundle_owner)500		};501502		for (key, value) in properties {503			let permission = <PalletCommon<T>>::property_permissions(collection.id)504				.get(&key)505				.cloned()506				.unwrap_or_else(PropertyPermission::none);507508			let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))509				.get(&key)510				.is_some();511512			match permission {513				PropertyPermission { mutable: false, .. } if is_property_exists => {514					return Err(<CommonError<T>>::NoPermission.into());515				}516517				PropertyPermission {518					collection_admin,519					token_owner,520					..521				} => {522					//TODO: investigate threats during public minting.523					let is_token_create =524						is_token_create && (collection_admin || token_owner) && value.is_some();525					if !(is_token_create526						|| (collection_admin && is_collection_admin())527						|| (token_owner && is_token_owner()?))528					{529						fail!(<CommonError<T>>::NoPermission);530					}531				}532			}533534			match value {535				Some(value) => {536					<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {537						properties.try_set(key.clone(), value)538					})539					.map_err(<CommonError<T>>::from)?;540541					<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(542						collection.id,543						token_id,544						key,545					));546				}547				None => {548					<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {549						properties.remove(&key)550					})551					.map_err(<CommonError<T>>::from)?;552553					<PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(554						collection.id,555						token_id,556						key,557					));558				}559			}560		}561562		Ok(())563	}564565	pub fn set_token_properties(566		collection: &RefungibleHandle<T>,567		sender: &T::CrossAccountId,568		token_id: TokenId,569		properties: impl Iterator<Item = Property>,570		is_token_create: bool,571		nesting_budget: &dyn Budget,572	) -> DispatchResult {573		Self::modify_token_properties(574			collection,575			sender,576			token_id,577			properties.map(|p| (p.key, Some(p.value))),578			is_token_create,579			nesting_budget,580		)581	}582583	pub fn set_token_property(584		collection: &RefungibleHandle<T>,585		sender: &T::CrossAccountId,586		token_id: TokenId,587		property: Property,588		nesting_budget: &dyn Budget,589	) -> DispatchResult {590		let is_token_create = false;591592		Self::set_token_properties(593			collection,594			sender,595			token_id,596			[property].into_iter(),597			is_token_create,598			nesting_budget,599		)600	}601602	pub fn delete_token_properties(603		collection: &RefungibleHandle<T>,604		sender: &T::CrossAccountId,605		token_id: TokenId,606		property_keys: impl Iterator<Item = PropertyKey>,607		nesting_budget: &dyn Budget,608	) -> DispatchResult {609		let is_token_create = false;610611		Self::modify_token_properties(612			collection,613			sender,614			token_id,615			property_keys.into_iter().map(|key| (key, None)),616			is_token_create,617			nesting_budget,618		)619	}620621	pub fn delete_token_property(622		collection: &RefungibleHandle<T>,623		sender: &T::CrossAccountId,624		token_id: TokenId,625		property_key: PropertyKey,626		nesting_budget: &dyn Budget,627	) -> DispatchResult {628		Self::delete_token_properties(629			collection,630			sender,631			token_id,632			[property_key].into_iter(),633			nesting_budget,634		)635	}636637	/// Transfer RFT token pieces from one account to another.638	///639	/// If the sender is no longer owns any pieces after the `transfer` than she stops being an owner of the token.640	///641	/// - `from`: Owner of token pieces to transfer.642	/// - `to`: Recepient of transfered token pieces.643	/// - `amount`: Amount of token pieces to transfer.644	/// - `token`: Token whos pieces should be transfered645	/// - `collection`: Collection that contains the token646	pub fn transfer(647		collection: &RefungibleHandle<T>,648		from: &T::CrossAccountId,649		to: &T::CrossAccountId,650		token: TokenId,651		amount: u128,652		nesting_budget: &dyn Budget,653	) -> DispatchResult {654		ensure!(655			collection.limits.transfers_enabled(),656			<CommonError<T>>::TransferNotAllowed657		);658659		if collection.permissions.access() == AccessMode::AllowList {660			collection.check_allowlist(from)?;661			collection.check_allowlist(to)?;662		}663		<PalletCommon<T>>::ensure_correct_receiver(to)?;664665		let balance_from = <Balance<T>>::get((collection.id, token, from))666			.checked_sub(amount)667			.ok_or(<CommonError<T>>::TokenValueTooLow)?;668		let mut create_target = false;669		let from_to_differ = from != to;670		let balance_to = if from != to {671			let old_balance = <Balance<T>>::get((collection.id, token, to));672			if old_balance == 0 {673				create_target = true;674			}675			Some(676				old_balance677					.checked_add(amount)678					.ok_or(ArithmeticError::Overflow)?,679			)680		} else {681			None682		};683684		let account_balance_from = if balance_from == 0 {685			Some(686				<AccountBalance<T>>::get((collection.id, from))687					.checked_sub(1)688					// Should not occur689					.ok_or(ArithmeticError::Underflow)?,690			)691		} else {692			None693		};694		// Account data is created in token, AccountBalance should be increased695		// But only if from != to as we shouldn't check overflow in this case696		let account_balance_to = if create_target && from_to_differ {697			let account_balance_to = <AccountBalance<T>>::get((collection.id, to))698				.checked_add(1)699				.ok_or(ArithmeticError::Overflow)?;700			ensure!(701				account_balance_to < collection.limits.account_token_ownership_limit(),702				<CommonError<T>>::AccountTokenLimitExceeded,703			);704705			Some(account_balance_to)706		} else {707			None708		};709710		// =========711712		<PalletStructure<T>>::nest_if_sent_to_token(713			from.clone(),714			to,715			collection.id,716			token,717			nesting_budget,718		)?;719720		if let Some(balance_to) = balance_to {721			// from != to722			if balance_from == 0 {723				<Balance<T>>::remove((collection.id, token, from));724				<PalletStructure<T>>::unnest_if_nested(from, collection.id, token);725			} else {726				<Balance<T>>::insert((collection.id, token, from), balance_from);727			}728			<Balance<T>>::insert((collection.id, token, to), balance_to);729			if let Some(account_balance_from) = account_balance_from {730				<AccountBalance<T>>::insert((collection.id, from), account_balance_from);731				<Owned<T>>::remove((collection.id, from, token));732			}733			if let Some(account_balance_to) = account_balance_to {734				<AccountBalance<T>>::insert((collection.id, to), account_balance_to);735				<Owned<T>>::insert((collection.id, to, token), true);736			}737		}738739		// TODO: ERC20 transfer event740		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(741			collection.id,742			token,743			from.clone(),744			to.clone(),745			amount,746		));747		Ok(())748	}749750	/// Batched operation to create multiple RFT tokens.751	///752	/// Same as `create_item` but creates multiple tokens.753	///754	/// - `data`: Same as 'data` in `create_item` but contains data for multiple tokens.755	pub fn create_multiple_items(756		collection: &RefungibleHandle<T>,757		sender: &T::CrossAccountId,758		data: Vec<CreateRefungibleExData<T::CrossAccountId>>,759		nesting_budget: &dyn Budget,760	) -> DispatchResult {761		if !collection.is_owner_or_admin(sender) {762			ensure!(763				collection.permissions.mint_mode(),764				<CommonError<T>>::PublicMintingNotAllowed765			);766			collection.check_allowlist(sender)?;767768			for item in data.iter() {769				for user in item.users.keys() {770					collection.check_allowlist(user)?;771				}772			}773		}774775		for item in data.iter() {776			for (owner, _) in item.users.iter() {777				<PalletCommon<T>>::ensure_correct_receiver(owner)?;778			}779		}780781		// Total pieces per tokens782		let totals = data783			.iter()784			.map(|data| {785				Ok(data786					.users787					.iter()788					.map(|u| u.1)789					.try_fold(0u128, |acc, v| acc.checked_add(*v))790					.ok_or(ArithmeticError::Overflow)?)791			})792			.collect::<Result<Vec<_>, DispatchError>>()?;793		for total in &totals {794			ensure!(795				*total <= MAX_REFUNGIBLE_PIECES,796				<Error<T>>::WrongRefungiblePieces797			);798		}799800		let first_token_id = <TokensMinted<T>>::get(collection.id);801		let tokens_minted = first_token_id802			.checked_add(data.len() as u32)803			.ok_or(ArithmeticError::Overflow)?;804		ensure!(805			tokens_minted < collection.limits.token_limit(),806			<CommonError<T>>::CollectionTokenLimitExceeded807		);808809		let mut balances = BTreeMap::new();810		for data in &data {811			for owner in data.users.keys() {812				let balance = balances813					.entry(owner)814					.or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));815				*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;816817				ensure!(818					*balance <= collection.limits.account_token_ownership_limit(),819					<CommonError<T>>::AccountTokenLimitExceeded,820				);821			}822		}823824		for (i, token) in data.iter().enumerate() {825			let token_id = TokenId(first_token_id + i as u32 + 1);826			for (to, _) in token.users.iter() {827				<PalletStructure<T>>::check_nesting(828					sender.clone(),829					to,830					collection.id,831					token_id,832					nesting_budget,833				)?;834			}835		}836837		// =========838839		with_transaction(|| {840			for (i, data) in data.iter().enumerate() {841				let token_id = first_token_id + i as u32 + 1;842				<TotalSupply<T>>::insert((collection.id, token_id), totals[i]);843844				<TokenData<T>>::insert(845					(collection.id, token_id),846					ItemData {847						const_data: data.const_data.clone(),848					},849				);850851				for (user, amount) in data.users.iter() {852					if *amount == 0 {853						continue;854					}855					<Balance<T>>::insert((collection.id, token_id, &user), amount);856					<Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);857					<PalletStructure<T>>::nest_if_sent_to_token_unchecked(858						user,859						collection.id,860						TokenId(token_id),861					);862				}863864				if let Err(e) = Self::set_token_properties(865					collection,866					sender,867					TokenId(token_id),868					data.properties.clone().into_iter(),869					true,870					nesting_budget,871				) {872					return TransactionOutcome::Rollback(Err(e));873				}874			}875			TransactionOutcome::Commit(Ok(()))876		})?;877878		<TokensMinted<T>>::insert(collection.id, tokens_minted);879880		for (account, balance) in balances {881			<AccountBalance<T>>::insert((collection.id, account), balance);882		}883884		for (i, token) in data.into_iter().enumerate() {885			let token_id = first_token_id + i as u32 + 1;886887			for (user, amount) in token.users.into_iter() {888				if amount == 0 {889					continue;890				}891892				// TODO: ERC20 transfer event893				<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(894					collection.id,895					TokenId(token_id),896					user,897					amount,898				));899			}900		}901		Ok(())902	}903904	pub fn set_allowance_unchecked(905		collection: &RefungibleHandle<T>,906		sender: &T::CrossAccountId,907		spender: &T::CrossAccountId,908		token: TokenId,909		amount: u128,910	) {911		if amount == 0 {912			<Allowance<T>>::remove((collection.id, token, sender, spender));913		} else {914			<Allowance<T>>::insert((collection.id, token, sender, spender), amount);915		}916		// TODO: ERC20 approval event917		<PalletCommon<T>>::deposit_event(CommonEvent::Approved(918			collection.id,919			token,920			sender.clone(),921			spender.clone(),922			amount,923		))924	}925926	/// Set allowance for the spender to `transfer` or `burn` sender's token pieces.927	///928	/// - `amount`: Amount of token pieces the spender is allowed to `transfer` or `burn.929	pub fn set_allowance(930		collection: &RefungibleHandle<T>,931		sender: &T::CrossAccountId,932		spender: &T::CrossAccountId,933		token: TokenId,934		amount: u128,935	) -> DispatchResult {936		if collection.permissions.access() == AccessMode::AllowList {937			collection.check_allowlist(sender)?;938			collection.check_allowlist(spender)?;939		}940941		<PalletCommon<T>>::ensure_correct_receiver(spender)?;942943		if <Balance<T>>::get((collection.id, token, sender)) < amount {944			ensure!(945				collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),946				<CommonError<T>>::CantApproveMoreThanOwned947			);948		}949950		// =========951952		Self::set_allowance_unchecked(collection, sender, spender, token, amount);953		Ok(())954	}955956	/// Returns allowance, which should be set after transaction957	fn check_allowed(958		collection: &RefungibleHandle<T>,959		spender: &T::CrossAccountId,960		from: &T::CrossAccountId,961		token: TokenId,962		amount: u128,963		nesting_budget: &dyn Budget,964	) -> Result<Option<u128>, DispatchError> {965		if spender.conv_eq(from) {966			return Ok(None);967		}968		if collection.permissions.access() == AccessMode::AllowList {969			// `from`, `to` checked in [`transfer`]970			collection.check_allowlist(spender)?;971		}972		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {973			// TODO: should collection owner be allowed to perform this transfer?974			ensure!(975				<PalletStructure<T>>::check_indirectly_owned(976					spender.clone(),977					source.0,978					source.1,979					None,980					nesting_budget981				)?,982				<CommonError<T>>::ApprovedValueTooLow,983			);984			return Ok(None);985		}986		let allowance =987			<Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);988		if allowance.is_none() {989			ensure!(990				collection.ignores_allowance(spender),991				<CommonError<T>>::ApprovedValueTooLow992			);993		}994		Ok(allowance)995	}996997	/// Transfer RFT token pieces from one account to another.998	///999	/// Same as the [`transfer`] but spender doesn't needs to be an owner of the token pieces.1000	/// The owner should set allowance for the spender to transfer pieces.1001	///1002	/// [`transfer`]: struct.Pallet.html#method.transfer1003	pub fn transfer_from(1004		collection: &RefungibleHandle<T>,1005		spender: &T::CrossAccountId,1006		from: &T::CrossAccountId,1007		to: &T::CrossAccountId,1008		token: TokenId,1009		amount: u128,1010		nesting_budget: &dyn Budget,1011	) -> DispatchResult {1012		let allowance =1013			Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;10141015		// =========10161017		Self::transfer(collection, from, to, token, amount, nesting_budget)?;1018		if let Some(allowance) = allowance {1019			Self::set_allowance_unchecked(collection, from, spender, token, allowance);1020		}1021		Ok(())1022	}10231024	/// Burn RFT token pieces from the account.1025	///1026	/// Same as the [`burn`] but spender doesn't need to be an owner of the token pieces. The owner should1027	/// set allowance for the spender to burn pieces1028	///1029	/// [`burn`]: struct.Pallet.html#method.burn1030	pub fn burn_from(1031		collection: &RefungibleHandle<T>,1032		spender: &T::CrossAccountId,1033		from: &T::CrossAccountId,1034		token: TokenId,1035		amount: u128,1036		nesting_budget: &dyn Budget,1037	) -> DispatchResult {1038		let allowance =1039			Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;10401041		// =========10421043		Self::burn(collection, from, token, amount)?;1044		if let Some(allowance) = allowance {1045			Self::set_allowance_unchecked(collection, from, spender, token, allowance);1046		}1047		Ok(())1048	}10491050	/// Create RFT token.1051	///1052	/// The sender should be the owner/admin of the collection or collection should be configured1053	/// to allow public minting.1054	///1055	/// - `data`: Contains list of users who will become the owners of the token pieces and amount1056	///   of token pieces they will receive.1057	pub fn create_item(1058		collection: &RefungibleHandle<T>,1059		sender: &T::CrossAccountId,1060		data: CreateRefungibleExData<T::CrossAccountId>,1061		nesting_budget: &dyn Budget,1062	) -> DispatchResult {1063		Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1064	}10651066	/// Repartition RFT token.1067	///1068	/// `repartition` will set token balance of the sender and total amount of token pieces.1069	/// Sender should own all of the token pieces. `repartition' could be done even if some1070	/// token pieces were burned before.1071	///1072	/// - `amount`: Total amount of token pieces that the token will have after `repartition`.1073	pub fn repartition(1074		collection: &RefungibleHandle<T>,1075		owner: &T::CrossAccountId,1076		token: TokenId,1077		amount: u128,1078	) -> DispatchResult {1079		ensure!(1080			amount <= MAX_REFUNGIBLE_PIECES,1081			<Error<T>>::WrongRefungiblePieces1082		);1083		ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1084		// Ensure user owns all pieces1085		let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1086		let balance = <Balance<T>>::get((collection.id, token, owner));1087		ensure!(1088			total_pieces == balance,1089			<Error<T>>::RepartitionWhileNotOwningAllPieces1090		);10911092		<Balance<T>>::insert((collection.id, token, owner), amount);1093		<TotalSupply<T>>::insert((collection.id, token), amount);1094		Ok(())1095	}10961097	fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {1098		let mut owner = None;1099		let mut count = 0;1100		for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1101			count += 1;1102			if count > 1 {1103				return None;1104			}1105			owner = Some(key);1106		}1107		owner1108	}11091110	fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1111		<TotalSupply<T>>::try_get((collection_id, token_id)).ok()1112	}11131114	pub fn set_collection_properties(1115		collection: &RefungibleHandle<T>,1116		sender: &T::CrossAccountId,1117		properties: Vec<Property>,1118	) -> DispatchResult {1119		<PalletCommon<T>>::set_collection_properties(collection, sender, properties)1120	}11211122	pub fn delete_collection_properties(1123		collection: &RefungibleHandle<T>,1124		sender: &T::CrossAccountId,1125		property_keys: Vec<PropertyKey>,1126	) -> DispatchResult {1127		<PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)1128	}11291130	pub fn set_token_property_permissions(1131		collection: &RefungibleHandle<T>,1132		sender: &T::CrossAccountId,1133		property_permissions: Vec<PropertyKeyPermission>,1134	) -> DispatchResult {1135		<PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1136	}1137}
modifiedruntime/common/src/dispatch.rsdiffbeforeafterboth
--- a/runtime/common/src/dispatch.rs
+++ b/runtime/common/src/dispatch.rs
@@ -25,7 +25,7 @@
 pub use pallet_common::dispatch::CollectionDispatch;
 use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};
 use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};
-use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle, erc::RefungibleTokenHandle};
+use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle, erc20::RefungibleTokenHandle};
 use up_data_structs::{
 	CollectionMode, CreateCollectionData, MAX_DECIMAL_POINTS, mapping::TokenAddressMapping,
 };