git.delta.rocks / unique-network / refs/commits / 57b97ca5400c

difftreelog

refactor split common pallet

Yaroslav Bolyukin2021-10-12parent: #5db875a.patch.diff
in: master

16 files changed

addedpallets/common/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/pallets/common/Cargo.toml
@@ -0,0 +1,35 @@
+[package]
+name = "pallet-common"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies.codec]
+default-features = false
+features = ['derive']
+package = 'parity-scale-codec'
+version = '2.0.0'
+
+[dependencies]
+frame-support = { default-features = false, version = '4.0.0-dev', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.10' }
+frame-system = { default-features = false, version = '4.0.0-dev', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.10' }
+sp-runtime = { default-features = false, version = '4.0.0-dev', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.10' }
+sp-std = { default-features = false, version = '4.0.0-dev', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.10' }
+sp-core = { default-features = false, version = '4.0.0-dev', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.10' }
+nft-data-structs = { default-features = false, path = '../../primitives/nft' }
+pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' }
+evm-coder = { default-features = false, path = '../../crates/evm-coder' }
+
+pallet-evm = { default-features = false, version = "6.0.0-dev", git = "https://github.com/uniquenetwork/frontier.git", branch = "unique-polkadot-v0.9.10" }
+serde = { version = "1.0.130", default-features = false }
+
+[features]
+default = ["std"]
+std = [
+    "frame-support/std",
+    "frame-system/std",
+    "sp-runtime/std",
+    "sp-std/std",
+    "nft-data-structs/std",
+    "pallet-evm/std",
+]
+runtime-benchmarks = []
addedpallets/common/src/account.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/common/src/account.rs
@@ -0,0 +1,142 @@
+use crate::Config;
+use codec::{Encode, EncodeLike, Decode};
+use sp_core::H160;
+use sp_core::crypto::AccountId32;
+use core::cmp::Ordering;
+use serde::{Serialize, Deserialize};
+use pallet_evm::AddressMapping;
+use sp_std::vec::Vec;
+use sp_std::clone::Clone;
+
+pub trait CrossAccountId<AccountId>:
+	Encode + EncodeLike + Decode + Clone + PartialEq + Ord + core::fmt::Debug + Default
+// +
+// Serialize + Deserialize<'static>
+{
+	fn as_sub(&self) -> &AccountId;
+	fn as_eth(&self) -> &H160;
+
+	fn from_sub(account: AccountId) -> Self;
+	fn from_eth(account: H160) -> Self;
+}
+
+#[derive(Eq, Serialize, Deserialize)]
+pub struct BasicCrossAccountId<T: Config> {
+	/// If true - then ethereum is canonical encoding
+	from_ethereum: bool,
+	substrate: T::AccountId,
+	ethereum: H160,
+}
+
+impl<T: Config> Default for BasicCrossAccountId<T> {
+	fn default() -> Self {
+		Self::from_sub(T::AccountId::default())
+	}
+}
+
+impl<T: Config> core::fmt::Debug for BasicCrossAccountId<T> {
+	fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
+		if self.from_ethereum {
+			fmt.debug_tuple("CrossAccountId::Ethereum")
+				.field(&self.ethereum)
+				.finish()
+		} else {
+			fmt.debug_tuple("CrossAccountId::Substrate")
+				.field(&self.substrate)
+				.finish()
+		}
+	}
+}
+
+impl<T: Config> PartialOrd for BasicCrossAccountId<T> {
+	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+		Some(self.substrate.cmp(&other.substrate))
+	}
+}
+
+impl<T: Config> Ord for BasicCrossAccountId<T> {
+	fn cmp(&self, other: &Self) -> Ordering {
+		self.partial_cmp(other)
+			.expect("substrate account is total ordered")
+	}
+}
+
+impl<T: Config> PartialEq for BasicCrossAccountId<T> {
+	fn eq(&self, other: &Self) -> bool {
+		if self.from_ethereum == other.from_ethereum {
+			self.substrate == other.substrate && self.ethereum == other.ethereum
+		} else if self.from_ethereum {
+			// ethereum is canonical encoding, but we need to compare derived address
+			self.substrate == other.substrate
+		} else {
+			self.ethereum == other.ethereum
+		}
+	}
+}
+impl<T: Config> Clone for BasicCrossAccountId<T> {
+	fn clone(&self) -> Self {
+		Self {
+			from_ethereum: self.from_ethereum,
+			substrate: self.substrate.clone(),
+			ethereum: self.ethereum,
+		}
+	}
+}
+impl<T: Config> Encode for BasicCrossAccountId<T> {
+	fn encode(&self) -> Vec<u8> {
+		let as_result = if !self.from_ethereum {
+			Ok(self.substrate.clone())
+		} else {
+			Err(self.ethereum)
+		};
+		as_result.encode()
+	}
+}
+impl<T: Config> EncodeLike for BasicCrossAccountId<T> {}
+impl<T: Config> Decode for BasicCrossAccountId<T> {
+	fn decode<I>(input: &mut I) -> Result<Self, codec::Error>
+	where
+		I: codec::Input,
+	{
+		Ok(match <Result<T::AccountId, H160>>::decode(input)? {
+			Ok(s) => Self::from_sub(s),
+			Err(e) => Self::from_eth(e),
+		})
+	}
+}
+impl<T: Config> CrossAccountId<T::AccountId> for BasicCrossAccountId<T> {
+	fn as_sub(&self) -> &T::AccountId {
+		&self.substrate
+	}
+	fn as_eth(&self) -> &H160 {
+		&self.ethereum
+	}
+	fn from_sub(substrate: T::AccountId) -> Self {
+		Self {
+			ethereum: T::EvmBackwardsAddressMapping::from_account_id(substrate.clone()),
+			substrate,
+			from_ethereum: false,
+		}
+	}
+	fn from_eth(ethereum: H160) -> Self {
+		Self {
+			ethereum,
+			substrate: T::EvmAddressMapping::into_account_id(ethereum),
+			from_ethereum: true,
+		}
+	}
+}
+
+pub trait EvmBackwardsAddressMapping<AccountId> {
+	fn from_account_id(account_id: AccountId) -> H160;
+}
+
+/// Should have same mapping as EnsureAddressTruncated
+pub struct MapBackwardsAddressTruncated;
+impl EvmBackwardsAddressMapping<AccountId32> for MapBackwardsAddressTruncated {
+	fn from_account_id(account_id: AccountId32) -> H160 {
+		let mut out = [0; 20];
+		out.copy_from_slice(&(account_id.as_ref() as &[u8])[0..20]);
+		H160(out)
+	}
+}
addedpallets/common/src/benchmarking.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/common/src/benchmarking.rs
@@ -0,0 +1 @@
+#![cfg(feature = "runtime-benchmarking")]
addedpallets/common/src/erc.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/common/src/erc.rs
@@ -0,0 +1,10 @@
+pub use pallet_evm::PrecompileOutput;
+use sp_core::{H160, U256};
+
+/// Does not always represent a full collection, for RFT it is either
+/// collection (Implementing ERC721), or specific collection token (Implementing ERC20)
+pub trait CommonEvmHandler {
+	const CODE: &'static [u8];
+
+	fn call(self, source: &H160, input: &[u8], value: U256) -> Option<PrecompileOutput>;
+}
addedpallets/common/src/eth.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/common/src/eth.rs
@@ -0,0 +1,50 @@
+use nft_data_structs::{CollectionId, TokenId};
+use sp_core::H160;
+
+// 0x17c4e6453Cc49AAAaEACA894e6D9683e00000001 - collection 1
+// TODO: Unhardcode prefix
+const ETH_ACCOUNT_PREFIX: [u8; 16] = [
+	0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e,
+];
+
+// 0xf8238ccfff8ed887463fd5e00000000100000002  - collection 1, token 2
+// TODO: Unhardcode prefix
+const ETH_ACCOUNT_TOKEN_PREFIX: [u8; 12] = [
+	0xf8, 0x23, 0x8c, 0xcf, 0xff, 0x8e, 0xd8, 0x87, 0x46, 0x3f, 0xd5, 0xe0,
+];
+
+pub fn map_eth_to_id(eth: &H160) -> Option<CollectionId> {
+	if eth[0..16] != ETH_ACCOUNT_PREFIX {
+		return None;
+	}
+	let mut id_bytes = [0; 4];
+	id_bytes.copy_from_slice(&eth[16..20]);
+	Some(CollectionId(u32::from_be_bytes(id_bytes)))
+}
+pub fn collection_id_to_address(id: CollectionId) -> H160 {
+	let mut out = [0; 20];
+	out[0..16].copy_from_slice(&ETH_ACCOUNT_PREFIX);
+	out[16..20].copy_from_slice(&u32::to_be_bytes(id.0));
+	H160(out)
+}
+
+pub fn map_eth_to_token_id(eth: &H160) -> Option<(CollectionId, TokenId)> {
+	if eth[0..12] != ETH_ACCOUNT_TOKEN_PREFIX {
+		return None;
+	}
+	let mut id_bytes = [0; 4];
+	let mut token_id_bytes = [0; 4];
+	id_bytes.copy_from_slice(&eth[12..16]);
+	token_id_bytes.copy_from_slice(&eth[16..20]);
+	Some((
+		CollectionId(u32::from_be_bytes(id_bytes)),
+		TokenId(u32::from_be_bytes(token_id_bytes)),
+	))
+}
+pub fn collection_token_id_to_address(id: CollectionId, token: TokenId) -> H160 {
+	let mut out = [0; 20];
+	out[0..12].copy_from_slice(&ETH_ACCOUNT_TOKEN_PREFIX);
+	out[12..16].copy_from_slice(&u32::to_be_bytes(id.0));
+	out[16..20].copy_from_slice(&u32::to_be_bytes(token.0));
+	H160(out)
+}
addedpallets/common/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/common/src/lib.rs
@@ -0,0 +1,543 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use core::ops::{Deref, DerefMut};
+use sp_std::vec::Vec;
+use account::CrossAccountId;
+use frame_support::{
+	dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo},
+	ensure, fail,
+	traits::{Imbalance, Get, Currency},
+};
+use nft_data_structs::{
+	COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,
+	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH,
+	MetaUpdatePermission, Pays, PostDispatchInfo, TokenId, Weight, WithdrawReasons,
+};
+pub use pallet::*;
+use sp_core::H160;
+use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
+pub mod account;
+pub mod benchmarking;
+pub mod erc;
+pub mod eth;
+
+#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]
+pub struct CollectionHandle<T: Config> {
+	pub id: CollectionId,
+	collection: Collection<T>,
+	pub recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,
+}
+impl<T: Config> CollectionHandle<T> {
+	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {
+		<CollectionById<T>>::get(id).map(|collection| Self {
+			id,
+			collection,
+			recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(
+				eth::collection_id_to_address(id),
+				gas_limit,
+			),
+		})
+	}
+	pub fn new(id: CollectionId) -> Option<Self> {
+		Self::new_with_gas_limit(id, u64::MAX)
+	}
+	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {
+		Ok(Self::new(id).ok_or_else(|| <Error<T>>::CollectionNotFound)?)
+	}
+	pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {
+		self.recorder.log_sub(log)
+	}
+	pub fn log_infallible(&self, log: impl evm_coder::ToLog) {
+		self.recorder.log_infallible(log)
+	}
+	#[allow(dead_code)]
+	fn consume_gas(&self, gas: u64) -> DispatchResult {
+		self.recorder.consume_gas_sub(gas)
+	}
+	pub fn consume_sload(&self) -> DispatchResult {
+		self.recorder.consume_sload_sub()
+	}
+	pub fn consume_sstores(&self, amount: usize) -> DispatchResult {
+		self.recorder.consume_sstores_sub(amount)
+	}
+	pub fn consume_sstore(&self) -> DispatchResult {
+		self.recorder.consume_sstore_sub()
+	}
+	pub fn consume_log(&self, topics: usize, data: usize) -> DispatchResult {
+		self.recorder.consume_log_sub(topics, data)
+	}
+	pub fn submit_logs(self) -> DispatchResult {
+		self.recorder.submit_logs()
+	}
+	pub fn save(self) -> DispatchResult {
+		self.recorder.submit_logs()?;
+		<CollectionById<T>>::insert(self.id, self.collection);
+		Ok(())
+	}
+}
+impl<T: Config> Deref for CollectionHandle<T> {
+	type Target = Collection<T>;
+
+	fn deref(&self) -> &Self::Target {
+		&self.collection
+	}
+}
+
+impl<T: Config> DerefMut for CollectionHandle<T> {
+	fn deref_mut(&mut self) -> &mut Self::Target {
+		&mut self.collection
+	}
+}
+
+impl<T: Config> CollectionHandle<T> {
+	pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {
+		ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);
+		Ok(())
+	}
+	pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> Result<bool, DispatchError> {
+		self.consume_sload()?;
+
+		Ok(*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject.as_sub())))
+	}
+	pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {
+		ensure!(self.is_owner_or_admin(subject)?, <Error<T>>::NoPermission);
+		Ok(())
+	}
+	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {
+		Ok(self.limits.owner_can_transfer && self.is_owner_or_admin(user)?)
+	}
+	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {
+		Ok(self.limits.owner_can_transfer && self.is_owner_or_admin(user)?)
+	}
+	pub fn check_whitelist(&self, user: &T::CrossAccountId) -> DispatchResult {
+		self.consume_sload()?;
+
+		ensure!(
+			<WhiteList<T>>::get((self.id, user.as_sub())),
+			<Error<T>>::AddressNotInWhiteList
+		);
+		Ok(())
+	}
+
+	pub fn check_can_update_meta(
+		&self,
+		subject: &T::CrossAccountId,
+		item_owner: &T::CrossAccountId,
+	) -> DispatchResult {
+		match self.meta_update_permission {
+			MetaUpdatePermission::ItemOwner => {
+				ensure!(subject == item_owner, <Error<T>>::NoPermission);
+				Ok(())
+			}
+			MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),
+			MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),
+		}
+	}
+}
+
+#[frame_support::pallet]
+pub mod pallet {
+	use super::*;
+	use frame_support::{pallet_prelude::*};
+	use frame_support::{Blake2_128Concat, storage::Key};
+	use account::{EvmBackwardsAddressMapping, CrossAccountId};
+	use frame_support::traits::Currency;
+	use nft_data_structs::TokenId;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config {
+		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;
+
+		type CrossAccountId: CrossAccountId<Self::AccountId>;
+
+		type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;
+		type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;
+
+		type Currency: Currency<Self::AccountId>;
+		type CollectionCreationPrice: Get<
+			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,
+		>;
+		type TreasuryAccountId: Get<Self::AccountId>;
+	}
+
+	#[pallet::pallet]
+	#[pallet::generate_store(pub(super) trait Store)]
+	pub struct Pallet<T>(_);
+
+	#[pallet::event]
+	#[pallet::generate_deposit(pub fn deposit_event)]
+	pub enum Event<T: Config> {
+		/// New collection was created
+		///
+		/// # Arguments
+		///
+		/// * collection_id: Globally unique identifier of newly created collection.
+		///
+		/// * mode: [CollectionMode] converted into u8.
+		///
+		/// * account_id: Collection owner.
+		CollectionCreated(CollectionId, u8, T::AccountId),
+
+		/// New item was created.
+		///
+		/// # Arguments
+		///
+		/// * collection_id: Id of the collection where item was created.
+		///
+		/// * item_id: Id of an item. Unique within the collection.
+		///
+		/// * recipient: Owner of newly created item
+		///
+		/// * amount: Always 1 for NFT
+		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),
+
+		/// Collection item was burned.
+		///
+		/// # Arguments
+		///
+		/// * collection_id.
+		///
+		/// * item_id: Identifier of burned NFT.
+		///
+		/// * owner: which user has destroyed its tokens
+		///
+		/// * amount: Always 1 for NFT
+		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),
+
+		/// Item was transferred
+		///
+		/// * collection_id: Id of collection to which item is belong
+		///
+		/// * item_id: Id of an item
+		///
+		/// * sender: Original owner of item
+		///
+		/// * recipient: New owner of item
+		///
+		/// * amount: Always 1 for NFT
+		Transfer(
+			CollectionId,
+			TokenId,
+			T::CrossAccountId,
+			T::CrossAccountId,
+			u128,
+		),
+
+		/// * collection_id
+		///
+		/// * item_id
+		///
+		/// * sender
+		///
+		/// * spender
+		///
+		/// * amount
+		Approved(
+			CollectionId,
+			TokenId,
+			T::CrossAccountId,
+			T::CrossAccountId,
+			u128,
+		),
+	}
+
+	#[pallet::error]
+	pub enum Error<T> {
+		/// This collection does not exist.
+		CollectionNotFound,
+		/// Sender parameter and item owner must be equal.
+		MustBeTokenOwner,
+		/// No permission to perform action
+		NoPermission,
+		/// Collection is not in mint mode.
+		PublicMintingNotAllowed,
+		/// Address is not in white list.
+		AddressNotInWhiteList,
+
+		/// Collection name can not be longer than 63 char.
+		CollectionNameLimitExceeded,
+		/// Collection description can not be longer than 255 char.
+		CollectionDescriptionLimitExceeded,
+		/// Token prefix can not be longer than 15 char.
+		CollectionTokenPrefixLimitExceeded,
+		/// Total collections bound exceeded.
+		TotalCollectionsLimitExceeded,
+		/// variable_data exceeded data limit.
+		TokenVariableDataLimitExceeded,
+
+		/// Collection settings not allowing items transferring
+		TransferNotAllowed,
+		/// Account token limit exceeded per collection
+		AccountTokenLimitExceeded,
+		/// Collection token limit exceeded
+		CollectionTokenLimitExceeded,
+		/// Metadata flag frozen
+		MetadataFlagFrozen,
+
+		/// Item not exists.
+		TokenNotFound,
+		/// Item balance not enough.
+		TokenValueTooLow,
+		/// Requested value more than approved.
+		TokenValueNotEnough,
+		/// Tried to approve more than owned
+		CantApproveMoreThanOwned,
+
+		/// Can't transfer tokens to ethereum zero address
+		AddressIsZero,
+		/// Target collection doesn't supports this operation
+		UnsupportedOperation,
+	}
+
+	#[pallet::storage]
+	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;
+	#[pallet::storage]
+	pub type DestroyedCollectionCount<T> =
+		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;
+
+	/// Collection info
+	#[pallet::storage]
+	pub type CollectionById<T> = StorageMap<
+		Hasher = Blake2_128Concat,
+		Key = CollectionId,
+		Value = Collection<T>,
+		QueryKind = OptionQuery,
+	>;
+
+	/// List of collection admins
+	#[pallet::storage]
+	pub type IsAdmin<T: Config> = StorageNMap<
+		Key = (
+			Key<Blake2_128Concat, CollectionId>,
+			Key<Blake2_128Concat, T::AccountId>,
+		),
+		Value = bool,
+		QueryKind = ValueQuery,
+	>;
+
+	/// Whitelisted collection users
+	#[pallet::storage]
+	pub type WhiteList<T: Config> = StorageNMap<
+		Key = (
+			Key<Blake2_128Concat, CollectionId>,
+			Key<Blake2_128Concat, T::AccountId>,
+		),
+		Value = bool,
+		QueryKind = ValueQuery,
+	>;
+}
+
+impl<T: Config> Pallet<T> {
+	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens
+	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {
+		ensure!(
+			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,
+			<Error<T>>::AddressIsZero
+		);
+		Ok(())
+	}
+}
+
+impl<T: Config> Pallet<T> {
+	pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {
+		{
+			ensure!(
+				data.name.len() <= MAX_COLLECTION_NAME_LENGTH,
+				Error::<T>::CollectionNameLimitExceeded
+			);
+			ensure!(
+				data.description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH,
+				Error::<T>::CollectionDescriptionLimitExceeded
+			);
+			ensure!(
+				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH,
+				Error::<T>::CollectionTokenPrefixLimitExceeded
+			);
+		}
+
+		let created_count = <CreatedCollectionCount<T>>::get()
+			.0
+			.checked_add(1)
+			.ok_or(ArithmeticError::Overflow)?;
+		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;
+		let id = CollectionId(created_count);
+
+		// bound Total number of collections
+		ensure!(
+			created_count - destroyed_count < COLLECTION_NUMBER_LIMIT,
+			<Error<T>>::TotalCollectionsLimitExceeded
+		);
+
+		// =========
+
+		// Take a (non-refundable) deposit of collection creation
+		{
+			let mut imbalance =
+				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();
+			imbalance.subsume(
+				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(
+					&T::TreasuryAccountId::get(),
+					T::CollectionCreationPrice::get(),
+				),
+			);
+			<T as Config>::Currency::settle(
+				&data.owner,
+				imbalance,
+				WithdrawReasons::TRANSFER,
+				ExistenceRequirement::KeepAlive,
+			)
+			.map_err(|_| Error::<T>::NoPermission)?;
+		}
+
+		<CreatedCollectionCount<T>>::put(created_count);
+		<Pallet<T>>::deposit_event(Event::CollectionCreated(
+			id,
+			data.mode.id(),
+			data.owner.clone(),
+		));
+		<CollectionById<T>>::insert(id, data);
+		Ok(id)
+	}
+	pub fn destroy_collection(
+		collection: CollectionHandle<T>,
+		sender: &T::CrossAccountId,
+	) -> DispatchResult {
+		if !collection.limits.owner_can_destroy {
+			fail!(Error::<T>::NoPermission);
+		}
+		collection.check_is_owner(&sender)?;
+
+		let destroyed_collections = <DestroyedCollectionCount<T>>::get()
+			.0
+			.checked_add(1)
+			.ok_or(ArithmeticError::Overflow)?;
+
+		// =========
+
+		<DestroyedCollectionCount<T>>::put(destroyed_collections);
+		<CollectionById<T>>::remove(collection.id);
+		<IsAdmin<T>>::remove_prefix((collection.id,), None);
+		<WhiteList<T>>::remove_prefix((collection.id,), None);
+		Ok(())
+	}
+
+	pub fn toggle_whitelist(
+		collection: &CollectionHandle<T>,
+		sender: &T::CrossAccountId,
+		user: &T::CrossAccountId,
+		allowed: bool,
+	) -> DispatchResult {
+		collection.check_is_owner_or_admin(&sender)?;
+
+		// =========
+
+		if allowed {
+			<WhiteList<T>>::insert((collection.id, user.as_sub()), true);
+		} else {
+			<WhiteList<T>>::remove((collection.id, user.as_sub()));
+		}
+
+		Ok(())
+	}
+}
+
+#[macro_export]
+macro_rules! unsupported {
+	() => {
+		Err(<Error<T>>::UnsupportedOperation.into())
+	};
+}
+
+/// Worst cases
+pub trait CommonWeightInfo {
+	fn create_item() -> Weight;
+	fn create_multiple_items(amount: u32) -> Weight;
+	fn burn_item() -> Weight;
+	fn transfer() -> Weight;
+	fn approve() -> Weight;
+	fn transfer_from() -> Weight;
+	fn set_variable_metadata(bytes: u32) -> Weight;
+}
+
+pub trait CommonCollectionOperations<T: Config> {
+	fn create_item(
+		&self,
+		sender: T::CrossAccountId,
+		to: T::CrossAccountId,
+		data: CreateItemData,
+	) -> DispatchResultWithPostInfo;
+	fn create_multiple_items(
+		&self,
+		sender: T::CrossAccountId,
+		to: T::CrossAccountId,
+		data: Vec<CreateItemData>,
+	) -> DispatchResultWithPostInfo;
+	fn burn_item(
+		&self,
+		sender: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> DispatchResultWithPostInfo;
+
+	fn transfer(
+		&self,
+		sender: T::CrossAccountId,
+		to: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> DispatchResultWithPostInfo;
+	fn approve(
+		&self,
+		sender: T::CrossAccountId,
+		spender: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> DispatchResultWithPostInfo;
+	fn transfer_from(
+		&self,
+		sender: T::CrossAccountId,
+		from: T::CrossAccountId,
+		to: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> DispatchResultWithPostInfo;
+
+	fn set_variable_metadata(
+		&self,
+		sender: T::CrossAccountId,
+		token: TokenId,
+		data: Vec<u8>,
+	) -> DispatchResultWithPostInfo;
+
+	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;
+	fn token_exists(&self, token: TokenId) -> bool;
+
+	fn token_owner(&self, token: TokenId) -> T::CrossAccountId;
+	fn const_metadata(&self, token: TokenId) -> Vec<u8>;
+	fn variable_metadata(&self, token: TokenId) -> Vec<u8>;
+
+	/// How many tokens collection contains (Applicable to nonfungible/refungible)
+	fn collection_tokens(&self) -> u32;
+	/// Amount of different tokens account has (Applicable to nonfungible/refungible)
+	fn account_balance(&self, account: T::CrossAccountId) -> u32;
+	/// Amount of specific token account have (Applicable to fungible/refungible)
+	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;
+	fn allowance(
+		&self,
+		sender: T::CrossAccountId,
+		spender: T::CrossAccountId,
+		token: TokenId,
+	) -> u128;
+}
+
+// Flexible enough for implementing CommonCollectionOperations
+pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {
+	let post_info = PostDispatchInfo {
+		actual_weight: Some(weight),
+		pays_fee: Pays::Yes,
+	};
+	match res {
+		Ok(()) => Ok(post_info),
+		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),
+	}
+}
deletedpallets/nft/src/eth/account.rsdiffbeforeafterboth
--- a/pallets/nft/src/eth/account.rs
+++ /dev/null
@@ -1,136 +0,0 @@
-use crate::Config;
-use codec::{Encode, EncodeLike, Decode};
-use sp_core::crypto::AccountId32;
-use primitive_types::H160;
-use core::cmp::Ordering;
-use serde::{Serialize, Deserialize};
-use pallet_evm::AddressMapping;
-use sp_std::vec::Vec;
-use sp_std::clone::Clone;
-
-pub trait CrossAccountId<AccountId>:
-	Encode + EncodeLike + Decode + Clone + PartialEq + Ord + core::fmt::Debug
-// +
-// Serialize + Deserialize<'static>
-{
-	fn as_sub(&self) -> &AccountId;
-	fn as_eth(&self) -> &H160;
-
-	fn from_sub(account: AccountId) -> Self;
-	fn from_eth(account: H160) -> Self;
-}
-
-#[derive(Eq, Serialize, Deserialize)]
-pub struct BasicCrossAccountId<T: Config> {
-	/// If true - then ethereum is canonical encoding
-	from_ethereum: bool,
-	substrate: T::AccountId,
-	ethereum: H160,
-}
-
-impl<T: Config> core::fmt::Debug for BasicCrossAccountId<T> {
-	fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
-		if self.from_ethereum {
-			fmt.debug_tuple("CrossAccountId::Ethereum")
-				.field(&self.ethereum)
-				.finish()
-		} else {
-			fmt.debug_tuple("CrossAccountId::Substrate")
-				.field(&self.substrate)
-				.finish()
-		}
-	}
-}
-
-impl<T: Config> PartialOrd for BasicCrossAccountId<T> {
-	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-		Some(self.substrate.cmp(&other.substrate))
-	}
-}
-
-impl<T: Config> Ord for BasicCrossAccountId<T> {
-	fn cmp(&self, other: &Self) -> Ordering {
-		self.partial_cmp(other)
-			.expect("substrate account is total ordered")
-	}
-}
-
-impl<T: Config> PartialEq for BasicCrossAccountId<T> {
-	fn eq(&self, other: &Self) -> bool {
-		if self.from_ethereum == other.from_ethereum {
-			self.substrate == other.substrate && self.ethereum == other.ethereum
-		} else if self.from_ethereum {
-			// ethereum is canonical encoding, but we need to compare derived address
-			self.substrate == other.substrate
-		} else {
-			self.ethereum == other.ethereum
-		}
-	}
-}
-impl<T: Config> Clone for BasicCrossAccountId<T> {
-	fn clone(&self) -> Self {
-		Self {
-			from_ethereum: self.from_ethereum,
-			substrate: self.substrate.clone(),
-			ethereum: self.ethereum,
-		}
-	}
-}
-impl<T: Config> Encode for BasicCrossAccountId<T> {
-	fn encode(&self) -> Vec<u8> {
-		let as_result = if !self.from_ethereum {
-			Ok(self.substrate.clone())
-		} else {
-			Err(self.ethereum)
-		};
-		as_result.encode()
-	}
-}
-impl<T: Config> EncodeLike for BasicCrossAccountId<T> {}
-impl<T: Config> Decode for BasicCrossAccountId<T> {
-	fn decode<I>(input: &mut I) -> Result<Self, codec::Error>
-	where
-		I: codec::Input,
-	{
-		Ok(match <Result<T::AccountId, H160>>::decode(input)? {
-			Ok(s) => Self::from_sub(s),
-			Err(e) => Self::from_eth(e),
-		})
-	}
-}
-impl<T: Config> CrossAccountId<T::AccountId> for BasicCrossAccountId<T> {
-	fn as_sub(&self) -> &T::AccountId {
-		&self.substrate
-	}
-	fn as_eth(&self) -> &H160 {
-		&self.ethereum
-	}
-	fn from_sub(substrate: T::AccountId) -> Self {
-		Self {
-			ethereum: T::EvmBackwardsAddressMapping::from_account_id(substrate.clone()),
-			substrate,
-			from_ethereum: false,
-		}
-	}
-	fn from_eth(ethereum: H160) -> Self {
-		Self {
-			ethereum,
-			substrate: T::EvmAddressMapping::into_account_id(ethereum),
-			from_ethereum: true,
-		}
-	}
-}
-
-pub trait EvmBackwardsAddressMapping<AccountId> {
-	fn from_account_id(account_id: AccountId) -> H160;
-}
-
-/// Should have same mapping as EnsureAddressTruncated
-pub struct MapBackwardsAddressTruncated;
-impl EvmBackwardsAddressMapping<AccountId32> for MapBackwardsAddressTruncated {
-	fn from_account_id(account_id: AccountId32) -> H160 {
-		let mut out = [0; 20];
-		out.copy_from_slice(&(account_id.as_ref() as &[u8])[0..20]);
-		H160(out)
-	}
-}
deletedpallets/nft/src/eth/erc.rsdiffbeforeafterboth
--- a/pallets/nft/src/eth/erc.rs
+++ /dev/null
@@ -1,496 +0,0 @@
-use core::char::{decode_utf16, REPLACEMENT_CHARACTER};
-use evm_coder::{ToLog, execution::Result, generate_stubgen, solidity, solidity_interface, types::*};
-use nft_data_structs::{CreateItemData, CreateNftData};
-use core::convert::TryInto;
-use crate::{
-	Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList,
-	ItemListIndex,
-};
-use frame_support::storage::{StorageMap, StorageDoubleMap};
-use pallet_evm::AddressMapping;
-use pallet_evm_coder_substrate::dispatch_to_evm;
-use super::account::CrossAccountId;
-use sp_std::{vec, vec::Vec};
-
-#[solidity_interface(name = "ERC165")]
-impl<T: Config> CollectionHandle<T> {
-	fn supports_interface(&self, interface_id: bytes4) -> Result<bool> {
-		Ok(match self.mode {
-			CollectionMode::Fungible(_) => UniqueFungibleCall::supports_interface(interface_id),
-			CollectionMode::NFT => UniqueNFTCall::supports_interface(interface_id),
-			_ => false,
-		})
-	}
-}
-
-#[solidity_interface(name = "InlineNameSymbol")]
-impl<T: Config> CollectionHandle<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())
-	}
-}
-
-#[solidity_interface(name = "InlineTotalSupply")]
-impl<T: Config> CollectionHandle<T> {
-	fn total_supply(&self) -> Result<uint256> {
-		// TODO: we do not track total amount of all tokens
-		Ok(0.into())
-	}
-}
-
-#[solidity_interface(name = "ERC721Metadata", inline_is(InlineNameSymbol))]
-impl<T: Config> CollectionHandle<T> {
-	#[solidity(rename_selector = "tokenURI")]
-	fn token_uri(&self, token_id: uint256) -> Result<string> {
-		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
-		Ok(string::from_utf8_lossy(
-			&<NftItemList<T>>::get(self.id, token_id)
-				.ok_or("token not found")?
-				.const_data,
-		)
-		.into())
-	}
-}
-
-#[solidity_interface(name = "ERC721Enumerable", inline_is(InlineTotalSupply))]
-impl<T: Config> CollectionHandle<T> {
-	fn token_by_index(&self, index: uint256) -> Result<uint256> {
-		Ok(index)
-	}
-
-	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {
-		// TODO: Not implemetable
-		Err("not implemented".into())
-	}
-}
-
-#[derive(ToLog)]
-pub enum ERC721Events {
-	Transfer {
-		#[indexed]
-		from: address,
-		#[indexed]
-		to: address,
-		#[indexed]
-		token_id: uint256,
-	},
-	Approval {
-		#[indexed]
-		owner: address,
-		#[indexed]
-		approved: address,
-		#[indexed]
-		token_id: uint256,
-	},
-	#[allow(dead_code)]
-	ApprovalForAll {
-		#[indexed]
-		owner: address,
-		#[indexed]
-		operator: address,
-		approved: bool,
-	},
-}
-
-#[solidity_interface(name = "ERC721", is(ERC165), events(ERC721Events))]
-impl<T: Config> CollectionHandle<T> {
-	#[solidity(rename_selector = "balanceOf")]
-	fn balance_of_nft(&self, owner: address) -> Result<uint256> {
-		let owner = T::EvmAddressMapping::into_account_id(owner);
-		let balance = <Balance<T>>::get(self.id, owner);
-		Ok(balance.into())
-	}
-	fn owner_of(&self, token_id: uint256) -> Result<address> {
-		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
-		let token = <NftItemList<T>>::get(self.id, token_id).ok_or("unknown token")?;
-		Ok(*token.owner.as_eth())
-	}
-	fn safe_transfer_from_with_data(
-		&mut self,
-		_from: address,
-		_to: address,
-		_token_id: uint256,
-		_data: bytes,
-		_value: value,
-	) -> Result<void> {
-		// TODO: Not implemetable
-		Err("not implemented".into())
-	}
-	fn safe_transfer_from(
-		&mut self,
-		_from: address,
-		_to: address,
-		_token_id: uint256,
-		_value: value,
-	) -> Result<void> {
-		// TODO: Not implemetable
-		Err("not implemented".into())
-	}
-
-	fn transfer_from(
-		&mut self,
-		caller: caller,
-		from: address,
-		to: address,
-		token_id: uint256,
-		_value: value,
-	) -> Result<void> {
-		let caller = T::CrossAccountId::from_eth(caller);
-		let from = T::CrossAccountId::from_eth(from);
-		let to = T::CrossAccountId::from_eth(to);
-		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;
-
-		<Module<T>>::transfer_from_internal(&caller, &from, &to, self, token_id, 1)
-			.map_err(|_| "transferFrom error")?;
-		Ok(())
-	}
-
-	fn approve(
-		&mut self,
-		caller: caller,
-		approved: address,
-		token_id: uint256,
-		_value: value,
-	) -> Result<void> {
-		let caller = T::CrossAccountId::from_eth(caller);
-		let approved = T::CrossAccountId::from_eth(approved);
-		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;
-
-		<Module<T>>::approve_internal(&caller, &approved, self, token_id, 1)
-			.map_err(|_| "approve internal")?;
-		Ok(())
-	}
-
-	fn set_approval_for_all(
-		&mut self,
-		_caller: caller,
-		_operator: address,
-		_approved: bool,
-	) -> Result<void> {
-		// TODO: Not implemetable
-		Err("not implemented".into())
-	}
-
-	fn get_approved(&self, _token_id: uint256) -> Result<address> {
-		// TODO: Not implemetable
-		Err("not implemented".into())
-	}
-
-	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {
-		// TODO: Not implemetable
-		Err("not implemented".into())
-	}
-}
-
-#[solidity_interface(name = "ERC721Burnable")]
-impl<T: Config> CollectionHandle<T> {
-	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {
-		let caller = T::CrossAccountId::from_eth(caller);
-		let token_id = token_id.try_into().map_err(|_| "amount overflow")?;
-
-		<Module<T>>::burn_item_internal(&caller, &self, token_id, 1).map_err(|_| "burn error")?;
-		Ok(())
-	}
-}
-
-#[derive(ToLog)]
-pub enum ERC721MintableEvents {
-	#[allow(dead_code)]
-	MintingFinished {},
-}
-
-#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]
-impl<T: Config> CollectionHandle<T> {
-	fn minting_finished(&self) -> Result<bool> {
-		Ok(false)
-	}
-
-	fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {
-		let caller = T::CrossAccountId::from_eth(caller);
-		let to = T::CrossAccountId::from_eth(to);
-		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;
-		if <ItemListIndex>::get(self.id)
-			.checked_add(1)
-			.ok_or("item id overflow")?
-			!= token_id
-		{
-			return Err("item id should be next".into());
-		}
-
-		<Module<T>>::create_item_internal(
-			&caller,
-			&self,
-			&to,
-			CreateItemData::NFT(CreateNftData {
-				const_data: vec![].try_into().unwrap(),
-				variable_data: vec![].try_into().unwrap(),
-			}),
-		)
-		.map_err(|_| "mint error")?;
-		Ok(true)
-	}
-
-	#[solidity(rename_selector = "mintWithTokenURI")]
-	fn mint_with_token_uri(
-		&mut self,
-		caller: caller,
-		to: address,
-		token_id: uint256,
-		token_uri: string,
-	) -> Result<bool> {
-		let caller = T::CrossAccountId::from_eth(caller);
-		let to = T::CrossAccountId::from_eth(to);
-		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;
-		if <ItemListIndex>::get(self.id)
-			.checked_add(1)
-			.ok_or("item id overflow")?
-			!= token_id
-		{
-			return Err("item id should be next".into());
-		}
-
-		<Module<T>>::create_item_internal(
-			&caller,
-			&self,
-			&to,
-			CreateItemData::NFT(CreateNftData {
-				const_data: Vec::<u8>::from(token_uri)
-					.try_into()
-					.map_err(|_| "token uri is too long")?,
-				variable_data: vec![].try_into().unwrap(),
-			}),
-		)
-		.map_err(|_| "mint error")?;
-		Ok(true)
-	}
-
-	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {
-		Err("not implementable".into())
-	}
-}
-
-#[solidity_interface(name = "ERC721UniqueExtensions")]
-impl<T: Config> CollectionHandle<T> {
-	#[solidity(rename_selector = "transfer")]
-	fn transfer_nft(
-		&mut self,
-		caller: caller,
-		to: address,
-		token_id: uint256,
-		_value: value,
-	) -> Result<void> {
-		let caller = T::CrossAccountId::from_eth(caller);
-		let to = T::CrossAccountId::from_eth(to);
-		let token_id = token_id.try_into().map_err(|_| "amount overflow")?;
-
-		<Module<T>>::transfer_internal(&caller, &to, self, token_id, 1)
-			.map_err(|_| "transfer error")?;
-		Ok(())
-	}
-
-	fn next_token_id(&self) -> Result<uint256> {
-		Ok(ItemListIndex::get(self.id)
-			.checked_add(1)
-			.ok_or("item id overflow")?
-			.into())
-	}
-
-	fn set_variable_metadata(
-		&mut self,
-		caller: caller,
-		token_id: uint256,
-		data: bytes,
-	) -> Result<void> {
-		let caller = T::CrossAccountId::from_eth(caller);
-		let token_id = token_id.try_into().map_err(|_| "token id overflow")?;
-
-		<Module<T>>::set_variable_meta_data_internal(&caller, self, token_id, data)
-			.map_err(dispatch_to_evm::<T>)?;
-		Ok(())
-	}
-
-	fn get_variable_metadata(&self, token_id: uint256) -> Result<bytes> {
-		let token_id = token_id.try_into().map_err(|_| "token id overflow")?;
-
-		<Module<T>>::get_variable_metadata(self, token_id).map_err(dispatch_to_evm::<T>)
-	}
-
-	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {
-		let caller = T::CrossAccountId::from_eth(caller);
-		let to = T::CrossAccountId::from_eth(to);
-		let mut expected_index = <ItemListIndex>::get(self.id)
-			.checked_add(1)
-			.ok_or("item id overflow")?;
-
-		let total_tokens = token_ids.len();
-		for id in token_ids.into_iter() {
-			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;
-			if id != expected_index {
-				return Err("item id should be next".into());
-			}
-			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;
-		}
-
-		let data = (0..total_tokens)
-			.map(|_| {
-				CreateItemData::NFT(CreateNftData {
-					const_data: vec![].try_into().unwrap(),
-					variable_data: vec![].try_into().unwrap(),
-				})
-			})
-			.collect();
-
-		<Module<T>>::create_multiple_items_internal(&caller, self, &to, data)
-			.map_err(dispatch_to_evm::<T>)?;
-		Ok(true)
-	}
-
-	#[solidity(rename_selector = "mintBulkWithTokenURI")]
-	fn mint_bulk_with_token_uri(
-		&mut self,
-		caller: caller,
-		to: address,
-		tokens: Vec<(uint256, string)>,
-	) -> Result<bool> {
-		let caller = T::CrossAccountId::from_eth(caller);
-		let to = T::CrossAccountId::from_eth(to);
-		let mut expected_index = <ItemListIndex>::get(self.id)
-			.checked_add(1)
-			.ok_or("item id overflow")?;
-
-		let mut data = Vec::with_capacity(tokens.len());
-		for (id, token_uri) in tokens {
-			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;
-			if id != expected_index {
-				panic!("item id should be next ({}) but got {}", expected_index, id);
-			}
-			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;
-
-			data.push(CreateItemData::NFT(CreateNftData {
-				const_data: Vec::<u8>::from(token_uri)
-					.try_into()
-					.map_err(|_| "token uri is too long")?,
-				variable_data: vec![].try_into().unwrap(),
-			}));
-		}
-
-		<Module<T>>::create_multiple_items_internal(&caller, self, &to, data)
-			.map_err(dispatch_to_evm::<T>)?;
-		Ok(true)
-	}
-}
-
-#[solidity_interface(
-	name = "UniqueNFT",
-	is(
-		ERC165,
-		ERC721,
-		ERC721Metadata,
-		ERC721Enumerable,
-		ERC721UniqueExtensions,
-		ERC721Mintable,
-		ERC721Burnable,
-	)
-)]
-impl<T: Config> CollectionHandle<T> {}
-
-#[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",
-	inline_is(InlineNameSymbol, InlineTotalSupply),
-	events(ERC20Events)
-)]
-impl<T: Config> CollectionHandle<T> {
-	fn decimals(&self) -> Result<uint8> {
-		Ok(if let CollectionMode::Fungible(decimals) = &self.mode {
-			*decimals
-		} else {
-			unreachable!()
-		})
-	}
-	fn balance_of(&self, owner: address) -> Result<uint256> {
-		let owner = T::EvmAddressMapping::into_account_id(owner);
-		let balance = <Balance<T>>::get(self.id, owner);
-		Ok(balance.into())
-	}
-	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")?;
-
-		<Module<T>>::transfer_internal(&caller, &to, self, 1, amount)
-			.map_err(|_| "transfer error")?;
-		Ok(true)
-	}
-	#[solidity(rename_selector = "transferFrom")]
-	fn transfer_from_fungible(
-		&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")?;
-
-		<Module<T>>::transfer_from_internal(&caller, &from, &to, self, 1, amount)
-			.map_err(|_| "transferFrom error")?;
-		Ok(true)
-	}
-	#[solidity(rename_selector = "approve")]
-	fn approve_fungible(
-		&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")?;
-
-		<Module<T>>::approve_internal(&caller, &spender, self, 1, amount)
-			.map_err(|_| "approve internal")?;
-		Ok(true)
-	}
-	fn allowance(&self, owner: address, spender: address) -> Result<uint256> {
-		let owner = T::CrossAccountId::from_eth(owner);
-		let spender = T::CrossAccountId::from_eth(spender);
-
-		Ok(<Allowances<T>>::get(self.id, (1, owner.as_sub(), spender.as_sub())).into())
-	}
-}
-
-#[solidity_interface(name = "UniqueFungible", is(ERC165, ERC20))]
-impl<T: Config> CollectionHandle<T> {}
-
-// Not a tests, but code generators
-generate_stubgen!(nft_impl, UniqueNFTCall, true);
-generate_stubgen!(nft_iface, UniqueNFTCall, false);
-
-generate_stubgen!(fungible_impl, UniqueFungibleCall, true);
-generate_stubgen!(fungible_iface, UniqueFungibleCall, false);
modifiedpallets/nft/src/eth/mod.rsdiffbeforeafterboth
--- a/pallets/nft/src/eth/mod.rs
+++ b/pallets/nft/src/eth/mod.rs
@@ -1,86 +1,21 @@
-pub mod account;
-pub mod erc;
 pub mod sponsoring;
 
-use pallet_evm_coder_substrate::call_internal;
+use pallet_common::{
+	CollectionById,
+	erc::CommonEvmHandler,
+	eth::{map_eth_to_id, map_eth_to_token_id},
+};
+use pallet_fungible::FungibleHandle;
+use pallet_nonfungible::NonfungibleHandle;
+use pallet_refungible::{RefungibleHandle, erc::RefungibleTokenHandle};
 use sp_std::borrow::ToOwned;
 use sp_std::vec::Vec;
 use pallet_evm::{PrecompileOutput};
 use sp_core::{H160, U256};
-use frame_support::storage::StorageMap;
-use crate::{Config, CollectionById, CollectionHandle, CollectionId, CollectionMode};
-use erc::{UniqueFungibleCall, UniqueNFTCall};
+use crate::{CollectionMode, Config, dispatch::Dispatched};
+use pallet_common::CollectionHandle;
 
 pub struct NftErcSupport<T: Config>(core::marker::PhantomData<T>);
-
-// 0x17c4e6453Cc49AAAaEACA894e6D9683e00000001 - collection
-// TODO: Unhardcode prefix
-const ETH_ACCOUNT_PREFIX: [u8; 16] = [
-	0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e,
-];
-
-fn map_eth_to_id(eth: &H160) -> Option<CollectionId> {
-	if eth[0..16] != ETH_ACCOUNT_PREFIX {
-		return None;
-	}
-	let mut id_bytes = [0; 4];
-	id_bytes.copy_from_slice(&eth[16..20]);
-	Some(u32::from_be_bytes(id_bytes))
-}
-pub fn collection_id_to_address(id: u32) -> H160 {
-	let mut out = [0; 20];
-	out[0..16].copy_from_slice(&ETH_ACCOUNT_PREFIX);
-	out[16..20].copy_from_slice(&u32::to_be_bytes(id));
-	H160(out)
-}
-
-/*
-fn call_internal<T: Config>(
-	collection: &mut CollectionHandle<T>,
-	caller: caller,
-	method_id: u32,
-	mut input: AbiReader,
-	value: U256,
-) -> Result<Option<AbiWriter>> {
-	match collection.mode.clone() {
-		CollectionMode::Fungible(_) => {
-			call_internal();
-			let call = match UniqueFungibleCall::parse(method_id, &mut input)? {
-				Some(v) => v,
-				None => {
-					#[cfg(feature = "std")]
-					{
-						println!("Method not found");
-					}
-					return Ok(None);
-				}
-			};
-			Ok(Some(<CollectionHandle<T> as UniqueFungible>::call(
-				collection,
-				Msg {
-					call,
-					caller,
-					value,
-				},
-			)?))
-		}
-		CollectionMode::NFT => {
-			let call = match UniqueNFTCall::parse(method_id, &mut input)? {
-				Some(v) => v,
-				None => return Ok(None),
-			};
-			Ok(Some(<CollectionHandle<T> as UniqueNFT>::call(
-				collection,
-				Msg {
-					call,
-					caller,
-					value,
-				},
-			)?))
-		}
-		_ => Err("erc calls only supported to fungible and nft collections for now".into()),
-	}
-}*/
 
 impl<T: Config> pallet_evm::OnMethodCall<T> for NftErcSupport<T> {
 	fn is_reserved(target: &H160) -> bool {
@@ -92,20 +27,26 @@
 			.unwrap_or(false)
 	}
 	fn get_code(target: &H160) -> Option<Vec<u8>> {
-		map_eth_to_id(target)
-			.and_then(<CollectionById<T>>::get)
-			.map(|collection| {
+		if let Some(collection_id) = map_eth_to_id(target) {
+			let collection = <CollectionById<T>>::get(collection_id)?;
+			Some(
 				match collection.mode {
-					CollectionMode::NFT => include_bytes!("stubs/UniqueNFT.raw") as &[u8],
-					CollectionMode::Fungible(_) => {
-						include_bytes!("stubs/UniqueFungible.raw") as &[u8]
-					}
-					CollectionMode::ReFungible => {
-						include_bytes!("stubs/UniqueRefungible.raw") as &[u8]
-					}
+					CollectionMode::NFT => <NonfungibleHandle<T>>::CODE,
+					CollectionMode::Fungible(_) => <FungibleHandle<T>>::CODE,
+					CollectionMode::ReFungible => <RefungibleHandle<T>>::CODE,
 				}
-				.to_owned()
-			})
+				.to_owned(),
+			)
+		} else if let Some((collection_id, _token_id)) = map_eth_to_token_id(target) {
+			let collection = <CollectionById<T>>::get(collection_id)?;
+			if collection.mode != CollectionMode::ReFungible {
+				return None;
+			}
+			// TODO: check token existence
+			Some(<RefungibleTokenHandle<T>>::CODE.to_owned())
+		} else {
+			None
+		}
 	}
 	fn call(
 		source: &H160,
@@ -114,17 +55,26 @@
 		input: &[u8],
 		value: U256,
 	) -> Option<PrecompileOutput> {
-		let mut collection = map_eth_to_id(target)
-			.and_then(|id| <CollectionHandle<T>>::get_with_gas_limit(id, gas_limit))?;
-		let result = match collection.mode {
-			CollectionMode::NFT => {
-				call_internal::<UniqueNFTCall, _>(*source, &mut collection, value, input)
+		if let Some(collection_id) = map_eth_to_id(target) {
+			let collection = <CollectionHandle<T>>::new_with_gas_limit(collection_id, gas_limit)?;
+			let dispatched = Dispatched::dispatch(collection);
+
+			match dispatched {
+				Dispatched::Fungible(h) => h.call(source, input, value),
+				Dispatched::Nonfungible(h) => h.call(source, input, value),
+				Dispatched::Refungible(h) => h.call(source, input, value),
 			}
-			CollectionMode::Fungible(_) => {
-				call_internal::<UniqueFungibleCall, _>(*source, &mut collection, value, input)
+		} else if let Some((collection_id, token_id)) = map_eth_to_token_id(target) {
+			let collection = <CollectionHandle<T>>::new_with_gas_limit(collection_id, gas_limit)?;
+			if collection.mode != CollectionMode::ReFungible {
+				return None;
 			}
-			_ => return None,
-		};
-		collection.recorder.evm_to_precompile_output(result)
+
+			let handle = RefungibleHandle::cast(collection);
+			// TODO: check token existence
+			RefungibleTokenHandle(handle, token_id).call(source, input, value)
+		} else {
+			None
+		}
 	}
 }
modifiedpallets/nft/src/eth/sponsoring.rsdiffbeforeafterboth
--- a/pallets/nft/src/eth/sponsoring.rs
+++ b/pallets/nft/src/eth/sponsoring.rs
@@ -1,29 +1,30 @@
 //! Implements EVM sponsoring logic via OnChargeEVMTransaction
 
-use crate::{
-	Collection, CollectionById, Config, FungibleTransferBasket, NftTransferBasket,
-	eth::{account::EvmBackwardsAddressMapping, map_eth_to_id},
-};
+use crate::{Collection, Config, FungibleTransferBasket, NftTransferBasket};
 use evm_coder::{Call, abi::AbiReader};
 use frame_support::{
-	storage::{StorageMap, StorageDoubleMap},
+	storage::{StorageDoubleMap},
 };
+use pallet_common::eth::map_eth_to_id;
 use sp_core::H160;
 use sp_std::prelude::*;
 use up_sponsorship::SponsorshipHandler;
-use super::{
-	account::CrossAccountId,
-	erc::{UniqueFungibleCall, UniqueNFTCall, ERC721Call, ERC20Call, ERC721UniqueExtensionsCall},
-};
-use core::convert::TryInto;
 use core::marker::PhantomData;
-use nft_data_structs::{NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT};
+use core::convert::TryInto;
+use nft_data_structs::{CollectionId, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT};
+use pallet_common::{
+	CollectionById,
+	account::{CrossAccountId, EvmBackwardsAddressMapping},
+};
+
+use pallet_nonfungible::erc::{UniqueNFTCall, ERC721UniqueExtensionsCall, ERC721Call};
+use pallet_fungible::erc::{UniqueFungibleCall, ERC20Call};
 
 struct AnyError;
 
 fn try_sponsor<T: Config>(
 	caller: &H160,
-	collection_id: u32,
+	collection_id: CollectionId,
 	collection: &Collection<T>,
 	call: &[u8],
 ) -> Result<(), AnyError> {
deletedpallets/nft/src/eth/stubs/UniqueFungible.rawdiffbeforeafterboth

binary blob — no preview

deletedpallets/nft/src/eth/stubs/UniqueFungible.soldiffbeforeafterboth
--- a/pallets/nft/src/eth/stubs/UniqueFungible.sol
+++ /dev/null
@@ -1,121 +0,0 @@
-// SPDX-License-Identifier: OTHER
-// This code is automatically generated
-
-pragma solidity >=0.8.0 <0.9.0;
-
-// Common stubs holder
-contract Dummy {
-	uint8 dummy;
-	string stub_error = "this contract is implemented in native";
-}
-
-// Inline
-contract ERC20Events {
-	event Transfer(address indexed from, address indexed to, uint256 value);
-	event Approval(
-		address indexed owner,
-		address indexed spender,
-		uint256 value
-	);
-}
-
-// Inline
-contract InlineNameSymbol is Dummy {
-	// Selector: name() 06fdde03
-	function name() public view returns (string memory) {
-		require(false, stub_error);
-		dummy;
-		return "";
-	}
-
-	// Selector: symbol() 95d89b41
-	function symbol() public view returns (string memory) {
-		require(false, stub_error);
-		dummy;
-		return "";
-	}
-}
-
-// Inline
-contract InlineTotalSupply is Dummy {
-	// Selector: totalSupply() 18160ddd
-	function totalSupply() public view returns (uint256) {
-		require(false, stub_error);
-		dummy;
-		return 0;
-	}
-}
-
-contract ERC165 is Dummy {
-	// Selector: supportsInterface(bytes4) 01ffc9a7
-	function supportsInterface(uint32 interfaceId) public view returns (bool) {
-		require(false, stub_error);
-		interfaceId;
-		dummy;
-		return false;
-	}
-}
-
-contract ERC20 is Dummy, InlineNameSymbol, InlineTotalSupply, ERC20Events {
-	// Selector: decimals() 313ce567
-	function decimals() public view returns (uint8) {
-		require(false, stub_error);
-		dummy;
-		return 0;
-	}
-
-	// Selector: balanceOf(address) 70a08231
-	function balanceOf(address owner) public view returns (uint256) {
-		require(false, stub_error);
-		owner;
-		dummy;
-		return 0;
-	}
-
-	// Selector: transfer(address,uint256) a9059cbb
-	function transfer(address to, uint256 amount) public returns (bool) {
-		require(false, stub_error);
-		to;
-		amount;
-		dummy = 0;
-		return false;
-	}
-
-	// Selector: transferFrom(address,address,uint256) 23b872dd
-	function transferFrom(
-		address from,
-		address to,
-		uint256 amount
-	) public returns (bool) {
-		require(false, stub_error);
-		from;
-		to;
-		amount;
-		dummy = 0;
-		return false;
-	}
-
-	// Selector: approve(address,uint256) 095ea7b3
-	function approve(address spender, uint256 amount) public returns (bool) {
-		require(false, stub_error);
-		spender;
-		amount;
-		dummy = 0;
-		return false;
-	}
-
-	// Selector: allowance(address,address) dd62ed3e
-	function allowance(address owner, address spender)
-		public
-		view
-		returns (uint256)
-	{
-		require(false, stub_error);
-		owner;
-		spender;
-		dummy;
-		return 0;
-	}
-}
-
-contract UniqueFungible is Dummy, ERC165, ERC20 {}
deletedpallets/nft/src/eth/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

deletedpallets/nft/src/eth/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nft/src/eth/stubs/UniqueNFT.sol
+++ /dev/null
@@ -1,326 +0,0 @@
-// SPDX-License-Identifier: OTHER
-// This code is automatically generated
-
-pragma solidity >=0.8.0 <0.9.0;
-
-// Anonymous struct
-struct Tuple0 {
-	uint256 field_0;
-	string field_1;
-}
-
-// Common stubs holder
-contract Dummy {
-	uint8 dummy;
-	string stub_error = "this contract is implemented in native";
-}
-
-// Inline
-contract ERC721Events {
-	event Transfer(
-		address indexed from,
-		address indexed to,
-		uint256 indexed tokenId
-	);
-	event Approval(
-		address indexed owner,
-		address indexed approved,
-		uint256 indexed tokenId
-	);
-	event ApprovalForAll(
-		address indexed owner,
-		address indexed operator,
-		bool approved
-	);
-}
-
-// Inline
-contract ERC721MintableEvents {
-	event MintingFinished();
-}
-
-// Inline
-contract InlineNameSymbol is Dummy {
-	// Selector: name() 06fdde03
-	function name() public view returns (string memory) {
-		require(false, stub_error);
-		dummy;
-		return "";
-	}
-
-	// Selector: symbol() 95d89b41
-	function symbol() public view returns (string memory) {
-		require(false, stub_error);
-		dummy;
-		return "";
-	}
-}
-
-// Inline
-contract InlineTotalSupply is Dummy {
-	// Selector: totalSupply() 18160ddd
-	function totalSupply() public view returns (uint256) {
-		require(false, stub_error);
-		dummy;
-		return 0;
-	}
-}
-
-contract ERC165 is Dummy {
-	// Selector: supportsInterface(bytes4) 01ffc9a7
-	function supportsInterface(uint32 interfaceId) public view returns (bool) {
-		require(false, stub_error);
-		interfaceId;
-		dummy;
-		return false;
-	}
-}
-
-contract ERC721 is Dummy, ERC165, ERC721Events {
-	// Selector: balanceOf(address) 70a08231
-	function balanceOf(address owner) public view returns (uint256) {
-		require(false, stub_error);
-		owner;
-		dummy;
-		return 0;
-	}
-
-	// Selector: ownerOf(uint256) 6352211e
-	function ownerOf(uint256 tokenId) public view returns (address) {
-		require(false, stub_error);
-		tokenId;
-		dummy;
-		return 0x0000000000000000000000000000000000000000;
-	}
-
-	// Selector: safeTransferFromWithData(address,address,uint256,bytes) 60a11672
-	function safeTransferFromWithData(
-		address from,
-		address to,
-		uint256 tokenId,
-		bytes memory data
-	) public {
-		require(false, stub_error);
-		from;
-		to;
-		tokenId;
-		data;
-		dummy = 0;
-	}
-
-	// Selector: safeTransferFrom(address,address,uint256) 42842e0e
-	function safeTransferFrom(
-		address from,
-		address to,
-		uint256 tokenId
-	) public {
-		require(false, stub_error);
-		from;
-		to;
-		tokenId;
-		dummy = 0;
-	}
-
-	// Selector: transferFrom(address,address,uint256) 23b872dd
-	function transferFrom(
-		address from,
-		address to,
-		uint256 tokenId
-	) public {
-		require(false, stub_error);
-		from;
-		to;
-		tokenId;
-		dummy = 0;
-	}
-
-	// Selector: approve(address,uint256) 095ea7b3
-	function approve(address approved, uint256 tokenId) public {
-		require(false, stub_error);
-		approved;
-		tokenId;
-		dummy = 0;
-	}
-
-	// Selector: setApprovalForAll(address,bool) a22cb465
-	function setApprovalForAll(address operator, bool approved) public {
-		require(false, stub_error);
-		operator;
-		approved;
-		dummy = 0;
-	}
-
-	// Selector: getApproved(uint256) 081812fc
-	function getApproved(uint256 tokenId) public view returns (address) {
-		require(false, stub_error);
-		tokenId;
-		dummy;
-		return 0x0000000000000000000000000000000000000000;
-	}
-
-	// Selector: isApprovedForAll(address,address) e985e9c5
-	function isApprovedForAll(address owner, address operator)
-		public
-		view
-		returns (address)
-	{
-		require(false, stub_error);
-		owner;
-		operator;
-		dummy;
-		return 0x0000000000000000000000000000000000000000;
-	}
-}
-
-contract ERC721Burnable is Dummy {
-	// Selector: burn(uint256) 42966c68
-	function burn(uint256 tokenId) public {
-		require(false, stub_error);
-		tokenId;
-		dummy = 0;
-	}
-}
-
-contract ERC721Enumerable is Dummy, InlineTotalSupply {
-	// Selector: tokenByIndex(uint256) 4f6ccce7
-	function tokenByIndex(uint256 index) public view returns (uint256) {
-		require(false, stub_error);
-		index;
-		dummy;
-		return 0;
-	}
-
-	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
-	function tokenOfOwnerByIndex(address owner, uint256 index)
-		public
-		view
-		returns (uint256)
-	{
-		require(false, stub_error);
-		owner;
-		index;
-		dummy;
-		return 0;
-	}
-}
-
-contract ERC721Metadata is Dummy, InlineNameSymbol {
-	// Selector: tokenURI(uint256) c87b56dd
-	function tokenURI(uint256 tokenId) public view returns (string memory) {
-		require(false, stub_error);
-		tokenId;
-		dummy;
-		return "";
-	}
-}
-
-contract ERC721Mintable is Dummy, ERC721MintableEvents {
-	// Selector: mintingFinished() 05d2035b
-	function mintingFinished() public view returns (bool) {
-		require(false, stub_error);
-		dummy;
-		return false;
-	}
-
-	// Selector: mint(address,uint256) 40c10f19
-	function mint(address to, uint256 tokenId) public returns (bool) {
-		require(false, stub_error);
-		to;
-		tokenId;
-		dummy = 0;
-		return false;
-	}
-
-	// Selector: mintWithTokenURI(address,uint256,string) 50bb4e7f
-	function mintWithTokenURI(
-		address to,
-		uint256 tokenId,
-		string memory tokenUri
-	) public returns (bool) {
-		require(false, stub_error);
-		to;
-		tokenId;
-		tokenUri;
-		dummy = 0;
-		return false;
-	}
-
-	// Selector: finishMinting() 7d64bcb4
-	function finishMinting() public returns (bool) {
-		require(false, stub_error);
-		dummy = 0;
-		return false;
-	}
-}
-
-contract ERC721UniqueExtensions is Dummy {
-	// Selector: transfer(address,uint256) a9059cbb
-	function transfer(address to, uint256 tokenId) public {
-		require(false, stub_error);
-		to;
-		tokenId;
-		dummy = 0;
-	}
-
-	// Selector: nextTokenId() 75794a3c
-	function nextTokenId() public view returns (uint256) {
-		require(false, stub_error);
-		dummy;
-		return 0;
-	}
-
-	// Selector: setVariableMetadata(uint256,bytes) d4eac26d
-	function setVariableMetadata(uint256 tokenId, bytes memory data) public {
-		require(false, stub_error);
-		tokenId;
-		data;
-		dummy = 0;
-	}
-
-	// Selector: getVariableMetadata(uint256) e6c5ce6f
-	function getVariableMetadata(uint256 tokenId)
-		public
-		view
-		returns (bytes memory)
-	{
-		require(false, stub_error);
-		tokenId;
-		dummy;
-		return hex"";
-	}
-
-	// Selector: mintBulk(address,uint256[]) 44a9945e
-	function mintBulk(address to, uint256[] memory tokenIds)
-		public
-		returns (bool)
-	{
-		require(false, stub_error);
-		to;
-		tokenIds;
-		dummy = 0;
-		return false;
-	}
-
-	// Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006
-	function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)
-		public
-		returns (bool)
-	{
-		require(false, stub_error);
-		to;
-		tokens;
-		dummy = 0;
-		return false;
-	}
-}
-
-contract UniqueNFT is
-	Dummy,
-	ERC165,
-	ERC721,
-	ERC721Metadata,
-	ERC721Enumerable,
-	ERC721UniqueExtensions,
-	ERC721Mintable,
-	ERC721Burnable
-{}
deletedpallets/nft/src/eth/stubs/UniqueRefungible.rawdiffbeforeafterboth
--- a/pallets/nft/src/eth/stubs/UniqueRefungible.raw
+++ /dev/null
@@ -1 +0,0 @@
-TODO
\ No newline at end of file
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
69decl_error! {69decl_error! {
70 /// Error for non-fungible-token module.70 /// Error for non-fungible-token module.
71 pub enum Error for Module<T: Config> {71 pub enum Error for Module<T: Config> {
72 /// Total collections bound exceeded.
73 TotalCollectionsLimitExceeded,
74 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.72 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.
75 CollectionDecimalPointLimitExceeded,73 CollectionDecimalPointLimitExceeded,
76 /// Collection name can not be longer than 63 char.
77 CollectionNameLimitExceeded,
78 /// Collection description can not be longer than 255 char.
79 CollectionDescriptionLimitExceeded,
80 /// Token prefix can not be longer than 15 char.
81 CollectionTokenPrefixLimitExceeded,
82 /// This collection does not exist.
83 CollectionNotFound,
84 /// Item not exists.
85 TokenNotFound,
86 /// Admin not found
87 AdminNotFound,
88 /// Arithmetic calculation overflow.
89 NumOverflow,
90 /// Account already has admin role.
91 AlreadyAdmin,
92 /// You do not own this collection.
93 NoPermission,
94 /// This address is not set as sponsor, use setCollectionSponsor first.74 /// This address is not set as sponsor, use setCollectionSponsor first.
95 ConfirmUnsetSponsorFail,75 ConfirmUnsetSponsorFail,
96 /// Collection is not in mint mode.
97 PublicMintingNotAllowed,
98 /// Sender parameter and item owner must be equal.
99 MustBeTokenOwner,
100 /// Item balance not enough.
101 TokenValueTooLow,
102 /// Size of item is too large.
103 NftSizeLimitExceeded,
104 /// No approve found
105 ApproveNotFound,
106 /// Requested value more than approved.
107 TokenValueNotEnough,
108 /// Only approved addresses can call this method.
109 ApproveRequired,
110 /// Address is not in white list.
111 AddresNotInWhiteList,
112 /// Number of collection admins bound exceeded.
113 CollectionAdminsLimitExceeded,
114 /// Owned tokens by a single address bound exceeded.
115 AddressOwnershipLimitExceeded,
116 /// Length of items properties must be greater than 0.76 /// Length of items properties must be greater than 0.
117 EmptyArgument,77 EmptyArgument,
118 /// const_data exceeded data limit.
119 TokenConstDataLimitExceeded,
120 /// variable_data exceeded data limit.
121 TokenVariableDataLimitExceeded,
122 /// Not NFT item data used to mint in NFT collection.
123 NotNftDataUsedToMintNftCollectionToken,
124 /// Not Fungible item data used to mint in Fungible collection.
125 NotFungibleDataUsedToMintFungibleCollectionToken,
126 /// Not Re Fungible item data used to mint in Re Fungible collection.
127 NotReFungibleDataUsedToMintReFungibleCollectionToken,
128 /// Unexpected collection type.
129 UnexpectedCollectionType,
130 /// Can't store metadata in fungible tokens.
131 CantStoreMetadataInFungibleTokens,
132 /// Collection token limit exceeded
133 CollectionTokenLimitExceeded,
134 /// Account token limit exceeded per collection
135 AccountTokenLimitExceeded,
136 /// Collection limit bounds per collection exceeded78 /// Collection limit bounds per collection exceeded
137 CollectionLimitBoundsExceeded,79 CollectionLimitBoundsExceeded,
138 /// Tried to enable permissions which are only permitted to be disabled80 /// Tried to enable permissions which are only permitted to be disabled
139 OwnerPermissionsCantBeReverted,81 OwnerPermissionsCantBeReverted,
140 /// Schema data size limit bound exceeded
141 SchemaDataLimitExceeded,
142 /// Maximum refungibility exceeded
143 WrongRefungiblePieces,
144 /// createRefungible should be called with one owner
145 BadCreateRefungibleCall,
146 /// Gas limit exceeded
147 OutOfGas,
148 /// Metadata update denied by collection settings
149 MetadataUpdateDenied,
150 /// Metadata update flag become unmutable with None option
151 MetadataFlagFrozen,
152 /// Collection settings not allowing items transferring
153 TransferNotAllowed,
154 /// Can't transfer tokens to ethereum zero address
155 AddressIsZero,
156 }82 }
157}83}
158
159#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]
160pub struct CollectionHandle<T: Config> {
161 pub id: CollectionId,
162 collection: Collection<T>,
163 recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,
164}
165impl<T: Config> CollectionHandle<T> {
166 pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {
167 <CollectionById<T>>::get(id).map(|collection| Self {
168 id,
169 collection,
170 recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(
171 eth::collection_id_to_address(id),
172 gas_limit,
173 ),
174 })
175 }
176 pub fn get(id: CollectionId) -> Option<Self> {
177 Self::get_with_gas_limit(id, u64::MAX)
178 }
179 pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {
180 self.recorder.log_sub(log)
181 }
182 #[allow(dead_code)]
183 fn consume_gas(&self, gas: u64) -> DispatchResult {
184 self.recorder.consume_gas_sub(gas)
185 }
186 fn consume_sload(&self) -> DispatchResult {
187 self.recorder.consume_sload_sub()
188 }
189 fn consume_sstore(&self) -> DispatchResult {
190 self.recorder.consume_sstore_sub()
191 }
192 pub fn submit_logs(self) -> DispatchResult {
193 self.recorder.submit_logs()
194 }
195 pub fn save(self) -> DispatchResult {
196 self.recorder.submit_logs()?;
197 <CollectionById<T>>::insert(self.id, self.collection);
198 Ok(())
199 }
200}
201impl<T: Config> Deref for CollectionHandle<T> {
202 type Target = Collection<T>;
203
204 fn deref(&self) -> &Self::Target {
205 &self.collection
206 }
207}
208
209impl<T: Config> DerefMut for CollectionHandle<T> {
210 fn deref_mut(&mut self) -> &mut Self::Target {
211 &mut self.collection
212 }
213}
214
215pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {84pub trait Config:
85 system::Config
86 + pallet_evm_coder_substrate::Config
87 + pallet_common::Config
216 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;88 + pallet_nonfungible::Config
21789 + pallet_refungible::Config
90 + pallet_fungible::Config
91 + Sized
92{
218 /// Weight information for extrinsics in this pallet.93 /// Weight information for extrinsics in this pallet.
219 type WeightInfo: WeightInfo;94 type WeightInfo: WeightInfo;
22095}
221 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;
222 type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;
223
224 type CrossAccountId: CrossAccountId<Self::AccountId>;
225 type Currency: Currency<Self::AccountId>;
226 type CollectionCreationPrice: Get<
227 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,
228 >;
229 type TreasuryAccountId: Get<Self::AccountId>;
230}
23196
232type SelfWeightOf<T> = <T as Config>::WeightInfo;97type SelfWeightOf<T> = <T as Config>::WeightInfo;
23398
293 trait Store for Module<T: Config> as Nft {158 trait Store for Module<T: Config> as Nft {
294159
295 //#region Private members160 //#region Private members
296 /// Id of next collection
297 CreatedCollectionCount: u32;
298 /// Used for migrations161 /// Used for migrations
299 ChainVersion: u64;162 ChainVersion: u64;
300 /// Id of last collection token
301 /// Collection id (controlled?1)
302 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;
303 //#endregion163 //#endregion
304
305 //#region Bound counters
306 /// Amount of collections destroyed, used for total amount tracking with
307 /// CreatedCollectionCount
308 DestroyedCollectionCount: u32;
309 //#endregion
310
311 //#region Basic collections
312 /// Collection info
313 /// Collection id (controlled?1)
314 pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;
315 /// List of collection admins
316 /// Collection id (controlled?2)
317 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;
318 /// Whitelisted collection users
319 /// Collection id (controlled?2), user id (controlled?3)
320 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;
321 //#endregion
322
323 /// How many of collection items user have
324 /// Collection id (controlled?2), account id (real)
325 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;
326
327 /// Amount of items which spender can transfer out of owners account (via transferFrom)
328 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))
329 /// TODO: Off chain worker should remove from this map when token gets removed
330 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;
331
332 //#region Item collections
333 /// Collection id (controlled?2), token id (controlled?1)
334 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;
335 /// Collection id (controlled?2), owner (controlled?2)
336 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;
337 /// Collection id (controlled?2), token id (controlled?1)
338 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;
339 //#endregion
340
341 //#region Index list
342 /// Collection id (controlled?2), tokens owner (controlled?2)
343 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;
344 //#endregion
345164
346 //#region Tokens transfer rate limit baskets165 //#region Tokens transfer rate limit baskets
347 /// (Collection id (controlled?2), who created (real))166 /// (Collection id (controlled?2), who created (real))
359 /// Collection id (controlled?2), token id (controlled?2)178 /// Collection id (controlled?2), token id (controlled?2)
360 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;179 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;
361 }180 }
362 add_extra_genesis {
363 build(|config: &GenesisConfig<T>| {
364 // Modification of storage
365 for (_num, _c) in &config.collection_id {
366 <Module<T>>::init_collection(_c);
367 }
368
369 for (_num, _c, _i) in &config.nft_item_id {
370 <Module<T>>::init_nft_token(*_c, _i);
371 }
372
373 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {
374 <Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);
375 }
376
377 for (_num, _c, _i) in &config.refungible_item_id {
378 <Module<T>>::init_refungible_token(*_c, _i);
379 }
380 })
381 }
382}181}
383
384decl_event!(
385 pub enum Event<T>
386 where
387 AccountId = <T as frame_system::Config>::AccountId,
388 CrossAccountId = <T as Config>::CrossAccountId,
389 {
390 /// New collection was created
391 ///
392 /// # Arguments
393 ///
394 /// * collection_id: Globally unique identifier of newly created collection.
395 ///
396 /// * mode: [CollectionMode] converted into u8.
397 ///
398 /// * account_id: Collection owner.
399 CollectionCreated(CollectionId, u8, AccountId),
400
401 /// New item was created.
402 ///
403 /// # Arguments
404 ///
405 /// * collection_id: Id of the collection where item was created.
406 ///
407 /// * item_id: Id of an item. Unique within the collection.
408 ///
409 /// * recipient: Owner of newly created item
410 ItemCreated(CollectionId, TokenId, CrossAccountId),
411
412 /// Collection item was burned.
413 ///
414 /// # Arguments
415 ///
416 /// collection_id.
417 ///
418 /// item_id: Identifier of burned NFT.
419 ItemDestroyed(CollectionId, TokenId),
420
421 /// Item was transferred
422 ///
423 /// * collection_id: Id of collection to which item is belong
424 ///
425 /// * item_id: Id of an item
426 ///
427 /// * sender: Original owner of item
428 ///
429 /// * recipient: New owner of item
430 ///
431 /// * amount: Always 1 for NFT
432 Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),
433
434 /// * collection_id
435 ///
436 /// * item_id
437 ///
438 /// * sender
439 ///
440 /// * spender
441 ///
442 /// * amount
443 Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),
444 }
445);
446182
447decl_module! {183decl_module! {
448 pub struct Module<T: Config> for enum Call184 pub struct Module<T: Config> for enum Call
449 where185 where
450 origin: T::Origin186 origin: T::Origin
451 {187 {
452 fn deposit_event() = default;
453 const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;188 const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;
454 type Error = Error<T>;189 type Error = Error<T>;
455190