git.delta.rocks / unique-network / refs/commits / 90fcef986d5a

difftreelog

Merge pull request #158 from usetech-llc/feature/sponsorship-refactor

Greg Zaitsev2021-06-25parents: #73e89cc #c1a55f7.patch.diff
in: master
Refactor sponsorship logic to reduce cross-crate dependencies

24 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5113,6 +5113,7 @@
  "nft-data-structs",
  "pallet-aura",
  "pallet-balances",
+ "pallet-contract-helpers",
  "pallet-contracts",
  "pallet-contracts-primitives",
  "pallet-contracts-rpc-runtime-api",
@@ -5481,6 +5482,19 @@
 ]
 
 [[package]]
+name = "pallet-contract-helpers"
+version = "0.1.0"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "pallet-contracts",
+ "parity-scale-codec 2.1.3",
+ "sp-runtime",
+ "sp-std",
+ "up-sponsorship",
+]
+
+[[package]]
 name = "pallet-contracts"
 version = "3.0.0"
 source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.3#c94e0cdfe5556680dca1996004751eeb114755d7"
@@ -5871,6 +5885,7 @@
  "sp-io",
  "sp-runtime",
  "sp-std",
+ "up-sponsorship",
 ]
 
 [[package]]
@@ -5880,13 +5895,8 @@
  "frame-benchmarking",
  "frame-support",
  "frame-system",
- "nft-data-structs",
  "pallet-balances",
- "pallet-contracts",
- "pallet-nft",
  "pallet-nft-transaction-payment",
- "pallet-randomness-collective-flip",
- "pallet-timestamp",
  "pallet-transaction-payment",
  "parity-scale-codec 2.1.3",
  "serde",
@@ -5903,12 +5913,6 @@
  "frame-benchmarking",
  "frame-support",
  "frame-system",
- "nft-data-structs",
- "pallet-balances",
- "pallet-contracts",
- "pallet-nft",
- "pallet-randomness-collective-flip",
- "pallet-timestamp",
  "pallet-transaction-payment",
  "parity-scale-codec 2.1.3",
  "serde",
@@ -5916,6 +5920,7 @@
  "sp-io",
  "sp-runtime",
  "sp-std",
+ "up-sponsorship",
 ]
 
 [[package]]
@@ -5996,10 +6001,6 @@
  "frame-support",
  "frame-system",
  "log",
- "nft-data-structs",
- "pallet-contracts",
- "pallet-nft",
- "pallet-nft-transaction-payment",
  "parity-scale-codec 2.1.3",
  "serde",
  "sp-core",
@@ -6007,6 +6008,7 @@
  "sp-runtime",
  "sp-std",
  "substrate-test-utils",
+ "up-sponsorship",
 ]
 
 [[package]]
@@ -11760,6 +11762,13 @@
 checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
 
 [[package]]
+name = "up-sponsorship"
+version = "0.1.0"
+dependencies = [
+ "impl-trait-for-tuples 0.2.1",
+]
+
+[[package]]
 name = "url"
 version = "1.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,7 @@
 members = [
     'node/*',
     'pallets/*',
-    'primitives',
+    'primitives/*',
     'runtime',
     'crates/evm-coder',
     'crates/evm-coder-macros',
modifiednode/cli/Cargo.tomldiffbeforeafterboth
--- a/node/cli/Cargo.toml
+++ b/node/cli/Cargo.toml
@@ -284,7 +284,7 @@
 version = '3.0.0'
 
 [dependencies.nft-data-structs]
-path="../../primitives"
+path="../../primitives/nft"
 default-features = false
 
 ################################################################################
addedpallets/contract-helpers/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/pallets/contract-helpers/Cargo.toml
@@ -0,0 +1,32 @@
+[package]
+name = "pallet-contract-helpers"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies.codec]
+default-features = false
+features = ['derive']
+package = 'parity-scale-codec'
+version = '2.0.0'
+
+[dependencies.up-sponsorship]
+default-features = false
+path = '../../primitives/sponsorship'
+version = '0.1.0'
+
+[dependencies]
+frame-support = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+frame-system = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+pallet-contracts = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-runtime = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-std = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+
+[features]
+default = ["std"]
+std = [
+    "frame-support/std",
+    "frame-system/std",
+    "pallet-contracts/std",
+    "sp-runtime/std",
+    "sp-std/std",
+]
\ No newline at end of file
addedpallets/contract-helpers/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/contract-helpers/src/lib.rs
@@ -0,0 +1,261 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+
+pub use pallet::*;
+
+#[frame_support::pallet]
+pub mod pallet {
+	use frame_support::sp_runtime::traits::StaticLookup;
+	use frame_support::{pallet_prelude::*, traits::IsSubType};
+	use frame_system::pallet_prelude::*;
+	use pallet_contracts::chain_extension::UncheckedFrom;
+	use sp_runtime::{
+		traits::{DispatchInfoOf, Hash, PostDispatchInfoOf, SignedExtension},
+		transaction_validity,
+	};
+	use sp_std::vec::Vec;
+	use up_sponsorship::SponsorshipHandler;
+
+    #[pallet::error]
+    pub enum Error<T> {
+        /// Should be contract owner
+        NoPermission,
+    }
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config + pallet_contracts::Config {}
+
+	#[pallet::pallet]
+	#[pallet::generate_store(pub(super) trait Store)]
+	pub struct Pallet<T>(_);
+
+	#[pallet::storage]
+	pub(super) type Owner<T: Config> = StorageMap<
+		Hasher = Twox128,
+		Key = T::AccountId,
+		Value = T::AccountId,
+		QueryKind = ValueQuery,
+	>;
+
+	#[pallet::storage]
+	pub(super) type AllowlistEnabled<T: Config> =
+		StorageMap<Hasher = Twox128, Key = T::AccountId, Value = bool, QueryKind = ValueQuery>;
+
+	#[pallet::storage]
+	pub(super) type Allowlist<T: Config> = StorageDoubleMap<
+		Hasher1 = Twox128,
+		Key1 = T::AccountId,
+		Hasher2 = Twox64Concat,
+		Key2 = T::AccountId,
+		Value = bool,
+		QueryKind = ValueQuery,
+	>;
+
+	#[pallet::storage]
+	pub(super) type SelfSponsoring<T: Config> =
+		StorageMap<Hasher = Twox128, Key = T::AccountId, Value = bool, QueryKind = ValueQuery>;
+
+	#[pallet::storage]
+	pub(super) type SponsoringRateLimit<T: Config> = StorageMap<
+		Hasher = Twox128,
+		Key = T::AccountId,
+		Value = T::BlockNumber,
+		QueryKind = ValueQuery,
+	>;
+
+	#[pallet::storage]
+	pub(super) type SponsorBasket<T: Config> = StorageDoubleMap<
+		Hasher1 = Twox128,
+		Key1 = T::AccountId,
+		Hasher2 = Twox128,
+		Key2 = T::AccountId,
+		Value = T::BlockNumber,
+		QueryKind = ValueQuery,
+	>;
+
+	#[pallet::call]
+	impl<T: Config> Pallet<T> {
+		#[pallet::weight(0)]
+		fn toggle_sponsoring(
+			origin: OriginFor<T>,
+			contract: T::AccountId,
+			sponsoring: bool,
+		) -> DispatchResult {
+			let sender = ensure_signed(origin)?;
+			ensure!(<Owner<T>>::get(&contract) == sender, <Error<T>>::NoPermission);
+
+			if sponsoring {
+				<SelfSponsoring<T>>::insert(contract, true);
+			} else {
+				<SelfSponsoring<T>>::remove(contract);
+			}
+			Ok(())
+		}
+
+		#[pallet::weight(0)]
+		fn toggle_allowlist(
+			origin: OriginFor<T>,
+			contract: T::AccountId,
+			enabled: bool,
+		) -> DispatchResult {
+			let sender = ensure_signed(origin)?;
+			ensure!(<Owner<T>>::get(&contract) == sender, <Error<T>>::NoPermission);
+
+			if enabled {
+				<AllowlistEnabled<T>>::insert(contract, true);
+			} else {
+				<AllowlistEnabled<T>>::remove(contract);
+			}
+			Ok(())
+		}
+
+		#[pallet::weight(0)]
+		fn toggle_allowed(
+			origin: OriginFor<T>,
+			contract: T::AccountId,
+			user: T::AccountId,
+			allowed: bool,
+		) -> DispatchResult {
+			let sender = ensure_signed(origin)?;
+			ensure!(<Owner<T>>::get(&contract) == sender, <Error<T>>::NoPermission);
+
+			if allowed {
+				<Allowlist<T>>::insert(contract, user, true);
+			} else {
+				<Allowlist<T>>::remove(contract, user);
+			}
+			Ok(())
+		}
+
+		#[pallet::weight(0)]
+		fn set_sponsoring_rate_limit(
+			origin: OriginFor<T>,
+			contract: T::AccountId,
+			rate_limit: T::BlockNumber,
+		) -> DispatchResult {
+			let sender = ensure_signed(origin)?;
+			ensure!(<Owner<T>>::get(&contract) == sender, <Error<T>>::NoPermission);
+
+			<SponsoringRateLimit<T>>::insert(contract, rate_limit);
+			Ok(())
+		}
+	}
+
+	#[derive(Encode, Decode, Clone, PartialEq, Eq)]
+	pub struct ContractHelpersExtension<T>(PhantomData<T>);
+	impl<T> core::fmt::Debug for ContractHelpersExtension<T> {
+		fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
+			fmt.debug_struct("ContractHelpersExtension").finish()
+		}
+	}
+
+	type CodeHash<T> = <T as frame_system::Config>::Hash;
+	impl<T> SignedExtension for ContractHelpersExtension<T>
+	where
+		T: Config + Send + Sync,
+		T::Call: sp_runtime::traits::Dispatchable,
+		T::Call: IsSubType<pallet_contracts::Call<T>>,
+		T::AccountId: UncheckedFrom<T::Hash>,
+		T::AccountId: AsRef<[u8]>,
+	{
+		const IDENTIFIER: &'static str = "ContractHelpers";
+		type AccountId = T::AccountId;
+		type Call = T::Call;
+		type AdditionalSigned = ();
+		type Pre = Option<(Self::AccountId, CodeHash<T>, Vec<u8>)>;
+
+		fn additional_signed(&self) -> Result<(), transaction_validity::TransactionValidityError> {
+			Ok(())
+		}
+
+		fn validate(
+			&self,
+			who: &T::AccountId,
+			call: &Self::Call,
+			_info: &DispatchInfoOf<Self::Call>,
+			_len: usize,
+		) -> transaction_validity::TransactionValidity {
+			match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {
+				Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {
+					let called_contract: T::AccountId =
+						T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());
+					if <AllowlistEnabled<T>>::get(&called_contract) {
+						if !<Allowlist<T>>::get(&called_contract, who)
+							&& &<Owner<T>>::get(&called_contract) != who
+						{
+							return Err(transaction_validity::InvalidTransaction::Call.into());
+						}
+					}
+				}
+				_ => {}
+			}
+			Ok(transaction_validity::ValidTransaction::default())
+		}
+
+		fn pre_dispatch(
+			self,
+			who: &Self::AccountId,
+			call: &Self::Call,
+			_info: &DispatchInfoOf<Self::Call>,
+			_len: usize,
+		) -> Result<Self::Pre, TransactionValidityError> {
+			match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {
+				Some(pallet_contracts::Call::instantiate(_, _, code_hash, _, salt)) => {
+					Ok(Some((who.clone(), code_hash.clone(), salt.clone())))
+				}
+				Some(pallet_contracts::Call::instantiate_with_code(_, _, code, _, salt)) => {
+					let code_hash = &T::Hashing::hash(&code);
+					Ok(Some((who.clone(), code_hash.clone(), salt.clone())))
+				}
+				_ => Ok(None),
+			}
+		}
+
+		fn post_dispatch(
+			pre: Self::Pre,
+			_info: &DispatchInfoOf<Self::Call>,
+			_post_info: &PostDispatchInfoOf<Self::Call>,
+			_len: usize,
+			_result: &DispatchResult,
+		) -> Result<(), TransactionValidityError> {
+			if let Some((who, code_hash, salt)) = pre {
+				let new_contract_address =
+					<pallet_contracts::Pallet<T>>::contract_address(&who, &code_hash, &salt);
+				<Owner<T>>::insert(&new_contract_address, &who);
+			}
+
+			Ok(())
+		}
+	}
+
+	pub struct ContractSponsorshipHandler<T>(PhantomData<T>);
+	impl<T, C> SponsorshipHandler<T::AccountId, C> for ContractSponsorshipHandler<T>
+	where
+		T: Config,
+		C: IsSubType<pallet_contracts::Call<T>>,
+		T::AccountId: UncheckedFrom<T::Hash>,
+		T::AccountId: AsRef<[u8]>,
+	{
+		fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {
+			match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {
+				Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {
+					let called_contract: T::AccountId =
+						T::Lookup::lookup((*dest).clone()).unwrap_or_default();
+					if <SelfSponsoring<T>>::get(&called_contract) {
+						let last_tx_block = SponsorBasket::<T>::get(&called_contract, &who);
+						let block_number =
+							<frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+						let rate_limit = SponsoringRateLimit::<T>::get(&called_contract);
+						let limit_time = last_tx_block + rate_limit;
+
+						if block_number >= limit_time {
+							SponsorBasket::<T>::insert(&called_contract, who, block_number);
+							return Some(called_contract);
+						}
+					}
+				}
+				_ => {}
+			}
+			None
+		}
+	}
+}
modifiedpallets/nft-charge-transaction/Cargo.tomldiffbeforeafterboth
--- a/pallets/nft-charge-transaction/Cargo.toml
+++ b/pallets/nft-charge-transaction/Cargo.toml
@@ -23,19 +23,14 @@
 frame-support = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 frame-system = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 pallet-balances = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
-pallet-contracts = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
-pallet-timestamp = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 pallet-transaction-payment = { default-features = false, version = "3.0.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
-pallet-randomness-collective-flip = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 sp-std = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 frame-benchmarking = { default-features = false, version = "3.0.0", optional = true, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 sp-core = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 sp-io = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 sp-runtime = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 
-pallet-nft = { default-features = false, path="../nft" }
 pallet-nft-transaction-payment = { default-features = false, path="../nft-transaction-payment" }
-nft-data-structs = { default-features = false, path="../../primitives", version = "0.9.0" }
 
 [features]
 default = ['std']
@@ -45,15 +40,10 @@
     'frame-support/std',
     'frame-system/std',
     'pallet-balances/std',
-    'pallet-timestamp/std',
-    'pallet-randomness-collective-flip/std',
-    'pallet-contracts/std',
-    'pallet-nft/std',
     'pallet-transaction-payment/std',
     'pallet-nft-transaction-payment/std',
     'sp-std/std',
     'sp-runtime/std',
-    'nft-data-structs/std',
     'frame-benchmarking/std',
 ]
 runtime-benchmarks = ["frame-benchmarking"]
modifiedpallets/nft-charge-transaction/src/lib.rsdiffbeforeafterboth
--- a/pallets/nft-charge-transaction/src/lib.rs
+++ b/pallets/nft-charge-transaction/src/lib.rs
@@ -14,16 +14,10 @@
 #[cfg(feature = "runtime-benchmarks")]
 mod benchmarking;
 
-#[cfg(test)]
-mod tests;
-
 use codec::{Decode, Encode};
-use frame_support::traits::{ Get};
+use frame_support::traits::Get;
 use frame_support::{
 	decl_module, decl_storage,
-	traits::{
-		IsSubType, 
-	},
 	weights::{
 		DispatchInfo, PostDispatchInfo, DispatchClass
 	}
@@ -37,7 +31,6 @@
 	},
 	FixedPointOperand, DispatchResult
 };
-use pallet_contracts::chain_extension::UncheckedFrom;
 use pallet_transaction_payment::OnChargeTransaction;
 use sp_std::prelude::*;
 
@@ -83,10 +76,8 @@
 
 impl<T: Config> ChargeTransactionPayment<T>
 where
-	T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<pallet_nft::Call<T>> + IsSubType<pallet_contracts::Call<T>>,
+	T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo>,
     BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,
-    T::AccountId: AsRef<[u8]>,
-    T::AccountId: UncheckedFrom<T::Hash>,
 {
     fn traditional_fee(
         len: usize,
@@ -134,13 +125,6 @@
 			.map(|i| (fee, i));
         }
 
-        // check errors
-        let _error = <pallet_nft_transaction_payment::Module<T>>::check_error(who, call);
-        match _error {
-            Err(_error) => return Err(_error),
-            Ok(_error) => {}
-        };
-
         // Determine who is paying transaction fee based on ecnomic model
 		// Parse call to extract collection ID and access collection sponsor	
 		let sponsor = <pallet_nft_transaction_payment::Module<T>>::withdraw_type(who, call);
@@ -156,9 +140,7 @@
     for ChargeTransactionPayment<T>
 where
     BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,
-	T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<pallet_nft::Call<T>> + IsSubType<pallet_contracts::Call<T>>,
-    T::AccountId: AsRef<[u8]>,
-    T::AccountId: UncheckedFrom<T::Hash>,
+	T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo>,
 {
     const IDENTIFIER: &'static str = "ChargeTransactionPayment";
     type AccountId = T::AccountId;
@@ -209,7 +191,7 @@
         _result: &DispatchResult,
     ) -> Result<(), TransactionValidityError> {
 		let (tip, who, imbalance) = pre;
-		let actual_fee = pallet_transaction_payment::Module::<T>::compute_actual_fee(
+		let actual_fee = pallet_transaction_payment::Pallet::<T>::compute_actual_fee(
 			len as u32,
 			info,
 			post_info,
modifiedpallets/nft-transaction-payment/Cargo.tomldiffbeforeafterboth
--- a/pallets/nft-transaction-payment/Cargo.toml
+++ b/pallets/nft-transaction-payment/Cargo.toml
@@ -22,19 +22,14 @@
 serde = { version = "1.0.119", default-features = false }
 frame-support = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 frame-system = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
-pallet-balances = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
-pallet-contracts = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
-pallet-timestamp = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 pallet-transaction-payment = { default-features = false, version = "3.0.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
-pallet-randomness-collective-flip = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 sp-std = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 frame-benchmarking = { default-features = false, version = "3.0.0", optional = true, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 sp-core = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 sp-io = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 sp-runtime = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 
-pallet-nft = { default-features = false, path="../nft" }
-nft-data-structs = { default-features = false, path="../../primitives", version = "0.9.0" }
+up-sponsorship = { default-features = false, path = "../../primitives/sponsorship", version = "0.1.0" }
 
 [features]
 default = ['std']
@@ -43,15 +38,13 @@
     'serde/std',
     'frame-support/std',
     'frame-system/std',
-    'pallet-balances/std',
-    'pallet-timestamp/std',
-    'pallet-randomness-collective-flip/std',
-    'pallet-contracts/std',
-    'pallet-nft/std',
+    'sp-core/std',
+    'sp-io/std',
     'pallet-transaction-payment/std',
     'sp-std/std',
     'sp-runtime/std',
-    'nft-data-structs/std',
     'frame-benchmarking/std',
+
+    'up-sponsorship/std',
 ]
 runtime-benchmarks = ["frame-benchmarking"]
modifiedpallets/nft-transaction-payment/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/nft-transaction-payment/src/benchmarking.rs
+++ b/pallets/nft-transaction-payment/src/benchmarking.rs
@@ -1,7 +1,6 @@
 #![cfg(feature = "runtime-benchmarks")]
 
 use super::*;
-use crate::Module as NftTransactionPayment;
 
 use sp_std::prelude::*;
 use frame_system::RawOrigin;
modifiedpallets/nft-transaction-payment/src/lib.rsdiffbeforeafterboth
--- a/pallets/nft-transaction-payment/src/lib.rs
+++ b/pallets/nft-transaction-payment/src/lib.rs
@@ -14,388 +14,32 @@
 #[cfg(feature = "runtime-benchmarks")]
 mod benchmarking;
 
-#[cfg(test)]
-mod tests;
-
-use frame_support::{
-	decl_error, decl_module, decl_storage,
-	traits::{
-		IsSubType, 
-	},
-	weights::{
-		DispatchInfo
-	}
-};
-use sp_runtime::traits::StaticLookup;
-use sp_runtime::{
-	traits::{ 
-		Hash, Dispatchable,
-	},
-	transaction_validity::{
-        InvalidTransaction, TransactionValidityError,
-    },
-};
-use pallet_contracts::chain_extension::UncheckedFrom;
+use frame_support::{decl_module, decl_storage};
 use sp_std::prelude::*;
-use nft_data_structs::{
-	CreateItemData,
-	CollectionId, CollectionMode, TokenId
-};
-
-type CodeHash<T> = <T as frame_system::Config>::Hash;
+use up_sponsorship::SponsorshipHandler;
 
-	pub trait Config: frame_system::Config + pallet_contracts::Config + pallet_transaction_payment::Config + pallet_nft::Config {
-	}
-
-	// Error for non-fungible-token module.
-	
-	decl_error! {
-		/// Error for non-fungible-token module.
-		pub enum Error for Module<T: Config> {
-		/// No available class ID
-		NoAvailableClassId,
-		/// No available token ID
-		NoAvailableTokenId,
-		/// Token(ClassId, TokenId) not found
-		TokenNotFound,
-		/// Class not found
-		CollectionNotFound,
-		/// The operator is not the owner of the token and has no permission
-		NoPermission,
-		/// Arithmetic calculation overflow
-		NumOverflow,
-		/// Can not destroy class
-		/// Total issuance is not 0
-		CannotDestroyClass,
-	}
+pub trait Config: frame_system::Config + pallet_transaction_payment::Config {
+	type SponsorshipHandler: SponsorshipHandler<Self::AccountId, Self::Call>;
 }
-
-	decl_storage! {
-		trait Store for Module<T: Config> as NftTransactionPayment{
 
+decl_storage! {
+	trait Store for Module<T: Config> as NftTransactionPayment{
 	}
 }
 
 decl_module! {
-
 	pub struct Module<T: Config> for enum Call 
     where 
 		origin: T::Origin,
     {
 	}
 }
-
-impl<T: Config> Module<T> 
-{
 
-	pub fn check_error(
-		who: &T::AccountId,
-		call: &T::Call
-	) -> Result<bool, TransactionValidityError> where 
-		T::Call: Dispatchable<Info=DispatchInfo>,
-		T::Call: IsSubType<pallet_nft::Call<T>>, 
-		T::Call: IsSubType<pallet_contracts::Call<T>>,
-		T::AccountId: AsRef<[u8]>,
-		T::AccountId: UncheckedFrom<T::Hash>
-		{
-
-			match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {
-				Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {
-	
-					let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());
-	
-					let owned_contract = pallet_nft::ContractOwner::<T>::get(called_contract.clone()).as_ref() == Some(who);
-					let white_list_enabled = pallet_nft::ContractWhiteListEnabled::<T>::contains_key(called_contract.clone());
-					  
-					if !owned_contract && white_list_enabled {
-						if !pallet_nft::ContractWhiteList::<T>::contains_key(called_contract.clone(), who) {
-							return Err(InvalidTransaction::Call.into());
-						}
-					}
-					Ok(true)
-				},
-				_ => { Ok(true) },
-			}
-		}
-
+impl<T: Config> Module<T> {
 	pub fn withdraw_type(
 		who: &T::AccountId,
 		call: &T::Call
-	) -> Option<T::AccountId> where 
-		T::Call: Dispatchable<Info=DispatchInfo>,
-		T::Call: IsSubType<pallet_nft::Call<T>>, 
-		T::Call: IsSubType<pallet_contracts::Call<T>>,
-		T::AccountId: AsRef<[u8]>,
-		T::AccountId: UncheckedFrom<T::Hash>
-		{
-
-        let mut sponsor: Option<T::AccountId> = match IsSubType::<pallet_nft::Call<T>>::is_sub_type(call)  {
-            Some(pallet_nft::Call::create_item(collection_id, _owner, _properties)) => {
-
-                Self::withdraw_create_item(who, collection_id, &_properties)
-            },
-            Some(pallet_nft::Call::transfer(_new_owner, collection_id, item_id, _value)) => {
-
-                Self::withdraw_transfer(who, collection_id, item_id)
-            },
-            Some(pallet_nft::Call::set_variable_meta_data(collection_id, item_id, data)) => {
-
-                Self::withdraw_set_variable_meta_data(collection_id, item_id, &data)
-			},
-			_ => None,
-        };
-
-        sponsor = sponsor.or_else(|| match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {
-            Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {
-
-                Self::withdraw_contract_call(who, dest)
-            },
-            Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {
-
-                Self::withdraw_contract_instantiate(&who, code_hash, salt)
-            },
-            Some(pallet_contracts::Call::instantiate_with_code(_endowment, _gas_limit, _code, _data, _salt))  => {
-
-                Self::withdraw_contract_instantiate(&who, &T::Hashing::hash(&_code), _salt)
-			},
-			_ => None,
-        });
-
-		sponsor
-	}
-
-
-
-	pub fn withdraw_create_item(
-		who: &T::AccountId,
-		collection_id: &CollectionId,
-		_properties: &CreateItemData,
 	) -> Option<T::AccountId> {
-	
-		let collection = pallet_nft::CollectionById::<T>::get(collection_id)?;
-
-		// sponsor timeout
-		let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
-
-		let limit = collection.limits.sponsor_transfer_timeout;
-		if pallet_nft::CreateItemBasket::<T>::contains_key((collection_id, &who)) {
-			let last_tx_block = pallet_nft::CreateItemBasket::<T>::get((collection_id, &who));
-			let limit_time = last_tx_block + limit.into();
-			if block_number <= limit_time {
-				return None;
-			}
-		}
-		pallet_nft::CreateItemBasket::<T>::insert((collection_id, who.clone()), block_number);
-
-		// check free create limit
-		if collection.limits.sponsored_data_size >= (_properties.len() as u32) {
-			collection.sponsorship.sponsor()
-				.cloned()
-		} else {
-			None
-		}
-	}
-
-	pub fn withdraw_transfer(
-		who: &T::AccountId,
-		collection_id: &CollectionId,
-		item_id: &TokenId,
-	) -> Option<T::AccountId> {
-
-		let collection = pallet_nft::CollectionById::<T>::get(collection_id)?;
-		let limits = pallet_nft::ChainLimit::get();
-
-		let mut sponsor_transfer = false;
-		if collection.sponsorship.confirmed() {
-
-			let collection_limits = collection.limits.clone();
-			let collection_mode = collection.mode.clone();
-
-			// sponsor timeout
-			let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
-			sponsor_transfer = match collection_mode {
-				CollectionMode::NFT => {
-
-					// get correct limit
-					let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
-						collection_limits.sponsor_transfer_timeout
-					} else {
-						limits.nft_sponsor_transfer_timeout
-					};
-
-					let mut sponsored = true;
-					if pallet_nft::NftTransferBasket::<T>::contains_key(collection_id, item_id) {
-						let last_tx_block = pallet_nft::NftTransferBasket::<T>::get(collection_id, item_id);
-						let limit_time = last_tx_block + limit.into();
-						if block_number <= limit_time {
-							sponsored = false;
-						}
-					}
-					if sponsored {
-						pallet_nft::NftTransferBasket::<T>::insert(collection_id, item_id, block_number);
-					}
-
-					sponsored
-				}
-				CollectionMode::Fungible(_) => {
-
-					// get correct limit
-					let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
-						collection_limits.sponsor_transfer_timeout
-					} else {
-						limits.fungible_sponsor_transfer_timeout
-					};
-
-					let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
-					let mut sponsored = true;
-					if pallet_nft::FungibleTransferBasket::<T>::contains_key(collection_id, who) {
-						let last_tx_block = pallet_nft::FungibleTransferBasket::<T>::get(collection_id, who);
-						let limit_time = last_tx_block + limit.into();
-						if block_number <= limit_time {
-							sponsored = false;
-						}
-					}
-					if sponsored {
-						pallet_nft::FungibleTransferBasket::<T>::insert(collection_id, who, block_number);
-					}
-
-					sponsored
-				}
-				CollectionMode::ReFungible => {
-
-					// get correct limit
-					let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
-						collection_limits.sponsor_transfer_timeout
-					} else {
-						limits.refungible_sponsor_transfer_timeout
-					};
-
-					let mut sponsored = true;
-					if pallet_nft::ReFungibleTransferBasket::<T>::contains_key(collection_id, item_id) {
-						let last_tx_block = pallet_nft::ReFungibleTransferBasket::<T>::get(collection_id, item_id);
-						let limit_time = last_tx_block + limit.into();
-						if block_number <= limit_time {
-							sponsored = false;
-						}
-					}
-					if sponsored {
-						pallet_nft::ReFungibleTransferBasket::<T>::insert(collection_id, item_id, block_number);
-					}
-
-					sponsored
-				}
-				_ => {
-					false
-				},
-			};
-		}
-
-		if !sponsor_transfer {
-			None
-		} else {
-			collection.sponsorship.sponsor()
-				.cloned()
-		}
-	}
-	
-	pub fn withdraw_set_variable_meta_data(
-		collection_id: &CollectionId,
-		item_id: &TokenId,
-		data: &Vec<u8>,
-	) -> Option<T::AccountId> {
-
-		let mut sponsor_metadata_changes = false;
-
-		let collection = pallet_nft::CollectionById::<T>::get(collection_id)?;
-
-		if
-			collection.sponsorship.confirmed() &&
-			// Can't sponsor fungible collection, this tx will be rejected
-			// as invalid
-			!matches!(collection.mode, CollectionMode::Fungible(_)) &&
-			data.len() <= collection.limits.sponsored_data_size as usize
-		{
-			if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {
-				let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
-
-				if pallet_nft::VariableMetaDataBasket::<T>::get(collection_id, item_id)
-					.map(|last_block| block_number - last_block > rate_limit)
-					.unwrap_or(true) 
-				{
-					sponsor_metadata_changes = true;
-					pallet_nft::VariableMetaDataBasket::<T>::insert(collection_id, item_id, block_number);
-				}
-			}
-		}
-
-		if !sponsor_metadata_changes {
-			None
-		} else {
-			collection.sponsorship.sponsor().cloned()
-		}
-
-	}
-
-	pub fn withdraw_contract_call(
-		who: &T::AccountId,
-		dest: &<T::Lookup as StaticLookup>::Source
-	) -> Option<T::AccountId> {
-
-		let called_contract: T::AccountId = T::Lookup::lookup((dest).clone()).unwrap_or(T::AccountId::default());
-
-		let owned_contract = pallet_nft::ContractOwner::<T>::get(called_contract.clone()).as_ref() == Some(who);
-		let white_list_enabled = pallet_nft::ContractWhiteListEnabled::<T>::contains_key(called_contract.clone());
-		  
-		// ???
-		if !owned_contract && white_list_enabled {
-		 	if !pallet_nft::ContractWhiteList::<T>::contains_key(called_contract.clone(), who) {
-				return Some(who.clone())
-		 		// return Err(InvalidTransaction::Call.into());
-		 	}
-		}
-
-		let mut sponsor_transfer = false;
-		if pallet_nft::ContractSponsoringRateLimit::<T>::contains_key(called_contract.clone()) {
-			let last_tx_block = pallet_nft::ContractSponsorBasket::<T>::get((&called_contract, &who));
-			let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
-			let rate_limit = pallet_nft::ContractSponsoringRateLimit::<T>::get(&called_contract);
-			let limit_time = last_tx_block + rate_limit;
-
-			if block_number >= limit_time {
-				pallet_nft::ContractSponsorBasket::<T>::insert((called_contract.clone(), who.clone()), block_number);
-				sponsor_transfer = true;
-			}
-		} else {
-			sponsor_transfer = false;
-		}
-	   
-		if sponsor_transfer {
-			if pallet_nft::ContractSelfSponsoring::<T>::contains_key(called_contract.clone()) {
-				if pallet_nft::ContractSelfSponsoring::<T>::get(called_contract.clone()) {
-					return Some(called_contract);
-				}
-			}
-		}
-
-		None
-	}
-
-	pub fn withdraw_contract_instantiate(
-		who: &T::AccountId,
-		code_hash: &CodeHash<T>,
-		salt: &[u8],
-	) -> Option<T::AccountId> where
-	T::AccountId: AsRef<[u8]>,
-	T::AccountId: UncheckedFrom<T::Hash>
-	{
-
-		let new_contract_address = <pallet_contracts::Pallet<T>>::contract_address(
-			&who,
-			code_hash,
-			salt,
-		);
-		pallet_nft::ContractOwner::<T>::insert(new_contract_address.clone(), who.clone());
-
-		None
+		T::SponsorshipHandler::get_sponsor(who, call)
 	}
 }
\ No newline at end of file
deletedpallets/nft-transaction-payment/src/tests.rsdiffbeforeafterboth
--- a/pallets/nft-transaction-payment/src/tests.rs
+++ /dev/null
@@ -1,241 +0,0 @@
-#[cfg(test)]
-mod tests {
-	use crate as pallet_inflation;
-
-	use frame_system;
-	use frame_support::{traits::{Currency}, parameter_types};
-	use frame_support::{traits::OnInitialize};
-	use sp_core::H256;
-	use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, testing::Header};
-
-	type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
-	type Block = frame_system::mocking::MockBlock<Test>;
-
-	const YEAR: u64 = 5_259_600;
-
-	parameter_types! {
-		pub const ExistentialDeposit: u64 = 1;
-		pub const MaxLocks: u32 = 50;
-	}
-	
-	impl pallet_balances::Config for Test {
-		type AccountStore = System;
-		type Balance = u64;
-		type DustRemoval = ();
-		type Event = ();
-		type ExistentialDeposit = ExistentialDeposit;
-		type WeightInfo = ();
-		type MaxLocks = MaxLocks;
-	}
-	
-	frame_support::construct_runtime!(
-		pub enum Test where
-			Block = Block,
-			NodeBlock = Block,
-			UncheckedExtrinsic = UncheckedExtrinsic,
-		{
-			Balances: pallet_balances::{Module, Call, Storage},
-			System: frame_system::{Module, Call, Config, Storage, Event<T>},
-			Inflation: pallet_inflation::{Module, Call, Storage},
-		}
-	);
-
-	parameter_types! {
-		pub const BlockHashCount: u64 = 250;
-		pub BlockWeights: frame_system::limits::BlockWeights =
-			frame_system::limits::BlockWeights::simple_max(1024);
-		pub const SS58Prefix: u8 = 42;
-	}
-
-	impl frame_system::Config for Test {
-		type BaseCallFilter = ();
-		type BlockWeights = ();
-		type BlockLength = ();
-		type DbWeight = ();
-		type Origin = Origin;
-		type Call = Call;
-		type Index = u64;
-		type BlockNumber = u64;
-		type Hash = H256;
-		type Hashing = BlakeTwo256;
-		type AccountId = u64;
-		type Lookup = IdentityLookup<Self::AccountId>;
-		type Header = Header;
-		type Event = ();
-		type BlockHashCount = BlockHashCount;
-		type Version = ();
-		type PalletInfo = PalletInfo;
-		type AccountData = pallet_balances::AccountData<u64>;
-		type OnNewAccount = ();
-		type OnKilledAccount = ();
-		type SystemWeightInfo = ();
-		type SS58Prefix = SS58Prefix;
-	}
-
-	parameter_types! {
-		pub TreasuryAccountId: u64 = 1234;
-		pub const InflationBlockInterval: u32 = 100; // every time per how many blocks inflation is applied
-	}
-		
-	impl pallet_inflation::Config for Test {
-		type Currency = Balances;
-		type TreasuryAccountId = TreasuryAccountId;
-		type InflationBlockInterval = InflationBlockInterval;
-	}
-
-	// Build genesis storage according to the mock runtime.
-	pub fn new_test_ext() -> sp_io::TestExternalities {
-		frame_system::GenesisConfig::default().build_storage::<Test>().unwrap().into()
-	}
-
-	#[test]
-	fn inflation_works() {
-		new_test_ext().execute_with(|| {
-			// Total issuance = 1_000_000_000
-			let initial_issuance: u64 = 1_000_000_000;
-			let _ = <Balances as Currency<_>>::deposit_creating(&1234, initial_issuance);
-			assert_eq!(Balances::free_balance(1234), initial_issuance);
-
-			// BlockInflation should be set after 1st block and 
-			// first inflation deposit should be equal to BlockInflation
-			Inflation::on_initialize(1);
-			assert!(Inflation::block_inflation() > 0);
-			assert_eq!(Balances::free_balance(1234) - initial_issuance, Inflation::block_inflation());
-		});
-	}
-
-	#[test]
-	fn inflation_second_deposit() {
-		new_test_ext().execute_with(|| {
-			// Total issuance = 1_000_000_000
-			let initial_issuance: u64 = 1_000_000_000;
-			let _ = <Balances as Currency<_>>::deposit_creating(&1234, initial_issuance);
-			assert_eq!(Balances::free_balance(1234), initial_issuance);
-			Inflation::on_initialize(1);
-
-			// Next inflation deposit happens when block is multiple of InflationBlockInterval
-			let mut block: u32 = 2;
-			let balance_before: u64 = Balances::free_balance(1234);
-			while block % InflationBlockInterval::get() != 0 {
-				Inflation::on_initialize(block as u64);
-				block += 1;
-			}
-			let balance_just_before: u64 = Balances::free_balance(1234);
-			assert_eq!(balance_before, balance_just_before);
-
-			// The block with inflation
-			Inflation::on_initialize(block as u64);
-			let balance_after: u64 = Balances::free_balance(1234);
-			assert_eq!(balance_after - balance_just_before, Inflation::block_inflation());
-		});
-	}
-
-	#[test]
-	fn inflation_in_1_year() {
-		new_test_ext().execute_with(|| {
-			// Total issuance = 1_000_000_000
-			let initial_issuance: u64 = 1_000_000_000;
-			let _ = <Balances as Currency<_>>::deposit_creating(&1234, initial_issuance);
-			assert_eq!(Balances::free_balance(1234), initial_issuance);
-			Inflation::on_initialize(1);
-			let block_inflation_year_0 = Inflation::block_inflation();
-
-			Inflation::on_initialize(YEAR);
-			let block_inflation_year_1 = Inflation::block_inflation();
-
-			// Assert that year 1 inflation is less than year 0
-			assert!(block_inflation_year_0 > block_inflation_year_1);
-		});
-	}
-
-	#[test]
-	fn inflation_in_1_to_9_years() {
-		new_test_ext().execute_with(|| {
-			// Total issuance = 1_000_000_000
-			let initial_issuance: u64 = 1_000_000_000;
-			let _ = <Balances as Currency<_>>::deposit_creating(&1234, initial_issuance);
-			assert_eq!(Balances::free_balance(1234), initial_issuance);
-			Inflation::on_initialize(1);
-
-			for year in 1..=9 {
-				let block_inflation_year_before = Inflation::block_inflation();
-				Inflation::on_initialize(YEAR * year);
-				let block_inflation_year_after = Inflation::block_inflation();
-
-				// Assert that next year inflation is less than previous year inflation
-				assert!(block_inflation_year_before > block_inflation_year_after);
-			}
-
-		});
-	}
-
-	#[test]
-	fn inflation_after_year_10_is_flat() {
-		new_test_ext().execute_with(|| {
-			// Total issuance = 1_000_000_000
-			let initial_issuance: u64 = 1_000_000_000;
-			let _ = <Balances as Currency<_>>::deposit_creating(&1234, initial_issuance);
-			assert_eq!(Balances::free_balance(1234), initial_issuance);
-			Inflation::on_initialize(YEAR * 9);
-
-			for year in 10..=20 {
-				let block_inflation_year_before = Inflation::block_inflation();
-				Inflation::on_initialize(YEAR * year);
-				let block_inflation_year_after = Inflation::block_inflation();
-
-				// Assert that next year inflation is equal to previous year inflation
-				assert_eq!(block_inflation_year_before, block_inflation_year_after);
-			}
-		});
-	}
-
-	#[test]
-	fn inflation_rate_by_year() {
-		new_test_ext().execute_with(|| {
-			let payouts: u64 = YEAR / InflationBlockInterval::get() as u64;
-
-			// Inflation starts at 10% and does down by 2/3% every year until year 9 (included), 
-			// then it is flat.
-			let payout_by_year: [u64; 11] = [
-				1000,
-				933,
-				867,
-				800,
-				733,
-				667,
-				600,
-				533,
-				467,
-				400,
-				400
-			];
-
-			// For accuracy total issuance = payout0 * payouts * 10;
-			let initial_issuance: u64 = payout_by_year[0] * payouts * 10;
-			let _ = <Balances as Currency<_>>::deposit_creating(&1234, initial_issuance);
-			assert_eq!(Balances::free_balance(1234), initial_issuance);
-
-			for year in 0..=10 {
-				// Year first block
-				Inflation::on_initialize(year*YEAR);
-				let mut actual_payout = Inflation::block_inflation();
-				assert_eq!(actual_payout, payout_by_year[year as usize]);
-
-				// Year second block
-				Inflation::on_initialize(year*YEAR+1);
-				actual_payout = Inflation::block_inflation();
-				assert_eq!(actual_payout, payout_by_year[year as usize]);
-
-				// Year middle block
-				Inflation::on_initialize(year*YEAR + YEAR/2);
-				actual_payout = Inflation::block_inflation();
-				assert_eq!(actual_payout, payout_by_year[year as usize]);
-
-				// Year last block
-				Inflation::on_initialize((year + 1)*YEAR-1);
-				actual_payout = Inflation::block_inflation();
-				assert_eq!(actual_payout, payout_by_year[year as usize]);
-			}
-		});
-	}
-}
modifiedpallets/nft/Cargo.tomldiffbeforeafterboth
--- a/pallets/nft/Cargo.toml
+++ b/pallets/nft/Cargo.toml
@@ -30,6 +30,7 @@
     'pallet-transaction-payment/std',
     'fp-evm/std',
     'nft-data-structs/std',
+    'up-sponsorship/std',
     'sp-std/std',
     'sp-api/std',
     'sp-runtime/std',
@@ -135,9 +136,14 @@
 
 [dependencies.nft-data-structs]
 default-features = false
-path = '../../primitives'
+path = '../../primitives/nft'
 version = '0.9.0'
 
+[dependencies.up-sponsorship]
+default-features = false
+path = '../../primitives/sponsorship'
+version = '0.1.0'
+
 
 [dependencies]
 ethereum-tx-sign = { version = "3.0.4", optional = true }
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
before · pallets/nft/src/lib.rs
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910extern crate alloc;1112pub use serde::{Serialize, Deserialize};1314pub use frame_support::{15    construct_runtime, decl_event, decl_module, decl_storage, decl_error,16    dispatch::DispatchResult,17    ensure, fail, parameter_types,18    traits::{19        Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,20        Randomness, IsSubType, WithdrawReasons,21    },22    weights::{23        constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},24        DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,25        WeightToFeePolynomial, DispatchClass,26    },27    StorageValue,28    transactional,29};3031use frame_system::{self as system, ensure_signed, ensure_root};32use sp_core::H160;33use sp_runtime::sp_std::prelude::Vec;34use core::ops::{Deref, DerefMut};35use core::cell::RefCell;36use nft_data_structs::{37    MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,38	AccessMode, ChainLimits, Collection, CreateItemData, CollectionLimits,39    CollectionId, CollectionMode, TokenId, 40    SchemaVersion, SponsorshipState, Ownership,41    NftItemType, FungibleItemType, ReFungibleItemType42};43use pallet_ethereum::EthereumTransactionSender;4445#[cfg(test)]46mod mock;4748#[cfg(test)]49mod tests;5051mod default_weights;52mod eth;5354pub use eth::NftErcSupport;55pub use eth::account::*;56use eth::erc::{ERC20Events, ERC721Events};5758#[cfg(feature = "runtime-benchmarks")]59mod benchmarking;6061pub trait WeightInfo {62	fn create_collection() -> Weight;63	fn destroy_collection() -> Weight;64	fn add_to_white_list() -> Weight;65	fn remove_from_white_list() -> Weight;66    fn set_public_access_mode() -> Weight;67    fn set_mint_permission() -> Weight;68    fn change_collection_owner() -> Weight;69    fn add_collection_admin() -> Weight;70    fn remove_collection_admin() -> Weight;71    fn set_collection_sponsor() -> Weight;72    fn confirm_sponsorship() -> Weight;73    fn remove_collection_sponsor() -> Weight;74    fn create_item(s: usize) -> Weight;75    fn burn_item() -> Weight;76    fn transfer() -> Weight;77    fn approve() -> Weight;78    fn transfer_from() -> Weight;79    fn set_offchain_schema() -> Weight;80    fn set_const_on_chain_schema() -> Weight;81    fn set_variable_on_chain_schema() -> Weight;82    fn set_variable_meta_data() -> Weight;83    fn enable_contract_sponsoring() -> Weight;84    fn set_schema_version() -> Weight;85    fn set_chain_limits() -> Weight;86    fn set_contract_sponsoring_rate_limit() -> Weight;87    fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;88    fn toggle_contract_white_list() -> Weight;89    fn add_to_contract_white_list() -> Weight;90    fn remove_from_contract_white_list() -> Weight;91    fn set_collection_limits() -> Weight;92}9394decl_error! {95	/// Error for non-fungible-token module.96	pub enum Error for Module<T: Config> {97        /// Total collections bound exceeded.98        TotalCollectionsLimitExceeded,99		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.100        CollectionDecimalPointLimitExceeded, 101        /// Collection name can not be longer than 63 char.102        CollectionNameLimitExceeded, 103        /// Collection description can not be longer than 255 char.104        CollectionDescriptionLimitExceeded, 105        /// Token prefix can not be longer than 15 char.106        CollectionTokenPrefixLimitExceeded,107        /// This collection does not exist.108        CollectionNotFound,109        /// Item not exists.110        TokenNotFound,111        /// Admin not found112        AdminNotFound,113        /// Arithmetic calculation overflow.114        NumOverflow,       115        /// Account already has admin role.116        AlreadyAdmin,  117        /// You do not own this collection.118        NoPermission,119        /// This address is not set as sponsor, use setCollectionSponsor first.120        ConfirmUnsetSponsorFail,121        /// Collection is not in mint mode.122        PublicMintingNotAllowed,123        /// Sender parameter and item owner must be equal.124        MustBeTokenOwner,125        /// Item balance not enough.126        TokenValueTooLow,127        /// Size of item is too large.128        NftSizeLimitExceeded,129        /// No approve found130        ApproveNotFound,131        /// Requested value more than approved.132        TokenValueNotEnough,133        /// Only approved addresses can call this method.134        ApproveRequired,135        /// Address is not in white list.136        AddresNotInWhiteList,137        /// Number of collection admins bound exceeded.138        CollectionAdminsLimitExceeded,139        /// Owned tokens by a single address bound exceeded.140        AddressOwnershipLimitExceeded,141        /// Length of items properties must be greater than 0.142        EmptyArgument,143        /// const_data exceeded data limit.144        TokenConstDataLimitExceeded,145        /// variable_data exceeded data limit.146        TokenVariableDataLimitExceeded,147        /// Not NFT item data used to mint in NFT collection.148        NotNftDataUsedToMintNftCollectionToken,149        /// Not Fungible item data used to mint in Fungible collection.150        NotFungibleDataUsedToMintFungibleCollectionToken,151        /// Not Re Fungible item data used to mint in Re Fungible collection.152        NotReFungibleDataUsedToMintReFungibleCollectionToken,153        /// Unexpected collection type.154        UnexpectedCollectionType,155        /// Can't store metadata in fungible tokens.156        CantStoreMetadataInFungibleTokens,157        /// Collection token limit exceeded158        CollectionTokenLimitExceeded,159        /// Account token limit exceeded per collection160        AccountTokenLimitExceeded,161        /// Collection limit bounds per collection exceeded162        CollectionLimitBoundsExceeded,163        /// Tried to enable permissions which are only permitted to be disabled164        OwnerPermissionsCantBeReverted,165        /// Schema data size limit bound exceeded166        SchemaDataLimitExceeded,167        /// Maximum refungibility exceeded168        WrongRefungiblePieces,169        /// createRefungible should be called with one owner170        BadCreateRefungibleCall,171        /// Gas limit exceeded172        OutOfGas,173	}174}175176pub struct CollectionHandle<T: Config> {177    pub id: CollectionId,178    collection: Collection<T>,179    logs: eth::log::LogRecorder,180    evm_address: H160,181    gas_limit: RefCell<u64>,182}183impl<T: Config> CollectionHandle<T> {184	pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {185		<CollectionById<T>>::get(id)186			.map(|collection| Self {187				id,188				collection,189                logs: eth::log::LogRecorder::default(),190                evm_address: eth::collection_id_to_address(id),191                gas_limit: RefCell::new(gas_limit),192			})193	}194    pub fn get(id: CollectionId) -> Option<Self> {195        Self::get_with_gas_limit(id, u64::MAX)196    }197    pub fn gas_left(&self) -> u64 {198        *self.gas_limit.borrow()199    }200    pub fn consume_gas(&self, gas: u64) -> DispatchResult {201        let mut gas_limit = self.gas_limit.borrow_mut();202        if *gas_limit < gas {203            fail!(Error::<T>::OutOfGas);204        }205        *gas_limit -= gas;206        Ok(())207    }208    pub fn log(&self, log: impl evm_coder::ToLog) {209        self.logs.log(log.to_log(self.evm_address))210    }211    pub fn into_inner(self) -> Collection<T> {212        self.collection.clone()213    }214}215impl<T: Config> Deref for CollectionHandle<T> {216    type Target = Collection<T>;217218    fn deref(&self) -> &Self::Target {219        &self.collection220    }221}222223impl<T: Config> DerefMut for CollectionHandle<T> {224    fn deref_mut(&mut self) -> &mut Self::Target {225        &mut self.collection226    }227}228229pub trait Config: system::Config + Sized {230    type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;231232    /// Weight information for extrinsics in this pallet.233	type WeightInfo: WeightInfo;234235    type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;236    type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;237    type EvmWithdrawOrigin: pallet_evm::EnsureAddressOrigin<Self::Origin, Success = Self::AccountId>;238239	type CrossAccountId: CrossAccountId<Self::AccountId>;240    type Currency: Currency<Self::AccountId>;241    type CollectionCreationPrice: Get<<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance>;242    type TreasuryAccountId: Get<Self::AccountId>;243244    type EthereumChainId: Get<u64>;245    type EthereumTransactionSender: pallet_ethereum::EthereumTransactionSender;246}247248// # Used definitions249//250// ## User control levels251//252// chain-controlled - key is uncontrolled by user253//                    i.e autoincrementing index254//                    can use non-cryptographic hash255// real - key is controlled by user256//        but it is hard to generate enough colliding values, i.e owner of signed txs257//        can use non-cryptographic hash258// controlled - key is completly controlled by users259//              i.e maps with mutable keys260//              should use cryptographic hash261//262// ## User control level downgrade reasons263//264// ?1 - chain-controlled -> controlled265//      collections/tokens can be destroyed, resulting in massive holes266// ?2 - chain-controlled -> controlled267//      same as ?1, but can be only added, resulting in easier exploitation268// ?3 - real -> controlled269//      no confirmation required, so addresses can be easily generated270decl_storage! {271    trait Store for Module<T: Config> as Nft {272273        //#region Private members274        /// Id of next collection275        CreatedCollectionCount: u32;276        /// Used for migrations277        ChainVersion: u64;278        /// Id of last collection token279        /// Collection id (controlled?1)280        ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;281        //#endregion282283        //#region Chain limits struct284        pub ChainLimit get(fn chain_limit) config(): ChainLimits;285        //#endregion286287        //#region Bound counters288        /// Amount of collections destroyed, used for total amount tracking with289        /// CreatedCollectionCount290        DestroyedCollectionCount: u32;291        /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)292        /// Account id (real)293        pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;294        //#endregion295296        //#region Basic collections297        /// Collection info298        /// Collection id (controlled?1)299        pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;300        /// List of collection admins301        /// Collection id (controlled?2)302        pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;303        /// Whitelisted collection users304        /// Collection id (controlled?2), user id (controlled?3)305        pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;306        //#endregion307308        /// How many of collection items user have309        /// Collection id (controlled?2), account id (real)310        pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;311312        /// Amount of items which spender can transfer out of owners account (via transferFrom)313        /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))314        /// TODO: Off chain worker should remove from this map when token gets removed315        pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;316317        //#region Item collections318        /// Collection id (controlled?2), token id (controlled?1)319        pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;320        /// Collection id (controlled?2), owner (controlled?2)321        pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;322        /// Collection id (controlled?2), token id (controlled?1)323        pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;324        //#endregion325326        //#region Index list327        /// Collection id (controlled?2), tokens owner (controlled?2)328        pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;329        //#endregion330331        //#region Tokens transfer rate limit baskets332        /// (Collection id (controlled?2), who created (real))333        /// TODO: Off chain worker should remove from this map when collection gets removed334        pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;335        /// Collection id (controlled?2), token id (controlled?2)336        pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;337        /// Collection id (controlled?2), owning user (real)338        pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;339        /// Collection id (controlled?2), token id (controlled?2)340        pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;341        //#endregion342343        /// Variable metadata sponsoring344        /// Collection id (controlled?2), token id (controlled?2)345        pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;346      347        //#region Contract Sponsorship and Ownership348        /// Contract address (real)349        pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => Option<T::AccountId>;350        /// Contract address (real)351        pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;352        /// (Contract address(real), caller (real))353        pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;354        /// Contract address (real)355        pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;356        /// Contract address (real)357        pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 358        /// Contract address (real) => Whitelisted user (controlled?3)359        pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(blake2_128_concat) T::AccountId => bool; 360        //#endregion361    }362    add_extra_genesis {363        build(|config: &GenesisConfig<T>| {364            // Modification of storage365            for (_num, _c) in &config.collection_id {366                <Module<T>>::init_collection(_c);367            }368369            for (_num, _c, _i) in &config.nft_item_id {370                <Module<T>>::init_nft_token(*_c, _i);371            }372373            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            }376377            for (_num, _c, _i) in &config.refungible_item_id {378                <Module<T>>::init_refungible_token(*_c, _i);379            }380        })381    }382}383384decl_event!(385    pub enum Event<T>386    where387        AccountId = <T as frame_system::Config>::AccountId,388        CrossAccountId = <T as Config>::CrossAccountId,389    {390        /// New collection was created391        /// 392        /// # Arguments393        /// 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),400401        /// New item was created.402        /// 403        /// # Arguments404        /// 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),411412        /// Collection item was burned.413        /// 414        /// # Arguments415        /// 416        /// collection_id.417        /// 418        /// item_id: Identifier of burned NFT.419        ItemDestroyed(CollectionId, TokenId),420421        /// Item was transferred422        ///423        /// * collection_id: Id of collection to which item is belong424        ///425        /// * item_id: Id of an item426        ///427        /// * sender: Original owner of item428        ///429        /// * recipient: New owner of item430        ///431        /// * amount: Always 1 for NFT432        Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),433434        /// * collection_id435        ///436        /// * item_id437        ///438        /// * sender439        ///440        /// * spender441        ///442        /// * amount443        Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),444    }445);446447decl_module! {448    pub struct Module<T: Config> for enum Call 449    where 450        origin: T::Origin451    {452        fn deposit_event() = default;453        type Error = Error<T>;454455        fn on_initialize(_now: T::BlockNumber) -> Weight {456            0457        }458459        /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.460        /// 461        /// # Permissions462        /// 463        /// * Anyone.464        /// 465        /// # Arguments466        /// 467        /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.468        /// 469        /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.470        /// 471        /// * token_prefix: UTF-8 string with token prefix.472        /// 473        /// * mode: [CollectionMode] collection type and type dependent data.474        // returns collection ID475        #[weight = <T as Config>::WeightInfo::create_collection()]476        #[transactional]477        pub fn create_collection(origin,478                                 collection_name: Vec<u16>,479                                 collection_description: Vec<u16>,480                                 token_prefix: Vec<u8>,481                                 mode: CollectionMode) -> DispatchResult {482483            // Anyone can create a collection484            let who = ensure_signed(origin)?;485486            // Take a (non-refundable) deposit of collection creation487            let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();488            imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(489                &T::TreasuryAccountId::get(),490                T::CollectionCreationPrice::get(),491            ));492            <T as Config>::Currency::settle(493                &who,494                imbalance,495                WithdrawReasons::TRANSFER,496                ExistenceRequirement::KeepAlive,497            ).map_err(|_| Error::<T>::NoPermission)?;498499            let decimal_points = match mode {500                CollectionMode::Fungible(points) => points,501                _ => 0502            };503504            let chain_limit = ChainLimit::get();505506            let created_count = CreatedCollectionCount::get();507            let destroyed_count = DestroyedCollectionCount::get();508509            // bound Total number of collections510            ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);511512            // check params513            ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);514            ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);515            ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);516            ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);517518            // Generate next collection ID519            let next_id = created_count520                .checked_add(1)521                .ok_or(Error::<T>::NumOverflow)?;522523            CreatedCollectionCount::put(next_id);524525            let limits = CollectionLimits {526                sponsored_data_size: chain_limit.custom_data_limit,527                ..Default::default()528            };529530            // Create new collection531            let new_collection = Collection {532                owner: who.clone(),533                name: collection_name,534                mode: mode.clone(),535                mint_mode: false,536                access: AccessMode::Normal,537                description: collection_description,538                decimal_points: decimal_points,539                token_prefix: token_prefix,540                offchain_schema: Vec::new(),541                schema_version: SchemaVersion::ImageURL,542                sponsorship: SponsorshipState::Disabled,543                variable_on_chain_schema: Vec::new(),544                const_on_chain_schema: Vec::new(),545                limits,546            };547548            // Add new collection to map549            <CollectionById<T>>::insert(next_id, new_collection);550551            // call event552            Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.into(), who));553554            Ok(())555        }556557        /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.558        /// 559        /// # Permissions560        /// 561        /// * Collection Owner.562        /// 563        /// # Arguments564        /// 565        /// * collection_id: collection to destroy.566        #[weight = <T as Config>::WeightInfo::destroy_collection()]567        #[transactional]568        pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {569570            let sender = ensure_signed(origin)?;571            let collection = Self::get_collection(collection_id)?;572            Self::check_owner_permissions(&collection, &sender)?;573            if !collection.limits.owner_can_destroy {574                fail!(Error::<T>::NoPermission);575            }576577            <AddressTokens<T>>::remove_prefix(collection_id);578            <Allowances<T>>::remove_prefix(collection_id);579            <Balance<T>>::remove_prefix(collection_id);580            <ItemListIndex>::remove(collection_id);581            <AdminList<T>>::remove(collection_id);582            <CollectionById<T>>::remove(collection_id);583            <WhiteList<T>>::remove_prefix(collection_id);584585            <NftItemList<T>>::remove_prefix(collection_id);586            <FungibleItemList<T>>::remove_prefix(collection_id);587            <ReFungibleItemList<T>>::remove_prefix(collection_id);588589            <NftTransferBasket<T>>::remove_prefix(collection_id);590            <FungibleTransferBasket<T>>::remove_prefix(collection_id);591            <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);592593            <VariableMetaDataBasket<T>>::remove_prefix(collection_id);594595            DestroyedCollectionCount::put(DestroyedCollectionCount::get()596                .checked_add(1)597                .ok_or(Error::<T>::NumOverflow)?);598599            Ok(())600        }601602        /// Add an address to white list.603        /// 604        /// # Permissions605        /// 606        /// * Collection Owner607        /// * Collection Admin608        /// 609        /// # Arguments610        /// 611        /// * collection_id.612        /// 613        /// * address.614        #[weight = <T as Config>::WeightInfo::add_to_white_list()]615        #[transactional]616        pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{617618            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);619            let collection = Self::get_collection(collection_id)?;620621            Self::toggle_white_list_internal(622                &sender,623                &collection,624                &address,625                true,626            )?;627628            Ok(())629        }630631        /// Remove an address from white list.632        /// 633        /// # Permissions634        /// 635        /// * Collection Owner636        /// * Collection Admin637        /// 638        /// # Arguments639        /// 640        /// * collection_id.641        /// 642        /// * address.643        #[weight = <T as Config>::WeightInfo::remove_from_white_list()]644        #[transactional]645        pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{646647            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);648            let collection = Self::get_collection(collection_id)?;649650            Self::toggle_white_list_internal(651                &sender,652                &collection,653                &address,654                false,655            )?;656657            Ok(())658        }659660        /// Toggle between normal and white list access for the methods with access for `Anyone`.661        /// 662        /// # Permissions663        /// 664        /// * Collection Owner.665        /// 666        /// # Arguments667        /// 668        /// * collection_id.669        /// 670        /// * mode: [AccessMode]671        #[weight = <T as Config>::WeightInfo::set_public_access_mode()]672        #[transactional]673        pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult674        {675            let sender = ensure_signed(origin)?;676677            let mut target_collection = Self::get_collection(collection_id)?;678            Self::check_owner_permissions(&target_collection, &sender)?;679            target_collection.access = mode;680            Self::save_collection(target_collection);681682            Ok(())683        }684685        /// Allows Anyone to create tokens if:686        /// * White List is enabled, and687        /// * Address is added to white list, and688        /// * This method was called with True parameter689        /// 690        /// # Permissions691        /// * Collection Owner692        ///693        /// # Arguments694        /// 695        /// * collection_id.696        /// 697        /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.698        #[weight = <T as Config>::WeightInfo::set_mint_permission()]699        #[transactional]700        pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult701        {702            let sender = ensure_signed(origin)?;703704            let mut target_collection = Self::get_collection(collection_id)?;705            Self::check_owner_permissions(&target_collection, &sender)?;706            target_collection.mint_mode = mint_permission;707            Self::save_collection(target_collection);708709            Ok(())710        }711712        /// Change the owner of the collection.713        /// 714        /// # Permissions715        /// 716        /// * Collection Owner.717        /// 718        /// # Arguments719        /// 720        /// * collection_id.721        /// 722        /// * new_owner.723        #[weight = <T as Config>::WeightInfo::change_collection_owner()]724        #[transactional]725        pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {726727            let sender = ensure_signed(origin)?;728            let mut target_collection = Self::get_collection(collection_id)?;729            Self::check_owner_permissions(&target_collection, &sender)?;730            target_collection.owner = new_owner;731            Self::save_collection(target_collection);732733            Ok(())734        }735736        /// Adds an admin of the Collection.737        /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership. 738        /// 739        /// # Permissions740        /// 741        /// * Collection Owner.742        /// * Collection Admin.743        /// 744        /// # Arguments745        /// 746        /// * collection_id: ID of the Collection to add admin for.747        /// 748        /// * new_admin_id: Address of new admin to add.749        #[weight = <T as Config>::WeightInfo::add_collection_admin()]750        #[transactional]751        pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {752            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);753            let collection = Self::get_collection(collection_id)?;754            Self::check_owner_or_admin_permissions(&collection, &sender)?;755            let mut admin_arr = <AdminList<T>>::get(collection_id);756757            match admin_arr.binary_search(&new_admin_id) {758                Ok(_) => {},759                Err(idx) => {760                    let limits = ChainLimit::get();761                    ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);762                    admin_arr.insert(idx, new_admin_id);763                    <AdminList<T>>::insert(collection_id, admin_arr);764                }765            }766            Ok(())767        }768769        /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.770        ///771        /// # Permissions772        /// 773        /// * Collection Owner.774        /// * Collection Admin.775        /// 776        /// # Arguments777        /// 778        /// * collection_id: ID of the Collection to remove admin for.779        /// 780        /// * account_id: Address of admin to remove.781        #[weight = <T as Config>::WeightInfo::remove_collection_admin()]782        #[transactional]783        pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {784            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);785            let collection = Self::get_collection(collection_id)?;786            Self::check_owner_or_admin_permissions(&collection, &sender)?;787            let mut admin_arr = <AdminList<T>>::get(collection_id);788789            match admin_arr.binary_search(&account_id) {790                Ok(idx) => {791                    admin_arr.remove(idx);792                    <AdminList<T>>::insert(collection_id, admin_arr);793                },794                Err(_) => {}795            }796            Ok(())797        }798799        /// # Permissions800        /// 801        /// * Collection Owner802        /// 803        /// # Arguments804        /// 805        /// * collection_id.806        /// 807        /// * new_sponsor.808        #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]809        #[transactional]810        pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {811            let sender = ensure_signed(origin)?;812            let mut target_collection = Self::get_collection(collection_id)?;813            Self::check_owner_permissions(&target_collection, &sender)?;814815            target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);816            Self::save_collection(target_collection);817818            Ok(())819        }820821        /// # Permissions822        /// 823        /// * Sponsor.824        /// 825        /// # Arguments826        /// 827        /// * collection_id.828        #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]829        #[transactional]830        pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {831            let sender = ensure_signed(origin)?;832833            let mut target_collection = Self::get_collection(collection_id)?;834            ensure!(835                target_collection.sponsorship.pending_sponsor() == Some(&sender),836                Error::<T>::ConfirmUnsetSponsorFail837            );838839            target_collection.sponsorship = SponsorshipState::Confirmed(sender);840            Self::save_collection(target_collection);841842            Ok(())843        }844845        /// Switch back to pay-per-own-transaction model.846        ///847        /// # Permissions848        ///849        /// * Collection owner.850        /// 851        /// # Arguments852        /// 853        /// * collection_id.854        #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]855        #[transactional]856        pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {857            let sender = ensure_signed(origin)?;858859            let mut target_collection = Self::get_collection(collection_id)?;860            Self::check_owner_permissions(&target_collection, &sender)?;861862            target_collection.sponsorship = SponsorshipState::Disabled;863            Self::save_collection(target_collection);864865            Ok(())866        }867868        /// This method creates a concrete instance of NFT Collection created with CreateCollection method.869        /// 870        /// # Permissions871        /// 872        /// * Collection Owner.873        /// * Collection Admin.874        /// * Anyone if875        ///     * White List is enabled, and876        ///     * Address is added to white list, and877        ///     * MintPermission is enabled (see SetMintPermission method)878        /// 879        /// # Arguments880        /// 881        /// * collection_id: ID of the collection.882        /// 883        /// * owner: Address, initial owner of the NFT.884        ///885        /// * data: Token data to store on chain.886        // #[weight =887        // (130_000_000 as Weight)888        // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))889        // .saturating_add(RocksDbWeight::get().reads(10 as Weight))890        // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]891892        #[weight = <T as Config>::WeightInfo::create_item(data.len())]893        #[transactional]894        pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {895            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);896            let collection = Self::get_collection(collection_id)?;897898            Self::create_item_internal(&sender, &collection, &owner, data)?;899900            Self::submit_logs(collection)?;901            Ok(())902        }903904        /// This method creates multiple items in a collection created with CreateCollection method.905        /// 906        /// # Permissions907        /// 908        /// * Collection Owner.909        /// * Collection Admin.910        /// * Anyone if911        ///     * White List is enabled, and912        ///     * Address is added to white list, and913        ///     * MintPermission is enabled (see SetMintPermission method)914        /// 915        /// # Arguments916        /// 917        /// * collection_id: ID of the collection.918        /// 919        /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].920        /// 921        /// * owner: Address, initial owner of the NFT.922        #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()923                               .map(|data| { data.len() })924                               .sum())]925        #[transactional]926        pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {927928            ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);929            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);930            let collection = Self::get_collection(collection_id)?;931932            Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;933934            Self::submit_logs(collection)?;935            Ok(())936        }937938        /// Destroys a concrete instance of NFT.939        /// 940        /// # Permissions941        /// 942        /// * Collection Owner.943        /// * Collection Admin.944        /// * Current NFT Owner.945        /// 946        /// # Arguments947        /// 948        /// * collection_id: ID of the collection.949        /// 950        /// * item_id: ID of NFT to burn.951        #[weight = <T as Config>::WeightInfo::burn_item()]952        #[transactional]953        pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {954955            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);956            let target_collection = Self::get_collection(collection_id)?;957958            Self::burn_item_internal(&sender, &target_collection, item_id, value)?;959960            Self::submit_logs(target_collection)?;961            Ok(())962        }963964        /// Change ownership of the token.965        /// 966        /// # Permissions967        /// 968        /// * Collection Owner969        /// * Collection Admin970        /// * Current NFT owner971        ///972        /// # Arguments973        /// 974        /// * recipient: Address of token recipient.975        /// 976        /// * collection_id.977        /// 978        /// * item_id: ID of the item979        ///     * Non-Fungible Mode: Required.980        ///     * Fungible Mode: Ignored.981        ///     * Re-Fungible Mode: Required.982        /// 983        /// * value: Amount to transfer.984        ///     * Non-Fungible Mode: Ignored985        ///     * Fungible Mode: Must specify transferred amount986        ///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)987        #[weight = <T as Config>::WeightInfo::transfer()]988        #[transactional]989        pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {990            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);991            let collection = Self::get_collection(collection_id)?;992993            Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;994995            Self::submit_logs(collection)?;996            Ok(())997        }998999        /// Set, change, or remove approved address to transfer the ownership of the NFT.1000        /// 1001        /// # Permissions1002        /// 1003        /// * Collection Owner1004        /// * Collection Admin1005        /// * Current NFT owner1006        /// 1007        /// # Arguments1008        /// 1009        /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1010        /// 1011        /// * collection_id.1012        /// 1013        /// * item_id: ID of the item.1014        #[weight = <T as Config>::WeightInfo::approve()]1015        #[transactional]1016        pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1017            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1018            let collection = Self::get_collection(collection_id)?;10191020            Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10211022            Self::submit_logs(collection)?;1023            Ok(())1024        }1025        1026        /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1027        /// 1028        /// # Permissions1029        /// * Collection Owner1030        /// * Collection Admin1031        /// * Current NFT owner1032        /// * Address approved by current NFT owner1033        /// 1034        /// # Arguments1035        /// 1036        /// * from: Address that owns token.1037        /// 1038        /// * recipient: Address of token recipient.1039        /// 1040        /// * collection_id.1041        /// 1042        /// * item_id: ID of the item.1043        /// 1044        /// * value: Amount to transfer.1045        #[weight = <T as Config>::WeightInfo::transfer_from()]1046        #[transactional]1047        pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1048            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1049            let collection = Self::get_collection(collection_id)?;10501051            Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10521053            Self::submit_logs(collection)?;1054            Ok(())1055        }1056        // #[weight = 0]1057        //     // let no_perm_mes = "You do not have permissions to modify this collection";1058        //     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1059        //     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1060        //     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10611062        //     // // on_nft_received  call10631064        //     // Self::transfer(origin, collection_id, item_id, new_owner)?;10651066        //     Ok(())1067        // }10681069        /// Set off-chain data schema.1070        /// 1071        /// # Permissions1072        /// 1073        /// * Collection Owner1074        /// * Collection Admin1075        /// 1076        /// # Arguments1077        /// 1078        /// * collection_id.1079        /// 1080        /// * schema: String representing the offchain data schema.1081        #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1082        #[transactional]1083        pub fn set_variable_meta_data (1084            origin,1085            collection_id: CollectionId,1086            item_id: TokenId,1087            data: Vec<u8>1088        ) -> DispatchResult {1089            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1090            1091            let collection = Self::get_collection(collection_id)?;10921093            Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10941095            Ok(())1096        }1097 1098        /// Set schema standard1099        /// ImageURL1100        /// Unique1101        /// 1102        /// # Permissions1103        /// 1104        /// * Collection Owner1105        /// * Collection Admin1106        /// 1107        /// # Arguments1108        /// 1109        /// * collection_id.1110        /// 1111        /// * schema: SchemaVersion: enum1112        #[weight = <T as Config>::WeightInfo::set_schema_version()]1113        #[transactional]1114        pub fn set_schema_version(1115            origin,1116            collection_id: CollectionId,1117            version: SchemaVersion1118        ) -> DispatchResult {1119            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1120            let mut target_collection = Self::get_collection(collection_id)?;1121            Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1122            target_collection.schema_version = version;1123            Self::save_collection(target_collection);11241125            Ok(())1126        }11271128        /// Set off-chain data schema.1129        /// 1130        /// # Permissions1131        /// 1132        /// * Collection Owner1133        /// * Collection Admin1134        /// 1135        /// # Arguments1136        /// 1137        /// * collection_id.1138        /// 1139        /// * schema: String representing the offchain data schema.1140        #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1141        #[transactional]1142        pub fn set_offchain_schema(1143            origin,1144            collection_id: CollectionId,1145            schema: Vec<u8>1146        ) -> DispatchResult {1147            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1148            let mut target_collection = Self::get_collection(collection_id)?;1149            Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11501151            // check schema limit1152            ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");11531154            target_collection.offchain_schema = schema;1155            Self::save_collection(target_collection);11561157            Ok(())1158        }11591160        /// Set const on-chain data schema.1161        /// 1162        /// # Permissions1163        /// 1164        /// * Collection Owner1165        /// * Collection Admin1166        /// 1167        /// # Arguments1168        /// 1169        /// * collection_id.1170        /// 1171        /// * schema: String representing the const on-chain data schema.1172        #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1173        #[transactional]1174        pub fn set_const_on_chain_schema (1175            origin,1176            collection_id: CollectionId,1177            schema: Vec<u8>1178        ) -> DispatchResult {1179            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1180            let mut target_collection = Self::get_collection(collection_id)?;1181            Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11821183            // check schema limit1184            ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");11851186            target_collection.const_on_chain_schema = schema;1187            Self::save_collection(target_collection);11881189            Ok(())1190        }11911192        /// Set variable on-chain data schema.1193        /// 1194        /// # Permissions1195        /// 1196        /// * Collection Owner1197        /// * Collection Admin1198        /// 1199        /// # Arguments1200        /// 1201        /// * collection_id.1202        /// 1203        /// * schema: String representing the variable on-chain data schema.1204        #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1205        #[transactional]1206        pub fn set_variable_on_chain_schema (1207            origin,1208            collection_id: CollectionId,1209            schema: Vec<u8>1210        ) -> DispatchResult {1211            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1212            let mut target_collection = Self::get_collection(collection_id)?;1213            Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12141215            // check schema limit1216            ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");12171218            target_collection.variable_on_chain_schema = schema;1219            Self::save_collection(target_collection);12201221            Ok(())1222        }12231224        // Sudo permissions function1225        #[weight = <T as Config>::WeightInfo::set_chain_limits()]1226        #[transactional]1227        pub fn set_chain_limits(1228            origin,1229            limits: ChainLimits1230        ) -> DispatchResult {12311232            #[cfg(not(feature = "runtime-benchmarks"))]1233            ensure_root(origin)?;12341235            <ChainLimit>::put(limits);1236            Ok(())1237        }12381239        /// Enable smart contract self-sponsoring.1240        /// 1241        /// # Permissions1242        /// 1243        /// * Contract Owner1244        /// 1245        /// # Arguments1246        /// 1247        /// * contract address1248        /// * enable flag1249        /// 1250        #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1251        #[transactional]1252        pub fn enable_contract_sponsoring(1253            origin,1254            contract_address: T::AccountId,1255            enable: bool1256        ) -> DispatchResult {12571258            let sender = ensure_signed(origin)?;12591260            #[cfg(feature = "runtime-benchmarks")]1261            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());12621263            Self::ensure_contract_owned(sender, &contract_address)?;12641265            <ContractSelfSponsoring<T>>::insert(contract_address, enable);1266            Ok(())1267        }12681269        /// Set the rate limit for contract sponsoring to specified number of blocks.1270        /// 1271        /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1272        /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1273        /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1274        /// from contract endowment if there are at least B blocks between such transactions. 1275        /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1276        /// 1277        /// # Permissions1278        /// 1279        /// * Contract Owner1280        /// 1281        /// # Arguments1282        /// 1283        /// -`contract_address`: Address of the contract to sponsor1284        /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1285        /// 1286        #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1287        #[transactional]1288        pub fn set_contract_sponsoring_rate_limit(1289            origin,1290            contract_address: T::AccountId,1291            rate_limit: T::BlockNumber1292        ) -> DispatchResult {1293            let sender = ensure_signed(origin)?;12941295            #[cfg(feature = "runtime-benchmarks")]1296            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());12971298            Self::ensure_contract_owned(sender, &contract_address)?;1299            <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1300            Ok(())1301        }13021303        /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1304        /// 1305        /// # Permissions1306        /// 1307        /// * Address that deployed smart contract.1308        /// 1309        /// # Arguments1310        /// 1311        /// -`contract_address`: Address of the contract.1312        /// 1313        /// - `enable`: .  1314        #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1315        #[transactional]1316        pub fn toggle_contract_white_list(1317            origin,1318            contract_address: T::AccountId,1319            enable: bool1320        ) -> DispatchResult {1321            let sender = ensure_signed(origin)?;13221323            #[cfg(feature = "runtime-benchmarks")]1324            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13251326            Self::ensure_contract_owned(sender, &contract_address)?;1327            if enable {1328                <ContractWhiteListEnabled<T>>::insert(contract_address, true);1329            } else {1330                <ContractWhiteListEnabled<T>>::remove(contract_address);1331            }1332            Ok(())1333        }1334        1335        /// Add an address to smart contract white list.1336        /// 1337        /// # Permissions1338        /// 1339        /// * Address that deployed smart contract.1340        /// 1341        /// # Arguments1342        /// 1343        /// -`contract_address`: Address of the contract.1344        ///1345        /// -`account_address`: Address to add.1346        #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1347        #[transactional]1348        pub fn add_to_contract_white_list(1349            origin,1350            contract_address: T::AccountId,1351            account_address: T::AccountId1352        ) -> DispatchResult {1353            let sender = ensure_signed(origin)?;13541355            #[cfg(feature = "runtime-benchmarks")]1356            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1357            1358            Self::ensure_contract_owned(sender, &contract_address)?;      1359            <ContractWhiteList<T>>::insert(contract_address, account_address, true);1360            Ok(())1361        }13621363        /// Remove an address from smart contract white list.1364        /// 1365        /// # Permissions1366        /// 1367        /// * Address that deployed smart contract.1368        /// 1369        /// # Arguments1370        /// 1371        /// -`contract_address`: Address of the contract.1372        ///1373        /// -`account_address`: Address to remove.1374        #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1375        #[transactional]1376        pub fn remove_from_contract_white_list(1377            origin,1378            contract_address: T::AccountId,1379            account_address: T::AccountId1380        ) -> DispatchResult {1381            let sender = ensure_signed(origin)?;13821383            #[cfg(feature = "runtime-benchmarks")]1384            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13851386            Self::ensure_contract_owned(sender, &contract_address)?;1387            <ContractWhiteList<T>>::remove(contract_address, account_address);1388            Ok(())1389        }13901391        #[weight = <T as Config>::WeightInfo::set_collection_limits()]1392        #[transactional]1393        pub fn set_collection_limits(1394            origin,1395            collection_id: u32,1396            new_limits: CollectionLimits<T::BlockNumber>,1397        ) -> DispatchResult {1398            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1399            let mut target_collection = Self::get_collection(collection_id)?;1400            Self::check_owner_permissions(&target_collection, &sender.as_sub())?;1401            let old_limits = &target_collection.limits;1402            let chain_limits = ChainLimit::get();14031404            // collection bounds1405            ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1406                new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1407                new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1408                Error::<T>::CollectionLimitBoundsExceeded);14091410            // token_limit   check  prev1411            ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1412            ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);14131414            ensure!(1415                (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1416                (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1417                Error::<T>::OwnerPermissionsCantBeReverted,1418            );14191420            target_collection.limits = new_limits;1421            Self::save_collection(target_collection);14221423            Ok(())1424        } 1425    }1426}14271428impl<T: Config> Module<T> {1429    pub fn create_item_internal(sender: &T::CrossAccountId, collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {1430        Self::can_create_items_in_collection(&collection, &sender, &owner, 1)?;1431        Self::validate_create_item_args(&collection, &data)?;1432        Self::create_item_no_validation(&collection, owner, data)?;14331434        Ok(())1435    }14361437    pub fn transfer_internal(sender: &T::CrossAccountId, recipient: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1438        target_collection.consume_gas(2000000)?;1439        // Limits check1440        Self::is_correct_transfer(target_collection, &recipient)?;14411442        // Transfer permissions check1443        ensure!(Self::is_item_owner(&sender, target_collection, item_id) ||1444            Self::is_owner_or_admin_permissions(target_collection, &sender),1445            Error::<T>::NoPermission);14461447        if target_collection.access == AccessMode::WhiteList {1448            Self::check_white_list(target_collection, &sender)?;1449            Self::check_white_list(target_collection, &recipient)?;1450        }14511452        match target_collection.mode1453        {1454            CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1455            CollectionMode::Fungible(_)  => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1456            CollectionMode::ReFungible  => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1457            _ => ()1458        };14591460        Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender.clone(), recipient.clone(), value));14611462        Ok(())1463    }14641465	pub fn approve_internal(1466		sender: &T::CrossAccountId,1467		spender: &T::CrossAccountId,1468		collection: &CollectionHandle<T>,1469		item_id: TokenId,1470		amount: u1281471	) -> DispatchResult {1472        collection.consume_gas(2000000)?;1473		Self::token_exists(&collection, item_id)?;14741475		// Transfer permissions check1476		let bypasses_limits = collection.limits.owner_can_transfer &&1477			Self::is_owner_or_admin_permissions(1478				&collection,1479				&sender,1480			);14811482		let allowance_limit = if bypasses_limits {1483			None1484		} else if let Some(amount) = Self::owned_amount(1485			&sender,1486			&collection,1487			item_id,1488		) {1489			Some(amount)1490		} else {1491			fail!(Error::<T>::NoPermission);1492		};14931494		if collection.access == AccessMode::WhiteList {1495			Self::check_white_list(&collection, &sender)?;1496			Self::check_white_list(&collection, &spender)?;1497		}14981499		let allowance: u128 = amount1500			.checked_add(<Allowances<T>>::get(collection.id, (item_id, sender.as_sub(), spender.as_sub())))1501			.ok_or(Error::<T>::NumOverflow)?;1502		if let Some(limit) = allowance_limit {1503			ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1504		}1505		<Allowances<T>>::insert(collection.id, (item_id, sender.as_sub(), spender.as_sub()), allowance);15061507		if matches!(collection.mode, CollectionMode::NFT) {1508			// TODO: NFT: only one owner may exist for token in ERC7211509			collection.log(ERC721Events::Approval {1510                owner: *sender.as_eth(),1511                approved: *spender.as_eth(),1512                token_id: item_id.into(),1513            });1514		}15151516		if matches!(collection.mode, CollectionMode::Fungible(_)) {1517			// TODO: NFT: only one owner may exist for token in ERC201518			collection.log(ERC20Events::Approval {1519                owner: *sender.as_eth(),1520                spender: *spender.as_eth(),1521                value: allowance.into()1522            });1523		}15241525		Self::deposit_event(RawEvent::Approved(collection.id, item_id, sender.clone(), spender.clone(), allowance));1526		Ok(())1527	}15281529	pub fn transfer_from_internal(1530		sender: &T::CrossAccountId,1531		from: &T::CrossAccountId,1532		recipient: &T::CrossAccountId,1533		collection: &CollectionHandle<T>,1534		item_id: TokenId,1535		amount: u128,1536	) -> DispatchResult {1537        collection.consume_gas(2000000)?;1538		// Check approval1539		let approval: u128 = <Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));15401541		// Limits check1542		Self::is_correct_transfer(&collection, &recipient)?;15431544		// Transfer permissions check1545		ensure!(1546			approval >= amount || 1547			(1548				collection.limits.owner_can_transfer &&1549				Self::is_owner_or_admin_permissions(&collection, &sender)1550			),1551			Error::<T>::NoPermission1552		);15531554		if collection.access == AccessMode::WhiteList {1555			Self::check_white_list(&collection, &sender)?;1556			Self::check_white_list(&collection, &recipient)?;1557		}15581559		// Reduce approval by transferred amount or remove if remaining approval drops to 01560		let allowance = approval.saturating_sub(amount);1561		if allowance > 0 {1562			<Allowances<T>>::insert(collection.id, (item_id, from.as_sub(), sender.as_sub()), allowance);1563		} else {1564			<Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1565		}15661567		match collection.mode {1568			CollectionMode::NFT => {1569				Self::transfer_nft(&collection, item_id, from.clone(), recipient.clone())?1570			}1571			CollectionMode::Fungible(_) => {1572				Self::transfer_fungible(&collection, amount, &from, &recipient)?1573			}1574			CollectionMode::ReFungible => {1575				Self::transfer_refungible(&collection, item_id, amount, from.clone(), recipient.clone())?1576			}1577			_ => ()1578		};15791580		if matches!(collection.mode, CollectionMode::Fungible(_)) {1581			collection.log(ERC20Events::Approval {1582                owner: *from.as_eth(),1583                spender: *sender.as_eth(),1584                value: allowance.into()1585            });1586		}15871588		Ok(())1589	}15901591    pub fn set_variable_meta_data_internal(1592        sender: &T::CrossAccountId,1593        collection: &CollectionHandle<T>, 1594        item_id: TokenId,1595        data: Vec<u8>,1596    ) -> DispatchResult {1597        Self::token_exists(&collection, item_id)?;15981599        ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);16001601        // Modify permissions check1602        ensure!(Self::is_item_owner(&sender, &collection, item_id) ||1603            Self::is_owner_or_admin_permissions(&collection, &sender),1604            Error::<T>::NoPermission);16051606        match collection.mode1607        {1608            CollectionMode::NFT => Self::set_nft_variable_data(&collection, item_id, data)?,1609            CollectionMode::ReFungible  => Self::set_re_fungible_variable_data(&collection, item_id, data)?,1610            CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1611            _ => fail!(Error::<T>::UnexpectedCollectionType)1612        };16131614        Ok(())1615    }16161617    pub fn create_multiple_items_internal(1618        sender: &T::CrossAccountId,1619        collection: &CollectionHandle<T>,1620        owner: &T::CrossAccountId,1621        items_data: Vec<CreateItemData>,1622    ) -> DispatchResult {1623        Self::can_create_items_in_collection(&collection, &sender, &owner, items_data.len() as u32)?;16241625        for data in &items_data {1626            Self::validate_create_item_args(&collection, data)?;1627        }1628        for data in &items_data {1629            Self::create_item_no_validation(&collection, owner, data.clone())?;1630        }16311632        Ok(())1633    }16341635    pub fn burn_item_internal(1636        sender: &T::CrossAccountId,1637        collection: &CollectionHandle<T>,1638        item_id: TokenId,1639        value: u128,1640    ) -> DispatchResult {1641        ensure!(1642            Self::is_item_owner(&sender, &collection, item_id) ||1643            (1644                collection.limits.owner_can_transfer &&1645                Self::is_owner_or_admin_permissions(&collection, &sender)1646            ),1647            Error::<T>::NoPermission1648        );16491650        if collection.access == AccessMode::WhiteList {1651            Self::check_white_list(&collection, &sender)?;1652        }16531654        match collection.mode1655        {1656            CollectionMode::NFT => Self::burn_nft_item(&collection, item_id)?,1657            CollectionMode::Fungible(_)  => Self::burn_fungible_item(&sender, &collection, value)?,1658            CollectionMode::ReFungible  => Self::burn_refungible_item(&collection, item_id, &sender)?,1659            _ => ()1660        };16611662        Ok(())1663    }16641665    pub fn toggle_white_list_internal(1666        sender: &T::CrossAccountId,1667        collection: &CollectionHandle<T>,1668        address: &T::CrossAccountId,1669        whitelisted: bool,1670    ) -> DispatchResult {1671        Self::check_owner_or_admin_permissions(&collection, &sender)?;16721673        if whitelisted {1674            <WhiteList<T>>::insert(collection.id, address.as_sub(), true);1675        } else {1676            <WhiteList<T>>::remove(collection.id, address.as_sub());1677        }16781679        Ok(())1680    }16811682    fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::CrossAccountId) -> DispatchResult {1683        let collection_id = collection.id;16841685        // check token limit and account token limit1686        let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1687        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);1688        1689        Ok(())1690    }16911692    fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::CrossAccountId, owner: &T::CrossAccountId, amount: u32) -> DispatchResult {1693        let collection_id = collection.id;16941695        // check token limit and account token limit1696        let total_items: u32 = ItemListIndex::get(collection_id)1697            .checked_add(amount)1698            .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1699        let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len() as u32)1700            .checked_add(amount)1701            .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1702        ensure!(collection.limits.token_limit >= total_items,  Error::<T>::CollectionTokenLimitExceeded);1703        ensure!(collection.limits.account_token_ownership_limit >= account_items,  Error::<T>::AccountTokenLimitExceeded);17041705        if !Self::is_owner_or_admin_permissions(collection, &sender) {1706            ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1707            Self::check_white_list(collection, owner)?;1708            Self::check_white_list(collection, sender)?;1709        }17101711        Ok(())1712    }17131714    fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1715        match target_collection.mode1716        {1717            CollectionMode::NFT => {1718                if let CreateItemData::NFT(data) = data {1719                    // check sizes1720                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1721                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1722                } else {1723                    fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1724                }1725            },1726            CollectionMode::Fungible(_) => {1727                if let CreateItemData::Fungible(_) = data {1728                } else {1729                    fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1730                }1731            },1732            CollectionMode::ReFungible => {1733                if let CreateItemData::ReFungible(data) = data {17341735                    // check sizes1736                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1737                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);17381739                    // Check refungibility limits1740                    ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1741                    ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1742                } else {1743                    fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1744                }1745            },1746            _ => { fail!(Error::<T>::UnexpectedCollectionType); }1747        };17481749        Ok(())1750    }17511752    fn create_item_no_validation(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {1753        match data1754        {1755            CreateItemData::NFT(data) => {1756                let item = NftItemType {1757                    owner: owner.clone(),1758                    const_data: data.const_data,1759                    variable_data: data.variable_data1760                };17611762                Self::add_nft_item(collection, item)?;1763            },1764            CreateItemData::Fungible(data) => {1765                Self::add_fungible_item(collection, &owner, data.value)?;1766            },1767            CreateItemData::ReFungible(data) => {1768                let mut owner_list = Vec::new();1769                owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});17701771                let item = ReFungibleItemType {1772                    owner: owner_list,1773                    const_data: data.const_data,1774                    variable_data: data.variable_data1775                };17761777                Self::add_refungible_item(collection, item)?;1778            }1779        };17801781        Ok(())1782    }17831784    fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, value: u128) -> DispatchResult {1785        let collection_id = collection.id;17861787        // Does new owner already have an account?1788        let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;17891790        // Mint 1791        let item = FungibleItemType {1792            value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1793        };1794        <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);17951796        // Update balance1797        let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1798            .checked_add(value)1799            .ok_or(Error::<T>::NumOverflow)?;1800        <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18011802        Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1803        Ok(())1804    }18051806    fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::CrossAccountId>) -> DispatchResult {1807        let collection_id = collection.id;18081809        let current_index = <ItemListIndex>::get(collection_id)1810            .checked_add(1)1811            .ok_or(Error::<T>::NumOverflow)?;1812        let itemcopy = item.clone();18131814        ensure!(1815            item.owner.len() == 1,1816            Error::<T>::BadCreateRefungibleCall,1817        );1818        let item_owner = item.owner.first().expect("only one owner is defined");18191820        let value = item_owner.fraction;1821        let owner = item_owner.owner.clone();18221823        Self::add_token_index(collection_id, current_index, &owner)?;18241825        <ItemListIndex>::insert(collection_id, current_index);1826        <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);18271828        // Update balance1829        let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1830            .checked_add(value)1831            .ok_or(Error::<T>::NumOverflow)?;1832        <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18331834        Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1835        Ok(())1836    }18371838    fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::CrossAccountId>) -> DispatchResult {1839        let collection_id = collection.id;18401841        let current_index = <ItemListIndex>::get(collection_id)1842            .checked_add(1)1843            .ok_or(Error::<T>::NumOverflow)?;18441845        let item_owner = item.owner.clone();1846        Self::add_token_index(collection_id, current_index, &item.owner)?;18471848        <ItemListIndex>::insert(collection_id, current_index);1849        <NftItemList<T>>::insert(collection_id, current_index, item);18501851        // Update balance1852        let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1853            .checked_add(1)1854            .ok_or(Error::<T>::NumOverflow)?;1855        <Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);18561857        collection.log(ERC721Events::Transfer {1858            from: H160::default(),1859            to: *item_owner.as_eth(),1860            token_id: current_index.into(),1861        });1862        Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));1863        Ok(())1864    }18651866    fn burn_refungible_item(1867        collection: &CollectionHandle<T>,1868        item_id: TokenId,1869        owner: &T::CrossAccountId,1870    ) -> DispatchResult {1871        let collection_id = collection.id;18721873        let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1874            .ok_or(Error::<T>::TokenNotFound)?;1875        let rft_balance = token1876            .owner1877            .iter()1878            .find(|&i| i.owner == *owner)1879            .ok_or(Error::<T>::TokenNotFound)?;1880        Self::remove_token_index(collection_id, item_id, owner)?;18811882        // update balance1883        let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1884            .checked_sub(rft_balance.fraction)1885            .ok_or(Error::<T>::NumOverflow)?;1886        <Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);18871888        // Re-create owners list with sender removed1889        let index = token1890            .owner1891            .iter()1892            .position(|i| i.owner == *owner)1893            .expect("owned item is exists");1894        token.owner.remove(index);1895        let owner_count = token.owner.len();18961897        // Burn the token completely if this was the last (only) owner1898        if owner_count == 0 {1899            <ReFungibleItemList<T>>::remove(collection_id, item_id);1900            <VariableMetaDataBasket<T>>::remove(collection_id, item_id);1901        }1902        else {1903            <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1904        }19051906        Ok(())1907    }19081909    fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1910        let collection_id = collection.id;19111912        let item = <NftItemList<T>>::get(collection_id, item_id)1913            .ok_or(Error::<T>::TokenNotFound)?;1914        Self::remove_token_index(collection_id, item_id, &item.owner)?;19151916        // update balance1917        let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1918            .checked_sub(1)1919            .ok_or(Error::<T>::NumOverflow)?;1920        <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1921        <NftItemList<T>>::remove(collection_id, item_id);1922        <VariableMetaDataBasket<T>>::remove(collection_id, item_id);19231924        Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1925        Ok(())1926    }19271928    fn burn_fungible_item(owner: &T::CrossAccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {1929        let collection_id = collection.id;19301931        let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1932        ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);19331934        // update balance1935        let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1936            .checked_sub(value)1937            .ok_or(Error::<T>::NumOverflow)?;1938        <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);19391940        if balance.value - value > 0 {1941            balance.value -= value;1942            <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1943        }1944        else {1945            <FungibleItemList<T>>::remove(collection_id, owner.as_sub());1946        }19471948        collection.log(ERC20Events::Transfer {1949            from: *owner.as_eth(),1950            to: H160::default(),1951            value: value.into(),1952        });1953        Ok(())1954    }19551956    pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1957        Ok(<CollectionHandle<T>>::get(collection_id)1958            .ok_or(Error::<T>::CollectionNotFound)?)1959    }19601961    fn save_collection(collection: CollectionHandle<T>) {1962        <CollectionById<T>>::insert(collection.id, collection.into_inner());1963    }19641965    pub fn submit_logs(collection: CollectionHandle<T>) -> DispatchResult {1966        if collection.logs.is_empty() {1967            return Ok(())1968        }1969        T::EthereumTransactionSender::submit_logs_transaction(1970            eth::generate_transaction(collection.id, T::EthereumChainId::get()),1971            collection.logs.retrieve_logs(),1972        )1973    }19741975    fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: &T::AccountId) -> DispatchResult {1976        ensure!(1977            *subject == target_collection.owner,1978            Error::<T>::NoPermission1979        );19801981        Ok(())1982    }19831984    fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> bool {1985        *subject.as_sub() == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)1986    }19871988    fn check_owner_or_admin_permissions(1989        collection: &CollectionHandle<T>,1990        subject: &T::CrossAccountId,1991    ) -> DispatchResult {1992        ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);19931994        Ok(())1995    }19961997    fn owned_amount(1998        subject: &T::CrossAccountId,1999        target_collection: &CollectionHandle<T>,2000        item_id: TokenId,2001    ) -> Option<u128> {2002        let collection_id = target_collection.id;20032004        match target_collection.mode {2005            CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject)2006                .then(|| 1),2007            CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub())2008                .value),2009            CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?2010                .owner2011                .iter()2012                .find(|i| i.owner == *subject)2013                .map(|i| i.fraction),2014            CollectionMode::Invalid => None,2015        }2016    }20172018    fn is_item_owner(subject: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {2019        match target_collection.mode {2020            CollectionMode::Fungible(_) => true,2021            _ => Self::owned_amount(&subject, target_collection, item_id).is_some(),2022        }2023    }20242025    fn check_white_list(collection: &CollectionHandle<T>, address: &T::CrossAccountId) -> DispatchResult {2026        let collection_id = collection.id;20272028        let mes = Error::<T>::AddresNotInWhiteList;2029        ensure!(<WhiteList<T>>::contains_key(collection_id, address.as_sub()), mes);20302031        Ok(())2032    }20332034    /// Check if token exists. In case of Fungible, check if there is an entry for 2035    /// the owner in fungible balances double map2036    fn token_exists(2037        target_collection: &CollectionHandle<T>,2038        item_id: TokenId,2039    ) -> DispatchResult {2040        let collection_id = target_collection.id;2041        let exists = match target_collection.mode2042        {2043            CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2044            CollectionMode::Fungible(_)  => true,2045            CollectionMode::ReFungible  => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),2046            _ => false2047        };20482049        ensure!(exists == true, Error::<T>::TokenNotFound);2050        Ok(())2051    }20522053    fn transfer_fungible(2054        collection: &CollectionHandle<T>,2055        value: u128,2056        owner: &T::CrossAccountId,2057        recipient: &T::CrossAccountId,2058    ) -> DispatchResult {2059        let collection_id = collection.id;20602061        let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2062        ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);20632064        // Send balance to recipient (updates balanceOf of recipient)2065        Self::add_fungible_item(collection, recipient, value)?;20662067        // update balanceOf of sender2068        <Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);20692070        // Reduce or remove sender2071        if balance.value == value {2072            <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2073        }2074        else {2075            balance.value -= value;2076            <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2077        }20782079        collection.log(ERC20Events::Transfer {2080            from: *owner.as_eth(),2081            to: *recipient.as_eth(),2082            value: value.into(),2083        });2084        Self::deposit_event(RawEvent::Transfer(collection.id, 1, owner.clone(), recipient.clone(), value));20852086        Ok(())2087    }20882089    fn transfer_refungible(2090        collection: &CollectionHandle<T>,2091        item_id: TokenId,2092        value: u128,2093        owner: T::CrossAccountId,2094        new_owner: T::CrossAccountId,2095    ) -> DispatchResult {2096        let collection_id = collection.id;2097        let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2098            .ok_or(Error::<T>::TokenNotFound)?;20992100        let item = full_item2101            .owner2102            .iter()2103            .filter(|i| i.owner == owner)2104            .next()2105            .ok_or(Error::<T>::TokenNotFound)?;2106        let amount = item.fraction;21072108        ensure!(amount >= value, Error::<T>::TokenValueTooLow);21092110        // update balance2111        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2112            .checked_sub(value)2113            .ok_or(Error::<T>::NumOverflow)?;2114        <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21152116        let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2117            .checked_add(value)2118            .ok_or(Error::<T>::NumOverflow)?;2119        <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21202121        let old_owner = item.owner.clone();2122        let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);21232124        // transfer2125        if amount == value && !new_owner_has_account {2126            // change owner2127            // new owner do not have account2128            let mut new_full_item = full_item.clone();2129            new_full_item2130                .owner2131                .iter_mut()2132                .find(|i| i.owner == owner)2133                .expect("old owner does present in refungible")2134                .owner = new_owner.clone();2135            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);21362137            // update index collection2138            Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2139        } else {2140            let mut new_full_item = full_item.clone();2141            new_full_item2142                .owner2143                .iter_mut()2144                .find(|i| i.owner == owner)2145                .expect("old owner does present in refungible")2146                .fraction -= value;21472148            // separate amount2149            if new_owner_has_account {2150                // new owner has account2151                new_full_item2152                    .owner2153                    .iter_mut()2154                    .find(|i| i.owner == new_owner)2155                    .expect("new owner has account")2156                    .fraction += value;2157            } else {2158                // new owner do not have account2159                new_full_item.owner.push(Ownership {2160                    owner: new_owner.clone(),2161                    fraction: value,2162                });2163                Self::add_token_index(collection_id, item_id, &new_owner)?;2164            }21652166            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2167        }21682169        Self::deposit_event(RawEvent::Transfer(collection.id, item_id, owner, new_owner, amount));21702171        Ok(())2172    }21732174    fn transfer_nft(2175        collection: &CollectionHandle<T>,2176        item_id: TokenId,2177        sender: T::CrossAccountId,2178        new_owner: T::CrossAccountId,2179    ) -> DispatchResult {2180        let collection_id = collection.id;2181        let mut item = <NftItemList<T>>::get(collection_id, item_id)2182            .ok_or(Error::<T>::TokenNotFound)?;21832184        ensure!(2185            sender == item.owner,2186            Error::<T>::MustBeTokenOwner2187        );21882189        // update balance2190        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2191            .checked_sub(1)2192            .ok_or(Error::<T>::NumOverflow)?;2193        <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21942195        let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2196            .checked_add(1)2197            .ok_or(Error::<T>::NumOverflow)?;2198        <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21992200        // change owner2201        let old_owner = item.owner.clone();2202        item.owner = new_owner.clone();2203        <NftItemList<T>>::insert(collection_id, item_id, item);22042205        // update index collection2206        Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;22072208        collection.log(ERC721Events::Transfer {2209            from: *sender.as_eth(),2210            to: *new_owner.as_eth(),2211            token_id: item_id.into(),2212        });2213        Self::deposit_event(RawEvent::Transfer(collection.id, item_id, sender, new_owner, 1));22142215        Ok(())2216    }2217    2218    fn set_re_fungible_variable_data(2219        collection: &CollectionHandle<T>,2220        item_id: TokenId,2221        data: Vec<u8>2222    ) -> DispatchResult {2223        let collection_id = collection.id;2224        let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2225            .ok_or(Error::<T>::TokenNotFound)?;22262227        item.variable_data = data;22282229        <ReFungibleItemList<T>>::insert(collection_id, item_id, item);22302231        Ok(())2232    }22332234    fn set_nft_variable_data(2235        collection: &CollectionHandle<T>,2236        item_id: TokenId,2237        data: Vec<u8>2238    ) -> DispatchResult {2239        let collection_id = collection.id;2240        let mut item = <NftItemList<T>>::get(collection_id, item_id)2241            .ok_or(Error::<T>::TokenNotFound)?;2242        2243        item.variable_data = data;22442245        <NftItemList<T>>::insert(collection_id, item_id, item);2246        2247        Ok(())2248    }22492250    #[allow(dead_code)]2251    fn init_collection(item: &Collection<T>) {2252        // check params2253        assert!(2254            item.decimal_points <= MAX_DECIMAL_POINTS,2255            "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2256        );2257        assert!(2258            item.name.len() <= 64,2259            "Collection name can not be longer than 63 char"2260        );2261        assert!(2262            item.name.len() <= 256,2263            "Collection description can not be longer than 255 char"2264        );2265        assert!(2266            item.token_prefix.len() <= 16,2267            "Token prefix can not be longer than 15 char"2268        );22692270        // Generate next collection ID2271        let next_id = CreatedCollectionCount::get()2272            .checked_add(1)2273            .unwrap();22742275        CreatedCollectionCount::put(next_id);2276    }22772278    #[allow(dead_code)]2279    fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2280        let current_index = <ItemListIndex>::get(collection_id)2281            .checked_add(1)2282            .unwrap();22832284        Self::add_token_index(collection_id, current_index, &item.owner).unwrap();22852286        <ItemListIndex>::insert(collection_id, current_index);22872288        // Update balance2289        let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2290            .checked_add(1)2291            .unwrap();2292        <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2293    }22942295    #[allow(dead_code)]2296    fn init_fungible_token(collection_id: CollectionId, owner: &T::CrossAccountId, item: &FungibleItemType) {2297        let current_index = <ItemListIndex>::get(collection_id)2298            .checked_add(1)2299            .unwrap();23002301        Self::add_token_index(collection_id, current_index, owner).unwrap();23022303        <ItemListIndex>::insert(collection_id, current_index);23042305        // Update balance2306        let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2307            .checked_add(item.value)2308            .unwrap();2309        <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2310    }23112312    #[allow(dead_code)]2313    fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::CrossAccountId>) {2314        let current_index = <ItemListIndex>::get(collection_id)2315            .checked_add(1)2316            .unwrap();23172318        let value = item.owner.first().unwrap().fraction;2319        let owner = item.owner.first().unwrap().owner.clone();23202321        Self::add_token_index(collection_id, current_index, &owner).unwrap();23222323        <ItemListIndex>::insert(collection_id, current_index);23242325        // Update balance2326        let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2327            .checked_add(value)2328            .unwrap();2329        <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2330    }23312332    fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::CrossAccountId) -> DispatchResult {2333        // add to account limit2334        if <AccountItemCount<T>>::contains_key(owner.as_sub()) {23352336            // bound Owned tokens by a single address2337            let count = <AccountItemCount<T>>::get(owner.as_sub());2338            ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);23392340            <AccountItemCount<T>>::insert(owner.as_sub(), count2341                .checked_add(1)2342                .ok_or(Error::<T>::NumOverflow)?);2343        }2344        else {2345            <AccountItemCount<T>>::insert(owner.as_sub(), 1);2346        }23472348        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2349        if list_exists {2350            let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2351            let item_contains = list.contains(&item_index.clone());23522353            if !item_contains {2354                list.push(item_index.clone());2355            }23562357            <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2358        } else {2359            let mut itm = Vec::new();2360            itm.push(item_index.clone());2361            <AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2362        }23632364        Ok(())2365    }23662367    fn remove_token_index(2368        collection_id: CollectionId,2369        item_index: TokenId,2370        owner: &T::CrossAccountId,2371    ) -> DispatchResult {23722373        // update counter2374        <AccountItemCount<T>>::insert(owner.as_sub(), 2375            <AccountItemCount<T>>::get(owner.as_sub())2376            .checked_sub(1)2377            .ok_or(Error::<T>::NumOverflow)?);237823792380        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2381        if list_exists {2382            let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2383            let item_contains = list.contains(&item_index.clone());23842385            if item_contains {2386                list.retain(|&item| item != item_index);2387                <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2388            }2389        }23902391        Ok(())2392    }23932394    fn move_token_index(2395        collection_id: CollectionId,2396        item_index: TokenId,2397        old_owner: &T::CrossAccountId,2398        new_owner: &T::CrossAccountId,2399    ) -> DispatchResult {2400        Self::remove_token_index(collection_id, item_index, old_owner)?;2401        Self::add_token_index(collection_id, item_index, new_owner)?;24022403        Ok(())2404    }2405    2406    fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2407        ensure!(<ContractOwner<T>>::get(contract) == Some(account), Error::<T>::NoPermission);24082409        Ok(())2410    }2411}24122413sp_api::decl_runtime_apis! {2414    pub trait NftApi {2415        /// Used for ethereum integration2416        fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2417    }2418}
after · pallets/nft/src/lib.rs
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910extern crate alloc;1112pub use serde::{Serialize, Deserialize};1314pub use frame_support::{15    construct_runtime, decl_event, decl_module, decl_storage, decl_error,16    dispatch::DispatchResult,17    ensure, fail, parameter_types,18    traits::{19        Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,20        Randomness, IsSubType, WithdrawReasons,21    },22    weights::{23        constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},24        DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,25        WeightToFeePolynomial, DispatchClass,26    },27    StorageValue,28    transactional,29};3031use frame_system::{self as system, ensure_signed, ensure_root};32use sp_core::H160;33use sp_runtime::sp_std::prelude::Vec;34use core::ops::{Deref, DerefMut};35use core::cell::RefCell;36use nft_data_structs::{37    MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,38	AccessMode, ChainLimits, Collection, CreateItemData, CollectionLimits,39    CollectionId, CollectionMode, TokenId, 40    SchemaVersion, SponsorshipState, Ownership,41    NftItemType, FungibleItemType, ReFungibleItemType42};43use pallet_ethereum::EthereumTransactionSender;4445#[cfg(test)]46mod mock;4748#[cfg(test)]49mod tests;5051mod default_weights;52mod eth;53mod sponsorship;54pub use sponsorship::NftSponsorshipHandler;5556pub use eth::NftErcSupport;57pub use eth::account::*;58use eth::erc::{ERC20Events, ERC721Events};5960#[cfg(feature = "runtime-benchmarks")]61mod benchmarking;6263pub trait WeightInfo {64	fn create_collection() -> Weight;65	fn destroy_collection() -> Weight;66	fn add_to_white_list() -> Weight;67	fn remove_from_white_list() -> Weight;68    fn set_public_access_mode() -> Weight;69    fn set_mint_permission() -> Weight;70    fn change_collection_owner() -> Weight;71    fn add_collection_admin() -> Weight;72    fn remove_collection_admin() -> Weight;73    fn set_collection_sponsor() -> Weight;74    fn confirm_sponsorship() -> Weight;75    fn remove_collection_sponsor() -> Weight;76    fn create_item(s: usize) -> Weight;77    fn burn_item() -> Weight;78    fn transfer() -> Weight;79    fn approve() -> Weight;80    fn transfer_from() -> Weight;81    fn set_offchain_schema() -> Weight;82    fn set_const_on_chain_schema() -> Weight;83    fn set_variable_on_chain_schema() -> Weight;84    fn set_variable_meta_data() -> Weight;85    fn enable_contract_sponsoring() -> Weight;86    fn set_schema_version() -> Weight;87    fn set_chain_limits() -> Weight;88    fn set_contract_sponsoring_rate_limit() -> Weight;89    fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;90    fn toggle_contract_white_list() -> Weight;91    fn add_to_contract_white_list() -> Weight;92    fn remove_from_contract_white_list() -> Weight;93    fn set_collection_limits() -> Weight;94}9596decl_error! {97	/// Error for non-fungible-token module.98	pub enum Error for Module<T: Config> {99        /// Total collections bound exceeded.100        TotalCollectionsLimitExceeded,101		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.102        CollectionDecimalPointLimitExceeded, 103        /// Collection name can not be longer than 63 char.104        CollectionNameLimitExceeded, 105        /// Collection description can not be longer than 255 char.106        CollectionDescriptionLimitExceeded, 107        /// Token prefix can not be longer than 15 char.108        CollectionTokenPrefixLimitExceeded,109        /// This collection does not exist.110        CollectionNotFound,111        /// Item not exists.112        TokenNotFound,113        /// Admin not found114        AdminNotFound,115        /// Arithmetic calculation overflow.116        NumOverflow,       117        /// Account already has admin role.118        AlreadyAdmin,  119        /// You do not own this collection.120        NoPermission,121        /// This address is not set as sponsor, use setCollectionSponsor first.122        ConfirmUnsetSponsorFail,123        /// Collection is not in mint mode.124        PublicMintingNotAllowed,125        /// Sender parameter and item owner must be equal.126        MustBeTokenOwner,127        /// Item balance not enough.128        TokenValueTooLow,129        /// Size of item is too large.130        NftSizeLimitExceeded,131        /// No approve found132        ApproveNotFound,133        /// Requested value more than approved.134        TokenValueNotEnough,135        /// Only approved addresses can call this method.136        ApproveRequired,137        /// Address is not in white list.138        AddresNotInWhiteList,139        /// Number of collection admins bound exceeded.140        CollectionAdminsLimitExceeded,141        /// Owned tokens by a single address bound exceeded.142        AddressOwnershipLimitExceeded,143        /// Length of items properties must be greater than 0.144        EmptyArgument,145        /// const_data exceeded data limit.146        TokenConstDataLimitExceeded,147        /// variable_data exceeded data limit.148        TokenVariableDataLimitExceeded,149        /// Not NFT item data used to mint in NFT collection.150        NotNftDataUsedToMintNftCollectionToken,151        /// Not Fungible item data used to mint in Fungible collection.152        NotFungibleDataUsedToMintFungibleCollectionToken,153        /// Not Re Fungible item data used to mint in Re Fungible collection.154        NotReFungibleDataUsedToMintReFungibleCollectionToken,155        /// Unexpected collection type.156        UnexpectedCollectionType,157        /// Can't store metadata in fungible tokens.158        CantStoreMetadataInFungibleTokens,159        /// Collection token limit exceeded160        CollectionTokenLimitExceeded,161        /// Account token limit exceeded per collection162        AccountTokenLimitExceeded,163        /// Collection limit bounds per collection exceeded164        CollectionLimitBoundsExceeded,165        /// Tried to enable permissions which are only permitted to be disabled166        OwnerPermissionsCantBeReverted,167        /// Schema data size limit bound exceeded168        SchemaDataLimitExceeded,169        /// Maximum refungibility exceeded170        WrongRefungiblePieces,171        /// createRefungible should be called with one owner172        BadCreateRefungibleCall,173        /// Gas limit exceeded174        OutOfGas,175	}176}177178pub struct CollectionHandle<T: Config> {179    pub id: CollectionId,180    collection: Collection<T>,181    logs: eth::log::LogRecorder,182    evm_address: H160,183    gas_limit: RefCell<u64>,184}185impl<T: Config> CollectionHandle<T> {186	pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {187		<CollectionById<T>>::get(id)188			.map(|collection| Self {189				id,190				collection,191                logs: eth::log::LogRecorder::default(),192                evm_address: eth::collection_id_to_address(id),193                gas_limit: RefCell::new(gas_limit),194			})195	}196    pub fn get(id: CollectionId) -> Option<Self> {197        Self::get_with_gas_limit(id, u64::MAX)198    }199    pub fn gas_left(&self) -> u64 {200        *self.gas_limit.borrow()201    }202    pub fn consume_gas(&self, gas: u64) -> DispatchResult {203        let mut gas_limit = self.gas_limit.borrow_mut();204        if *gas_limit < gas {205            fail!(Error::<T>::OutOfGas);206        }207        *gas_limit -= gas;208        Ok(())209    }210    pub fn log(&self, log: impl evm_coder::ToLog) {211        self.logs.log(log.to_log(self.evm_address))212    }213    pub fn into_inner(self) -> Collection<T> {214        self.collection.clone()215    }216}217impl<T: Config> Deref for CollectionHandle<T> {218    type Target = Collection<T>;219220    fn deref(&self) -> &Self::Target {221        &self.collection222    }223}224225impl<T: Config> DerefMut for CollectionHandle<T> {226    fn deref_mut(&mut self) -> &mut Self::Target {227        &mut self.collection228    }229}230231pub trait Config: system::Config + Sized {232    type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;233234    /// Weight information for extrinsics in this pallet.235	type WeightInfo: WeightInfo;236237    type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;238    type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;239    type EvmWithdrawOrigin: pallet_evm::EnsureAddressOrigin<Self::Origin, Success = Self::AccountId>;240241	type CrossAccountId: CrossAccountId<Self::AccountId>;242    type Currency: Currency<Self::AccountId>;243    type CollectionCreationPrice: Get<<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance>;244    type TreasuryAccountId: Get<Self::AccountId>;245246    type EthereumChainId: Get<u64>;247    type EthereumTransactionSender: pallet_ethereum::EthereumTransactionSender;248}249250// # Used definitions251//252// ## User control levels253//254// chain-controlled - key is uncontrolled by user255//                    i.e autoincrementing index256//                    can use non-cryptographic hash257// real - key is controlled by user258//        but it is hard to generate enough colliding values, i.e owner of signed txs259//        can use non-cryptographic hash260// controlled - key is completly controlled by users261//              i.e maps with mutable keys262//              should use cryptographic hash263//264// ## User control level downgrade reasons265//266// ?1 - chain-controlled -> controlled267//      collections/tokens can be destroyed, resulting in massive holes268// ?2 - chain-controlled -> controlled269//      same as ?1, but can be only added, resulting in easier exploitation270// ?3 - real -> controlled271//      no confirmation required, so addresses can be easily generated272decl_storage! {273    trait Store for Module<T: Config> as Nft {274275        //#region Private members276        /// Id of next collection277        CreatedCollectionCount: u32;278        /// Used for migrations279        ChainVersion: u64;280        /// Id of last collection token281        /// Collection id (controlled?1)282        ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;283        //#endregion284285        //#region Chain limits struct286        pub ChainLimit get(fn chain_limit) config(): ChainLimits;287        //#endregion288289        //#region Bound counters290        /// Amount of collections destroyed, used for total amount tracking with291        /// CreatedCollectionCount292        DestroyedCollectionCount: u32;293        /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)294        /// Account id (real)295        pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;296        //#endregion297298        //#region Basic collections299        /// Collection info300        /// Collection id (controlled?1)301        pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;302        /// List of collection admins303        /// Collection id (controlled?2)304        pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;305        /// Whitelisted collection users306        /// Collection id (controlled?2), user id (controlled?3)307        pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;308        //#endregion309310        /// How many of collection items user have311        /// Collection id (controlled?2), account id (real)312        pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;313314        /// Amount of items which spender can transfer out of owners account (via transferFrom)315        /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))316        /// TODO: Off chain worker should remove from this map when token gets removed317        pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;318319        //#region Item collections320        /// Collection id (controlled?2), token id (controlled?1)321        pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;322        /// Collection id (controlled?2), owner (controlled?2)323        pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;324        /// Collection id (controlled?2), token id (controlled?1)325        pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;326        //#endregion327328        //#region Index list329        /// Collection id (controlled?2), tokens owner (controlled?2)330        pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;331        //#endregion332333        //#region Tokens transfer rate limit baskets334        /// (Collection id (controlled?2), who created (real))335        /// TODO: Off chain worker should remove from this map when collection gets removed336        pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;337        /// Collection id (controlled?2), token id (controlled?2)338        pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;339        /// Collection id (controlled?2), owning user (real)340        pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;341        /// Collection id (controlled?2), token id (controlled?2)342        pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;343        //#endregion344345        /// Variable metadata sponsoring346        /// Collection id (controlled?2), token id (controlled?2)347        pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;348    }349    add_extra_genesis {350        build(|config: &GenesisConfig<T>| {351            // Modification of storage352            for (_num, _c) in &config.collection_id {353                <Module<T>>::init_collection(_c);354            }355356            for (_num, _c, _i) in &config.nft_item_id {357                <Module<T>>::init_nft_token(*_c, _i);358            }359360            for (collection_id, account_id, fungible_item) in &config.fungible_item_id {361                <Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);362            }363364            for (_num, _c, _i) in &config.refungible_item_id {365                <Module<T>>::init_refungible_token(*_c, _i);366            }367        })368    }369}370371decl_event!(372    pub enum Event<T>373    where374        AccountId = <T as frame_system::Config>::AccountId,375        CrossAccountId = <T as Config>::CrossAccountId,376    {377        /// New collection was created378        /// 379        /// # Arguments380        /// 381        /// * collection_id: Globally unique identifier of newly created collection.382        /// 383        /// * mode: [CollectionMode] converted into u8.384        /// 385        /// * account_id: Collection owner.386        CollectionCreated(CollectionId, u8, AccountId),387388        /// New item was created.389        /// 390        /// # Arguments391        /// 392        /// * collection_id: Id of the collection where item was created.393        /// 394        /// * item_id: Id of an item. Unique within the collection.395        ///396        /// * recipient: Owner of newly created item 397        ItemCreated(CollectionId, TokenId, CrossAccountId),398399        /// Collection item was burned.400        /// 401        /// # Arguments402        /// 403        /// collection_id.404        /// 405        /// item_id: Identifier of burned NFT.406        ItemDestroyed(CollectionId, TokenId),407408        /// Item was transferred409        ///410        /// * collection_id: Id of collection to which item is belong411        ///412        /// * item_id: Id of an item413        ///414        /// * sender: Original owner of item415        ///416        /// * recipient: New owner of item417        ///418        /// * amount: Always 1 for NFT419        Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),420421        /// * collection_id422        ///423        /// * item_id424        ///425        /// * sender426        ///427        /// * spender428        ///429        /// * amount430        Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),431    }432);433434decl_module! {435    pub struct Module<T: Config> for enum Call 436    where 437        origin: T::Origin438    {439        fn deposit_event() = default;440        type Error = Error<T>;441442        fn on_initialize(_now: T::BlockNumber) -> Weight {443            0444        }445446        /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.447        /// 448        /// # Permissions449        /// 450        /// * Anyone.451        /// 452        /// # Arguments453        /// 454        /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.455        /// 456        /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.457        /// 458        /// * token_prefix: UTF-8 string with token prefix.459        /// 460        /// * mode: [CollectionMode] collection type and type dependent data.461        // returns collection ID462        #[weight = <T as Config>::WeightInfo::create_collection()]463        #[transactional]464        pub fn create_collection(origin,465                                 collection_name: Vec<u16>,466                                 collection_description: Vec<u16>,467                                 token_prefix: Vec<u8>,468                                 mode: CollectionMode) -> DispatchResult {469470            // Anyone can create a collection471            let who = ensure_signed(origin)?;472473            // Take a (non-refundable) deposit of collection creation474            let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();475            imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(476                &T::TreasuryAccountId::get(),477                T::CollectionCreationPrice::get(),478            ));479            <T as Config>::Currency::settle(480                &who,481                imbalance,482                WithdrawReasons::TRANSFER,483                ExistenceRequirement::KeepAlive,484            ).map_err(|_| Error::<T>::NoPermission)?;485486            let decimal_points = match mode {487                CollectionMode::Fungible(points) => points,488                _ => 0489            };490491            let chain_limit = ChainLimit::get();492493            let created_count = CreatedCollectionCount::get();494            let destroyed_count = DestroyedCollectionCount::get();495496            // bound Total number of collections497            ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);498499            // check params500            ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);501            ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);502            ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);503            ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);504505            // Generate next collection ID506            let next_id = created_count507                .checked_add(1)508                .ok_or(Error::<T>::NumOverflow)?;509510            CreatedCollectionCount::put(next_id);511512            let limits = CollectionLimits {513                sponsored_data_size: chain_limit.custom_data_limit,514                ..Default::default()515            };516517            // Create new collection518            let new_collection = Collection {519                owner: who.clone(),520                name: collection_name,521                mode: mode.clone(),522                mint_mode: false,523                access: AccessMode::Normal,524                description: collection_description,525                decimal_points: decimal_points,526                token_prefix: token_prefix,527                offchain_schema: Vec::new(),528                schema_version: SchemaVersion::ImageURL,529                sponsorship: SponsorshipState::Disabled,530                variable_on_chain_schema: Vec::new(),531                const_on_chain_schema: Vec::new(),532                limits,533            };534535            // Add new collection to map536            <CollectionById<T>>::insert(next_id, new_collection);537538            // call event539            Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.into(), who));540541            Ok(())542        }543544        /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.545        /// 546        /// # Permissions547        /// 548        /// * Collection Owner.549        /// 550        /// # Arguments551        /// 552        /// * collection_id: collection to destroy.553        #[weight = <T as Config>::WeightInfo::destroy_collection()]554        #[transactional]555        pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {556557            let sender = ensure_signed(origin)?;558            let collection = Self::get_collection(collection_id)?;559            Self::check_owner_permissions(&collection, &sender)?;560            if !collection.limits.owner_can_destroy {561                fail!(Error::<T>::NoPermission);562            }563564            <AddressTokens<T>>::remove_prefix(collection_id);565            <Allowances<T>>::remove_prefix(collection_id);566            <Balance<T>>::remove_prefix(collection_id);567            <ItemListIndex>::remove(collection_id);568            <AdminList<T>>::remove(collection_id);569            <CollectionById<T>>::remove(collection_id);570            <WhiteList<T>>::remove_prefix(collection_id);571572            <NftItemList<T>>::remove_prefix(collection_id);573            <FungibleItemList<T>>::remove_prefix(collection_id);574            <ReFungibleItemList<T>>::remove_prefix(collection_id);575576            <NftTransferBasket<T>>::remove_prefix(collection_id);577            <FungibleTransferBasket<T>>::remove_prefix(collection_id);578            <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);579580            <VariableMetaDataBasket<T>>::remove_prefix(collection_id);581582            DestroyedCollectionCount::put(DestroyedCollectionCount::get()583                .checked_add(1)584                .ok_or(Error::<T>::NumOverflow)?);585586            Ok(())587        }588589        /// Add an address to white list.590        /// 591        /// # Permissions592        /// 593        /// * Collection Owner594        /// * Collection Admin595        /// 596        /// # Arguments597        /// 598        /// * collection_id.599        /// 600        /// * address.601        #[weight = <T as Config>::WeightInfo::add_to_white_list()]602        #[transactional]603        pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{604605            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);606            let collection = Self::get_collection(collection_id)?;607608            Self::toggle_white_list_internal(609                &sender,610                &collection,611                &address,612                true,613            )?;614615            Ok(())616        }617618        /// Remove an address from white list.619        /// 620        /// # Permissions621        /// 622        /// * Collection Owner623        /// * Collection Admin624        /// 625        /// # Arguments626        /// 627        /// * collection_id.628        /// 629        /// * address.630        #[weight = <T as Config>::WeightInfo::remove_from_white_list()]631        #[transactional]632        pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{633634            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);635            let collection = Self::get_collection(collection_id)?;636637            Self::toggle_white_list_internal(638                &sender,639                &collection,640                &address,641                false,642            )?;643644            Ok(())645        }646647        /// Toggle between normal and white list access for the methods with access for `Anyone`.648        /// 649        /// # Permissions650        /// 651        /// * Collection Owner.652        /// 653        /// # Arguments654        /// 655        /// * collection_id.656        /// 657        /// * mode: [AccessMode]658        #[weight = <T as Config>::WeightInfo::set_public_access_mode()]659        #[transactional]660        pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult661        {662            let sender = ensure_signed(origin)?;663664            let mut target_collection = Self::get_collection(collection_id)?;665            Self::check_owner_permissions(&target_collection, &sender)?;666            target_collection.access = mode;667            Self::save_collection(target_collection);668669            Ok(())670        }671672        /// Allows Anyone to create tokens if:673        /// * White List is enabled, and674        /// * Address is added to white list, and675        /// * This method was called with True parameter676        /// 677        /// # Permissions678        /// * Collection Owner679        ///680        /// # Arguments681        /// 682        /// * collection_id.683        /// 684        /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.685        #[weight = <T as Config>::WeightInfo::set_mint_permission()]686        #[transactional]687        pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult688        {689            let sender = ensure_signed(origin)?;690691            let mut target_collection = Self::get_collection(collection_id)?;692            Self::check_owner_permissions(&target_collection, &sender)?;693            target_collection.mint_mode = mint_permission;694            Self::save_collection(target_collection);695696            Ok(())697        }698699        /// Change the owner of the collection.700        /// 701        /// # Permissions702        /// 703        /// * Collection Owner.704        /// 705        /// # Arguments706        /// 707        /// * collection_id.708        /// 709        /// * new_owner.710        #[weight = <T as Config>::WeightInfo::change_collection_owner()]711        #[transactional]712        pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {713714            let sender = ensure_signed(origin)?;715            let mut target_collection = Self::get_collection(collection_id)?;716            Self::check_owner_permissions(&target_collection, &sender)?;717            target_collection.owner = new_owner;718            Self::save_collection(target_collection);719720            Ok(())721        }722723        /// Adds an admin of the Collection.724        /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership. 725        /// 726        /// # Permissions727        /// 728        /// * Collection Owner.729        /// * Collection Admin.730        /// 731        /// # Arguments732        /// 733        /// * collection_id: ID of the Collection to add admin for.734        /// 735        /// * new_admin_id: Address of new admin to add.736        #[weight = <T as Config>::WeightInfo::add_collection_admin()]737        #[transactional]738        pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {739            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);740            let collection = Self::get_collection(collection_id)?;741            Self::check_owner_or_admin_permissions(&collection, &sender)?;742            let mut admin_arr = <AdminList<T>>::get(collection_id);743744            match admin_arr.binary_search(&new_admin_id) {745                Ok(_) => {},746                Err(idx) => {747                    let limits = ChainLimit::get();748                    ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);749                    admin_arr.insert(idx, new_admin_id);750                    <AdminList<T>>::insert(collection_id, admin_arr);751                }752            }753            Ok(())754        }755756        /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.757        ///758        /// # Permissions759        /// 760        /// * Collection Owner.761        /// * Collection Admin.762        /// 763        /// # Arguments764        /// 765        /// * collection_id: ID of the Collection to remove admin for.766        /// 767        /// * account_id: Address of admin to remove.768        #[weight = <T as Config>::WeightInfo::remove_collection_admin()]769        #[transactional]770        pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {771            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);772            let collection = Self::get_collection(collection_id)?;773            Self::check_owner_or_admin_permissions(&collection, &sender)?;774            let mut admin_arr = <AdminList<T>>::get(collection_id);775776            match admin_arr.binary_search(&account_id) {777                Ok(idx) => {778                    admin_arr.remove(idx);779                    <AdminList<T>>::insert(collection_id, admin_arr);780                },781                Err(_) => {}782            }783            Ok(())784        }785786        /// # Permissions787        /// 788        /// * Collection Owner789        /// 790        /// # Arguments791        /// 792        /// * collection_id.793        /// 794        /// * new_sponsor.795        #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]796        #[transactional]797        pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {798            let sender = ensure_signed(origin)?;799            let mut target_collection = Self::get_collection(collection_id)?;800            Self::check_owner_permissions(&target_collection, &sender)?;801802            target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);803            Self::save_collection(target_collection);804805            Ok(())806        }807808        /// # Permissions809        /// 810        /// * Sponsor.811        /// 812        /// # Arguments813        /// 814        /// * collection_id.815        #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]816        #[transactional]817        pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {818            let sender = ensure_signed(origin)?;819820            let mut target_collection = Self::get_collection(collection_id)?;821            ensure!(822                target_collection.sponsorship.pending_sponsor() == Some(&sender),823                Error::<T>::ConfirmUnsetSponsorFail824            );825826            target_collection.sponsorship = SponsorshipState::Confirmed(sender);827            Self::save_collection(target_collection);828829            Ok(())830        }831832        /// Switch back to pay-per-own-transaction model.833        ///834        /// # Permissions835        ///836        /// * Collection owner.837        /// 838        /// # Arguments839        /// 840        /// * collection_id.841        #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]842        #[transactional]843        pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {844            let sender = ensure_signed(origin)?;845846            let mut target_collection = Self::get_collection(collection_id)?;847            Self::check_owner_permissions(&target_collection, &sender)?;848849            target_collection.sponsorship = SponsorshipState::Disabled;850            Self::save_collection(target_collection);851852            Ok(())853        }854855        /// This method creates a concrete instance of NFT Collection created with CreateCollection method.856        /// 857        /// # Permissions858        /// 859        /// * Collection Owner.860        /// * Collection Admin.861        /// * Anyone if862        ///     * White List is enabled, and863        ///     * Address is added to white list, and864        ///     * MintPermission is enabled (see SetMintPermission method)865        /// 866        /// # Arguments867        /// 868        /// * collection_id: ID of the collection.869        /// 870        /// * owner: Address, initial owner of the NFT.871        ///872        /// * data: Token data to store on chain.873        // #[weight =874        // (130_000_000 as Weight)875        // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))876        // .saturating_add(RocksDbWeight::get().reads(10 as Weight))877        // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]878879        #[weight = <T as Config>::WeightInfo::create_item(data.len())]880        #[transactional]881        pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {882            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);883            let collection = Self::get_collection(collection_id)?;884885            Self::create_item_internal(&sender, &collection, &owner, data)?;886887            Self::submit_logs(collection)?;888            Ok(())889        }890891        /// This method creates multiple items in a collection created with CreateCollection method.892        /// 893        /// # Permissions894        /// 895        /// * Collection Owner.896        /// * Collection Admin.897        /// * Anyone if898        ///     * White List is enabled, and899        ///     * Address is added to white list, and900        ///     * MintPermission is enabled (see SetMintPermission method)901        /// 902        /// # Arguments903        /// 904        /// * collection_id: ID of the collection.905        /// 906        /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].907        /// 908        /// * owner: Address, initial owner of the NFT.909        #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()910                               .map(|data| { data.len() })911                               .sum())]912        #[transactional]913        pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {914915            ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);916            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);917            let collection = Self::get_collection(collection_id)?;918919            Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;920921            Self::submit_logs(collection)?;922            Ok(())923        }924925        /// Destroys a concrete instance of NFT.926        /// 927        /// # Permissions928        /// 929        /// * Collection Owner.930        /// * Collection Admin.931        /// * Current NFT Owner.932        /// 933        /// # Arguments934        /// 935        /// * collection_id: ID of the collection.936        /// 937        /// * item_id: ID of NFT to burn.938        #[weight = <T as Config>::WeightInfo::burn_item()]939        #[transactional]940        pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {941942            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);943            let target_collection = Self::get_collection(collection_id)?;944945            Self::burn_item_internal(&sender, &target_collection, item_id, value)?;946947            Self::submit_logs(target_collection)?;948            Ok(())949        }950951        /// Change ownership of the token.952        /// 953        /// # Permissions954        /// 955        /// * Collection Owner956        /// * Collection Admin957        /// * Current NFT owner958        ///959        /// # Arguments960        /// 961        /// * recipient: Address of token recipient.962        /// 963        /// * collection_id.964        /// 965        /// * item_id: ID of the item966        ///     * Non-Fungible Mode: Required.967        ///     * Fungible Mode: Ignored.968        ///     * Re-Fungible Mode: Required.969        /// 970        /// * value: Amount to transfer.971        ///     * Non-Fungible Mode: Ignored972        ///     * Fungible Mode: Must specify transferred amount973        ///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)974        #[weight = <T as Config>::WeightInfo::transfer()]975        #[transactional]976        pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {977            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);978            let collection = Self::get_collection(collection_id)?;979980            Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;981982            Self::submit_logs(collection)?;983            Ok(())984        }985986        /// Set, change, or remove approved address to transfer the ownership of the NFT.987        /// 988        /// # Permissions989        /// 990        /// * Collection Owner991        /// * Collection Admin992        /// * Current NFT owner993        /// 994        /// # Arguments995        /// 996        /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).997        /// 998        /// * collection_id.999        /// 1000        /// * item_id: ID of the item.1001        #[weight = <T as Config>::WeightInfo::approve()]1002        #[transactional]1003        pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1004            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1005            let collection = Self::get_collection(collection_id)?;10061007            Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10081009            Self::submit_logs(collection)?;1010            Ok(())1011        }1012        1013        /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1014        /// 1015        /// # Permissions1016        /// * Collection Owner1017        /// * Collection Admin1018        /// * Current NFT owner1019        /// * Address approved by current NFT owner1020        /// 1021        /// # Arguments1022        /// 1023        /// * from: Address that owns token.1024        /// 1025        /// * recipient: Address of token recipient.1026        /// 1027        /// * collection_id.1028        /// 1029        /// * item_id: ID of the item.1030        /// 1031        /// * value: Amount to transfer.1032        #[weight = <T as Config>::WeightInfo::transfer_from()]1033        #[transactional]1034        pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1035            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1036            let collection = Self::get_collection(collection_id)?;10371038            Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10391040            Self::submit_logs(collection)?;1041            Ok(())1042        }1043        // #[weight = 0]1044        //     // let no_perm_mes = "You do not have permissions to modify this collection";1045        //     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1046        //     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1047        //     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10481049        //     // // on_nft_received  call10501051        //     // Self::transfer(origin, collection_id, item_id, new_owner)?;10521053        //     Ok(())1054        // }10551056        /// Set off-chain data schema.1057        /// 1058        /// # Permissions1059        /// 1060        /// * Collection Owner1061        /// * Collection Admin1062        /// 1063        /// # Arguments1064        /// 1065        /// * collection_id.1066        /// 1067        /// * schema: String representing the offchain data schema.1068        #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1069        #[transactional]1070        pub fn set_variable_meta_data (1071            origin,1072            collection_id: CollectionId,1073            item_id: TokenId,1074            data: Vec<u8>1075        ) -> DispatchResult {1076            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1077            1078            let collection = Self::get_collection(collection_id)?;10791080            Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10811082            Ok(())1083        }1084 1085        /// Set schema standard1086        /// ImageURL1087        /// Unique1088        /// 1089        /// # Permissions1090        /// 1091        /// * Collection Owner1092        /// * Collection Admin1093        /// 1094        /// # Arguments1095        /// 1096        /// * collection_id.1097        /// 1098        /// * schema: SchemaVersion: enum1099        #[weight = <T as Config>::WeightInfo::set_schema_version()]1100        #[transactional]1101        pub fn set_schema_version(1102            origin,1103            collection_id: CollectionId,1104            version: SchemaVersion1105        ) -> DispatchResult {1106            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1107            let mut target_collection = Self::get_collection(collection_id)?;1108            Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1109            target_collection.schema_version = version;1110            Self::save_collection(target_collection);11111112            Ok(())1113        }11141115        /// Set off-chain data schema.1116        /// 1117        /// # Permissions1118        /// 1119        /// * Collection Owner1120        /// * Collection Admin1121        /// 1122        /// # Arguments1123        /// 1124        /// * collection_id.1125        /// 1126        /// * schema: String representing the offchain data schema.1127        #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1128        #[transactional]1129        pub fn set_offchain_schema(1130            origin,1131            collection_id: CollectionId,1132            schema: Vec<u8>1133        ) -> DispatchResult {1134            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1135            let mut target_collection = Self::get_collection(collection_id)?;1136            Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11371138            // check schema limit1139            ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");11401141            target_collection.offchain_schema = schema;1142            Self::save_collection(target_collection);11431144            Ok(())1145        }11461147        /// Set const on-chain data schema.1148        /// 1149        /// # Permissions1150        /// 1151        /// * Collection Owner1152        /// * Collection Admin1153        /// 1154        /// # Arguments1155        /// 1156        /// * collection_id.1157        /// 1158        /// * schema: String representing the const on-chain data schema.1159        #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1160        #[transactional]1161        pub fn set_const_on_chain_schema (1162            origin,1163            collection_id: CollectionId,1164            schema: Vec<u8>1165        ) -> DispatchResult {1166            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1167            let mut target_collection = Self::get_collection(collection_id)?;1168            Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11691170            // check schema limit1171            ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");11721173            target_collection.const_on_chain_schema = schema;1174            Self::save_collection(target_collection);11751176            Ok(())1177        }11781179        /// Set variable on-chain data schema.1180        /// 1181        /// # Permissions1182        /// 1183        /// * Collection Owner1184        /// * Collection Admin1185        /// 1186        /// # Arguments1187        /// 1188        /// * collection_id.1189        /// 1190        /// * schema: String representing the variable on-chain data schema.1191        #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1192        #[transactional]1193        pub fn set_variable_on_chain_schema (1194            origin,1195            collection_id: CollectionId,1196            schema: Vec<u8>1197        ) -> DispatchResult {1198            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1199            let mut target_collection = Self::get_collection(collection_id)?;1200            Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12011202            // check schema limit1203            ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");12041205            target_collection.variable_on_chain_schema = schema;1206            Self::save_collection(target_collection);12071208            Ok(())1209        }12101211        // Sudo permissions function1212        #[weight = <T as Config>::WeightInfo::set_chain_limits()]1213        #[transactional]1214        pub fn set_chain_limits(1215            origin,1216            limits: ChainLimits1217        ) -> DispatchResult {12181219            #[cfg(not(feature = "runtime-benchmarks"))]1220            ensure_root(origin)?;12211222            <ChainLimit>::put(limits);1223            Ok(())1224        }12251226        #[weight = <T as Config>::WeightInfo::set_collection_limits()]1227        #[transactional]1228        pub fn set_collection_limits(1229            origin,1230            collection_id: u32,1231            new_limits: CollectionLimits<T::BlockNumber>,1232        ) -> DispatchResult {1233            let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1234            let mut target_collection = Self::get_collection(collection_id)?;1235            Self::check_owner_permissions(&target_collection, &sender.as_sub())?;1236            let old_limits = &target_collection.limits;1237            let chain_limits = ChainLimit::get();12381239            // collection bounds1240            ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1241                new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1242                new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1243                Error::<T>::CollectionLimitBoundsExceeded);12441245            // token_limit   check  prev1246            ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1247            ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12481249            ensure!(1250                (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1251                (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1252                Error::<T>::OwnerPermissionsCantBeReverted,1253            );12541255            target_collection.limits = new_limits;1256            Self::save_collection(target_collection);12571258            Ok(())1259        } 1260    }1261}12621263impl<T: Config> Module<T> {1264    pub fn create_item_internal(sender: &T::CrossAccountId, collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {1265        Self::can_create_items_in_collection(&collection, &sender, &owner, 1)?;1266        Self::validate_create_item_args(&collection, &data)?;1267        Self::create_item_no_validation(&collection, owner, data)?;12681269        Ok(())1270    }12711272    pub fn transfer_internal(sender: &T::CrossAccountId, recipient: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1273        target_collection.consume_gas(2000000)?;1274        // Limits check1275        Self::is_correct_transfer(target_collection, &recipient)?;12761277        // Transfer permissions check1278        ensure!(Self::is_item_owner(&sender, target_collection, item_id) ||1279            Self::is_owner_or_admin_permissions(target_collection, &sender),1280            Error::<T>::NoPermission);12811282        if target_collection.access == AccessMode::WhiteList {1283            Self::check_white_list(target_collection, &sender)?;1284            Self::check_white_list(target_collection, &recipient)?;1285        }12861287        match target_collection.mode1288        {1289            CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1290            CollectionMode::Fungible(_)  => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1291            CollectionMode::ReFungible  => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1292            _ => ()1293        };12941295        Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender.clone(), recipient.clone(), value));12961297        Ok(())1298    }12991300	pub fn approve_internal(1301		sender: &T::CrossAccountId,1302		spender: &T::CrossAccountId,1303		collection: &CollectionHandle<T>,1304		item_id: TokenId,1305		amount: u1281306	) -> DispatchResult {1307        collection.consume_gas(2000000)?;1308		Self::token_exists(&collection, item_id)?;13091310		// Transfer permissions check1311		let bypasses_limits = collection.limits.owner_can_transfer &&1312			Self::is_owner_or_admin_permissions(1313				&collection,1314				&sender,1315			);13161317		let allowance_limit = if bypasses_limits {1318			None1319		} else if let Some(amount) = Self::owned_amount(1320			&sender,1321			&collection,1322			item_id,1323		) {1324			Some(amount)1325		} else {1326			fail!(Error::<T>::NoPermission);1327		};13281329		if collection.access == AccessMode::WhiteList {1330			Self::check_white_list(&collection, &sender)?;1331			Self::check_white_list(&collection, &spender)?;1332		}13331334		let allowance: u128 = amount1335			.checked_add(<Allowances<T>>::get(collection.id, (item_id, sender.as_sub(), spender.as_sub())))1336			.ok_or(Error::<T>::NumOverflow)?;1337		if let Some(limit) = allowance_limit {1338			ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1339		}1340		<Allowances<T>>::insert(collection.id, (item_id, sender.as_sub(), spender.as_sub()), allowance);13411342		if matches!(collection.mode, CollectionMode::NFT) {1343			// TODO: NFT: only one owner may exist for token in ERC7211344			collection.log(ERC721Events::Approval {1345                owner: *sender.as_eth(),1346                approved: *spender.as_eth(),1347                token_id: item_id.into(),1348            });1349		}13501351		if matches!(collection.mode, CollectionMode::Fungible(_)) {1352			// TODO: NFT: only one owner may exist for token in ERC201353			collection.log(ERC20Events::Approval {1354                owner: *sender.as_eth(),1355                spender: *spender.as_eth(),1356                value: allowance.into()1357            });1358		}13591360		Self::deposit_event(RawEvent::Approved(collection.id, item_id, sender.clone(), spender.clone(), allowance));1361		Ok(())1362	}13631364	pub fn transfer_from_internal(1365		sender: &T::CrossAccountId,1366		from: &T::CrossAccountId,1367		recipient: &T::CrossAccountId,1368		collection: &CollectionHandle<T>,1369		item_id: TokenId,1370		amount: u128,1371	) -> DispatchResult {1372        collection.consume_gas(2000000)?;1373		// Check approval1374		let approval: u128 = <Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));13751376		// Limits check1377		Self::is_correct_transfer(&collection, &recipient)?;13781379		// Transfer permissions check1380		ensure!(1381			approval >= amount || 1382			(1383				collection.limits.owner_can_transfer &&1384				Self::is_owner_or_admin_permissions(&collection, &sender)1385			),1386			Error::<T>::NoPermission1387		);13881389		if collection.access == AccessMode::WhiteList {1390			Self::check_white_list(&collection, &sender)?;1391			Self::check_white_list(&collection, &recipient)?;1392		}13931394		// Reduce approval by transferred amount or remove if remaining approval drops to 01395		let allowance = approval.saturating_sub(amount);1396		if allowance > 0 {1397			<Allowances<T>>::insert(collection.id, (item_id, from.as_sub(), sender.as_sub()), allowance);1398		} else {1399			<Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1400		}14011402		match collection.mode {1403			CollectionMode::NFT => {1404				Self::transfer_nft(&collection, item_id, from.clone(), recipient.clone())?1405			}1406			CollectionMode::Fungible(_) => {1407				Self::transfer_fungible(&collection, amount, &from, &recipient)?1408			}1409			CollectionMode::ReFungible => {1410				Self::transfer_refungible(&collection, item_id, amount, from.clone(), recipient.clone())?1411			}1412			_ => ()1413		};14141415		if matches!(collection.mode, CollectionMode::Fungible(_)) {1416			collection.log(ERC20Events::Approval {1417                owner: *from.as_eth(),1418                spender: *sender.as_eth(),1419                value: allowance.into()1420            });1421		}14221423		Ok(())1424	}14251426    pub fn set_variable_meta_data_internal(1427        sender: &T::CrossAccountId,1428        collection: &CollectionHandle<T>, 1429        item_id: TokenId,1430        data: Vec<u8>,1431    ) -> DispatchResult {1432        Self::token_exists(&collection, item_id)?;14331434        ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);14351436        // Modify permissions check1437        ensure!(Self::is_item_owner(&sender, &collection, item_id) ||1438            Self::is_owner_or_admin_permissions(&collection, &sender),1439            Error::<T>::NoPermission);14401441        match collection.mode1442        {1443            CollectionMode::NFT => Self::set_nft_variable_data(&collection, item_id, data)?,1444            CollectionMode::ReFungible  => Self::set_re_fungible_variable_data(&collection, item_id, data)?,1445            CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1446            _ => fail!(Error::<T>::UnexpectedCollectionType)1447        };14481449        Ok(())1450    }14511452    pub fn create_multiple_items_internal(1453        sender: &T::CrossAccountId,1454        collection: &CollectionHandle<T>,1455        owner: &T::CrossAccountId,1456        items_data: Vec<CreateItemData>,1457    ) -> DispatchResult {1458        Self::can_create_items_in_collection(&collection, &sender, &owner, items_data.len() as u32)?;14591460        for data in &items_data {1461            Self::validate_create_item_args(&collection, data)?;1462        }1463        for data in &items_data {1464            Self::create_item_no_validation(&collection, owner, data.clone())?;1465        }14661467        Ok(())1468    }14691470    pub fn burn_item_internal(1471        sender: &T::CrossAccountId,1472        collection: &CollectionHandle<T>,1473        item_id: TokenId,1474        value: u128,1475    ) -> DispatchResult {1476        ensure!(1477            Self::is_item_owner(&sender, &collection, item_id) ||1478            (1479                collection.limits.owner_can_transfer &&1480                Self::is_owner_or_admin_permissions(&collection, &sender)1481            ),1482            Error::<T>::NoPermission1483        );14841485        if collection.access == AccessMode::WhiteList {1486            Self::check_white_list(&collection, &sender)?;1487        }14881489        match collection.mode1490        {1491            CollectionMode::NFT => Self::burn_nft_item(&collection, item_id)?,1492            CollectionMode::Fungible(_)  => Self::burn_fungible_item(&sender, &collection, value)?,1493            CollectionMode::ReFungible  => Self::burn_refungible_item(&collection, item_id, &sender)?,1494            _ => ()1495        };14961497        Ok(())1498    }14991500    pub fn toggle_white_list_internal(1501        sender: &T::CrossAccountId,1502        collection: &CollectionHandle<T>,1503        address: &T::CrossAccountId,1504        whitelisted: bool,1505    ) -> DispatchResult {1506        Self::check_owner_or_admin_permissions(&collection, &sender)?;15071508        if whitelisted {1509            <WhiteList<T>>::insert(collection.id, address.as_sub(), true);1510        } else {1511            <WhiteList<T>>::remove(collection.id, address.as_sub());1512        }15131514        Ok(())1515    }15161517    fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::CrossAccountId) -> DispatchResult {1518        let collection_id = collection.id;15191520        // check token limit and account token limit1521        let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1522        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);1523        1524        Ok(())1525    }15261527    fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::CrossAccountId, owner: &T::CrossAccountId, amount: u32) -> DispatchResult {1528        let collection_id = collection.id;15291530        // check token limit and account token limit1531        let total_items: u32 = ItemListIndex::get(collection_id)1532            .checked_add(amount)1533            .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1534        let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len() as u32)1535            .checked_add(amount)1536            .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1537        ensure!(collection.limits.token_limit >= total_items,  Error::<T>::CollectionTokenLimitExceeded);1538        ensure!(collection.limits.account_token_ownership_limit >= account_items,  Error::<T>::AccountTokenLimitExceeded);15391540        if !Self::is_owner_or_admin_permissions(collection, &sender) {1541            ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1542            Self::check_white_list(collection, owner)?;1543            Self::check_white_list(collection, sender)?;1544        }15451546        Ok(())1547    }15481549    fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1550        match target_collection.mode1551        {1552            CollectionMode::NFT => {1553                if let CreateItemData::NFT(data) = data {1554                    // check sizes1555                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1556                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1557                } else {1558                    fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1559                }1560            },1561            CollectionMode::Fungible(_) => {1562                if let CreateItemData::Fungible(_) = data {1563                } else {1564                    fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1565                }1566            },1567            CollectionMode::ReFungible => {1568                if let CreateItemData::ReFungible(data) = data {15691570                    // check sizes1571                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1572                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);15731574                    // Check refungibility limits1575                    ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1576                    ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1577                } else {1578                    fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1579                }1580            },1581            _ => { fail!(Error::<T>::UnexpectedCollectionType); }1582        };15831584        Ok(())1585    }15861587    fn create_item_no_validation(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {1588        match data1589        {1590            CreateItemData::NFT(data) => {1591                let item = NftItemType {1592                    owner: owner.clone(),1593                    const_data: data.const_data,1594                    variable_data: data.variable_data1595                };15961597                Self::add_nft_item(collection, item)?;1598            },1599            CreateItemData::Fungible(data) => {1600                Self::add_fungible_item(collection, &owner, data.value)?;1601            },1602            CreateItemData::ReFungible(data) => {1603                let mut owner_list = Vec::new();1604                owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});16051606                let item = ReFungibleItemType {1607                    owner: owner_list,1608                    const_data: data.const_data,1609                    variable_data: data.variable_data1610                };16111612                Self::add_refungible_item(collection, item)?;1613            }1614        };16151616        Ok(())1617    }16181619    fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, value: u128) -> DispatchResult {1620        let collection_id = collection.id;16211622        // Does new owner already have an account?1623        let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;16241625        // Mint 1626        let item = FungibleItemType {1627            value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1628        };1629        <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);16301631        // Update balance1632        let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1633            .checked_add(value)1634            .ok_or(Error::<T>::NumOverflow)?;1635        <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);16361637        Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1638        Ok(())1639    }16401641    fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::CrossAccountId>) -> DispatchResult {1642        let collection_id = collection.id;16431644        let current_index = <ItemListIndex>::get(collection_id)1645            .checked_add(1)1646            .ok_or(Error::<T>::NumOverflow)?;1647        let itemcopy = item.clone();16481649        ensure!(1650            item.owner.len() == 1,1651            Error::<T>::BadCreateRefungibleCall,1652        );1653        let item_owner = item.owner.first().expect("only one owner is defined");16541655        let value = item_owner.fraction;1656        let owner = item_owner.owner.clone();16571658        Self::add_token_index(collection_id, current_index, &owner)?;16591660        <ItemListIndex>::insert(collection_id, current_index);1661        <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16621663        // Update balance1664        let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1665            .checked_add(value)1666            .ok_or(Error::<T>::NumOverflow)?;1667        <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);16681669        Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1670        Ok(())1671    }16721673    fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::CrossAccountId>) -> DispatchResult {1674        let collection_id = collection.id;16751676        let current_index = <ItemListIndex>::get(collection_id)1677            .checked_add(1)1678            .ok_or(Error::<T>::NumOverflow)?;16791680        let item_owner = item.owner.clone();1681        Self::add_token_index(collection_id, current_index, &item.owner)?;16821683        <ItemListIndex>::insert(collection_id, current_index);1684        <NftItemList<T>>::insert(collection_id, current_index, item);16851686        // Update balance1687        let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1688            .checked_add(1)1689            .ok_or(Error::<T>::NumOverflow)?;1690        <Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);16911692        collection.log(ERC721Events::Transfer {1693            from: H160::default(),1694            to: *item_owner.as_eth(),1695            token_id: current_index.into(),1696        });1697        Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));1698        Ok(())1699    }17001701    fn burn_refungible_item(1702        collection: &CollectionHandle<T>,1703        item_id: TokenId,1704        owner: &T::CrossAccountId,1705    ) -> DispatchResult {1706        let collection_id = collection.id;17071708        let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1709            .ok_or(Error::<T>::TokenNotFound)?;1710        let rft_balance = token1711            .owner1712            .iter()1713            .find(|&i| i.owner == *owner)1714            .ok_or(Error::<T>::TokenNotFound)?;1715        Self::remove_token_index(collection_id, item_id, owner)?;17161717        // update balance1718        let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1719            .checked_sub(rft_balance.fraction)1720            .ok_or(Error::<T>::NumOverflow)?;1721        <Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);17221723        // Re-create owners list with sender removed1724        let index = token1725            .owner1726            .iter()1727            .position(|i| i.owner == *owner)1728            .expect("owned item is exists");1729        token.owner.remove(index);1730        let owner_count = token.owner.len();17311732        // Burn the token completely if this was the last (only) owner1733        if owner_count == 0 {1734            <ReFungibleItemList<T>>::remove(collection_id, item_id);1735            <VariableMetaDataBasket<T>>::remove(collection_id, item_id);1736        }1737        else {1738            <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1739        }17401741        Ok(())1742    }17431744    fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1745        let collection_id = collection.id;17461747        let item = <NftItemList<T>>::get(collection_id, item_id)1748            .ok_or(Error::<T>::TokenNotFound)?;1749        Self::remove_token_index(collection_id, item_id, &item.owner)?;17501751        // update balance1752        let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1753            .checked_sub(1)1754            .ok_or(Error::<T>::NumOverflow)?;1755        <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1756        <NftItemList<T>>::remove(collection_id, item_id);1757        <VariableMetaDataBasket<T>>::remove(collection_id, item_id);17581759        Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1760        Ok(())1761    }17621763    fn burn_fungible_item(owner: &T::CrossAccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {1764        let collection_id = collection.id;17651766        let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1767        ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17681769        // update balance1770        let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1771            .checked_sub(value)1772            .ok_or(Error::<T>::NumOverflow)?;1773        <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17741775        if balance.value - value > 0 {1776            balance.value -= value;1777            <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1778        }1779        else {1780            <FungibleItemList<T>>::remove(collection_id, owner.as_sub());1781        }17821783        collection.log(ERC20Events::Transfer {1784            from: *owner.as_eth(),1785            to: H160::default(),1786            value: value.into(),1787        });1788        Ok(())1789    }17901791    pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1792        Ok(<CollectionHandle<T>>::get(collection_id)1793            .ok_or(Error::<T>::CollectionNotFound)?)1794    }17951796    fn save_collection(collection: CollectionHandle<T>) {1797        <CollectionById<T>>::insert(collection.id, collection.into_inner());1798    }17991800    pub fn submit_logs(collection: CollectionHandle<T>) -> DispatchResult {1801        if collection.logs.is_empty() {1802            return Ok(())1803        }1804        T::EthereumTransactionSender::submit_logs_transaction(1805            eth::generate_transaction(collection.id, T::EthereumChainId::get()),1806            collection.logs.retrieve_logs(),1807        )1808    }18091810    fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: &T::AccountId) -> DispatchResult {1811        ensure!(1812            *subject == target_collection.owner,1813            Error::<T>::NoPermission1814        );18151816        Ok(())1817    }18181819    fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> bool {1820        *subject.as_sub() == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)1821    }18221823    fn check_owner_or_admin_permissions(1824        collection: &CollectionHandle<T>,1825        subject: &T::CrossAccountId,1826    ) -> DispatchResult {1827        ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);18281829        Ok(())1830    }18311832    fn owned_amount(1833        subject: &T::CrossAccountId,1834        target_collection: &CollectionHandle<T>,1835        item_id: TokenId,1836    ) -> Option<u128> {1837        let collection_id = target_collection.id;18381839        match target_collection.mode {1840            CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject)1841                .then(|| 1),1842            CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub())1843                .value),1844            CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1845                .owner1846                .iter()1847                .find(|i| i.owner == *subject)1848                .map(|i| i.fraction),1849            CollectionMode::Invalid => None,1850        }1851    }18521853    fn is_item_owner(subject: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {1854        match target_collection.mode {1855            CollectionMode::Fungible(_) => true,1856            _ => Self::owned_amount(&subject, target_collection, item_id).is_some(),1857        }1858    }18591860    fn check_white_list(collection: &CollectionHandle<T>, address: &T::CrossAccountId) -> DispatchResult {1861        let collection_id = collection.id;18621863        let mes = Error::<T>::AddresNotInWhiteList;1864        ensure!(<WhiteList<T>>::contains_key(collection_id, address.as_sub()), mes);18651866        Ok(())1867    }18681869    /// Check if token exists. In case of Fungible, check if there is an entry for 1870    /// the owner in fungible balances double map1871    fn token_exists(1872        target_collection: &CollectionHandle<T>,1873        item_id: TokenId,1874    ) -> DispatchResult {1875        let collection_id = target_collection.id;1876        let exists = match target_collection.mode1877        {1878            CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1879            CollectionMode::Fungible(_)  => true,1880            CollectionMode::ReFungible  => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1881            _ => false1882        };18831884        ensure!(exists == true, Error::<T>::TokenNotFound);1885        Ok(())1886    }18871888    fn transfer_fungible(1889        collection: &CollectionHandle<T>,1890        value: u128,1891        owner: &T::CrossAccountId,1892        recipient: &T::CrossAccountId,1893    ) -> DispatchResult {1894        let collection_id = collection.id;18951896        let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1897        ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18981899        // Send balance to recipient (updates balanceOf of recipient)1900        Self::add_fungible_item(collection, recipient, value)?;19011902        // update balanceOf of sender1903        <Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);19041905        // Reduce or remove sender1906        if balance.value == value {1907            <FungibleItemList<T>>::remove(collection_id, owner.as_sub());1908        }1909        else {1910            balance.value -= value;1911            <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1912        }19131914        collection.log(ERC20Events::Transfer {1915            from: *owner.as_eth(),1916            to: *recipient.as_eth(),1917            value: value.into(),1918        });1919        Self::deposit_event(RawEvent::Transfer(collection.id, 1, owner.clone(), recipient.clone(), value));19201921        Ok(())1922    }19231924    fn transfer_refungible(1925        collection: &CollectionHandle<T>,1926        item_id: TokenId,1927        value: u128,1928        owner: T::CrossAccountId,1929        new_owner: T::CrossAccountId,1930    ) -> DispatchResult {1931        let collection_id = collection.id;1932        let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)1933            .ok_or(Error::<T>::TokenNotFound)?;19341935        let item = full_item1936            .owner1937            .iter()1938            .filter(|i| i.owner == owner)1939            .next()1940            .ok_or(Error::<T>::TokenNotFound)?;1941        let amount = item.fraction;19421943        ensure!(amount >= value, Error::<T>::TokenValueTooLow);19441945        // update balance1946        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())1947            .checked_sub(value)1948            .ok_or(Error::<T>::NumOverflow)?;1949        <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);19501951        let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())1952            .checked_add(value)1953            .ok_or(Error::<T>::NumOverflow)?;1954        <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);19551956        let old_owner = item.owner.clone();1957        let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19581959        // transfer1960        if amount == value && !new_owner_has_account {1961            // change owner1962            // new owner do not have account1963            let mut new_full_item = full_item.clone();1964            new_full_item1965                .owner1966                .iter_mut()1967                .find(|i| i.owner == owner)1968                .expect("old owner does present in refungible")1969                .owner = new_owner.clone();1970            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19711972            // update index collection1973            Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;1974        } else {1975            let mut new_full_item = full_item.clone();1976            new_full_item1977                .owner1978                .iter_mut()1979                .find(|i| i.owner == owner)1980                .expect("old owner does present in refungible")1981                .fraction -= value;19821983            // separate amount1984            if new_owner_has_account {1985                // new owner has account1986                new_full_item1987                    .owner1988                    .iter_mut()1989                    .find(|i| i.owner == new_owner)1990                    .expect("new owner has account")1991                    .fraction += value;1992            } else {1993                // new owner do not have account1994                new_full_item.owner.push(Ownership {1995                    owner: new_owner.clone(),1996                    fraction: value,1997                });1998                Self::add_token_index(collection_id, item_id, &new_owner)?;1999            }20002001            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2002        }20032004        Self::deposit_event(RawEvent::Transfer(collection.id, item_id, owner, new_owner, amount));20052006        Ok(())2007    }20082009    fn transfer_nft(2010        collection: &CollectionHandle<T>,2011        item_id: TokenId,2012        sender: T::CrossAccountId,2013        new_owner: T::CrossAccountId,2014    ) -> DispatchResult {2015        let collection_id = collection.id;2016        let mut item = <NftItemList<T>>::get(collection_id, item_id)2017            .ok_or(Error::<T>::TokenNotFound)?;20182019        ensure!(2020            sender == item.owner,2021            Error::<T>::MustBeTokenOwner2022        );20232024        // update balance2025        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2026            .checked_sub(1)2027            .ok_or(Error::<T>::NumOverflow)?;2028        <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);20292030        let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2031            .checked_add(1)2032            .ok_or(Error::<T>::NumOverflow)?;2033        <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);20342035        // change owner2036        let old_owner = item.owner.clone();2037        item.owner = new_owner.clone();2038        <NftItemList<T>>::insert(collection_id, item_id, item);20392040        // update index collection2041        Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;20422043        collection.log(ERC721Events::Transfer {2044            from: *sender.as_eth(),2045            to: *new_owner.as_eth(),2046            token_id: item_id.into(),2047        });2048        Self::deposit_event(RawEvent::Transfer(collection.id, item_id, sender, new_owner, 1));20492050        Ok(())2051    }2052    2053    fn set_re_fungible_variable_data(2054        collection: &CollectionHandle<T>,2055        item_id: TokenId,2056        data: Vec<u8>2057    ) -> DispatchResult {2058        let collection_id = collection.id;2059        let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2060            .ok_or(Error::<T>::TokenNotFound)?;20612062        item.variable_data = data;20632064        <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20652066        Ok(())2067    }20682069    fn set_nft_variable_data(2070        collection: &CollectionHandle<T>,2071        item_id: TokenId,2072        data: Vec<u8>2073    ) -> DispatchResult {2074        let collection_id = collection.id;2075        let mut item = <NftItemList<T>>::get(collection_id, item_id)2076            .ok_or(Error::<T>::TokenNotFound)?;2077        2078        item.variable_data = data;20792080        <NftItemList<T>>::insert(collection_id, item_id, item);2081        2082        Ok(())2083    }20842085    #[allow(dead_code)]2086    fn init_collection(item: &Collection<T>) {2087        // check params2088        assert!(2089            item.decimal_points <= MAX_DECIMAL_POINTS,2090            "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2091        );2092        assert!(2093            item.name.len() <= 64,2094            "Collection name can not be longer than 63 char"2095        );2096        assert!(2097            item.name.len() <= 256,2098            "Collection description can not be longer than 255 char"2099        );2100        assert!(2101            item.token_prefix.len() <= 16,2102            "Token prefix can not be longer than 15 char"2103        );21042105        // Generate next collection ID2106        let next_id = CreatedCollectionCount::get()2107            .checked_add(1)2108            .unwrap();21092110        CreatedCollectionCount::put(next_id);2111    }21122113    #[allow(dead_code)]2114    fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2115        let current_index = <ItemListIndex>::get(collection_id)2116            .checked_add(1)2117            .unwrap();21182119        Self::add_token_index(collection_id, current_index, &item.owner).unwrap();21202121        <ItemListIndex>::insert(collection_id, current_index);21222123        // Update balance2124        let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2125            .checked_add(1)2126            .unwrap();2127        <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2128    }21292130    #[allow(dead_code)]2131    fn init_fungible_token(collection_id: CollectionId, owner: &T::CrossAccountId, item: &FungibleItemType) {2132        let current_index = <ItemListIndex>::get(collection_id)2133            .checked_add(1)2134            .unwrap();21352136        Self::add_token_index(collection_id, current_index, owner).unwrap();21372138        <ItemListIndex>::insert(collection_id, current_index);21392140        // Update balance2141        let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2142            .checked_add(item.value)2143            .unwrap();2144        <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2145    }21462147    #[allow(dead_code)]2148    fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::CrossAccountId>) {2149        let current_index = <ItemListIndex>::get(collection_id)2150            .checked_add(1)2151            .unwrap();21522153        let value = item.owner.first().unwrap().fraction;2154        let owner = item.owner.first().unwrap().owner.clone();21552156        Self::add_token_index(collection_id, current_index, &owner).unwrap();21572158        <ItemListIndex>::insert(collection_id, current_index);21592160        // Update balance2161        let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2162            .checked_add(value)2163            .unwrap();2164        <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2165    }21662167    fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::CrossAccountId) -> DispatchResult {2168        // add to account limit2169        if <AccountItemCount<T>>::contains_key(owner.as_sub()) {21702171            // bound Owned tokens by a single address2172            let count = <AccountItemCount<T>>::get(owner.as_sub());2173            ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21742175            <AccountItemCount<T>>::insert(owner.as_sub(), count2176                .checked_add(1)2177                .ok_or(Error::<T>::NumOverflow)?);2178        }2179        else {2180            <AccountItemCount<T>>::insert(owner.as_sub(), 1);2181        }21822183        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2184        if list_exists {2185            let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2186            let item_contains = list.contains(&item_index.clone());21872188            if !item_contains {2189                list.push(item_index.clone());2190            }21912192            <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2193        } else {2194            let mut itm = Vec::new();2195            itm.push(item_index.clone());2196            <AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2197        }21982199        Ok(())2200    }22012202    fn remove_token_index(2203        collection_id: CollectionId,2204        item_index: TokenId,2205        owner: &T::CrossAccountId,2206    ) -> DispatchResult {22072208        // update counter2209        <AccountItemCount<T>>::insert(owner.as_sub(), 2210            <AccountItemCount<T>>::get(owner.as_sub())2211            .checked_sub(1)2212            .ok_or(Error::<T>::NumOverflow)?);221322142215        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2216        if list_exists {2217            let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2218            let item_contains = list.contains(&item_index.clone());22192220            if item_contains {2221                list.retain(|&item| item != item_index);2222                <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2223            }2224        }22252226        Ok(())2227    }22282229    fn move_token_index(2230        collection_id: CollectionId,2231        item_index: TokenId,2232        old_owner: &T::CrossAccountId,2233        new_owner: &T::CrossAccountId,2234    ) -> DispatchResult {2235        Self::remove_token_index(collection_id, item_index, old_owner)?;2236        Self::add_token_index(collection_id, item_index, new_owner)?;22372238        Ok(())2239    }2240}22412242sp_api::decl_runtime_apis! {2243    pub trait NftApi {2244        /// Used for ethereum integration2245        fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2246    }2247}
addedpallets/nft/src/sponsorship.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/nft/src/sponsorship.rs
@@ -0,0 +1,203 @@
+use crate::{Config, Call, CollectionById, CreateItemBasket, VariableMetaDataBasket, ReFungibleTransferBasket, FungibleTransferBasket, NftTransferBasket, ChainLimit, CreateItemData, CollectionMode};
+use core::marker::PhantomData;
+use up_sponsorship::SponsorshipHandler;
+use frame_support::{
+	traits::IsSubType,
+	storage::{StorageMap, StorageDoubleMap, StorageValue},
+};
+use nft_data_structs::{TokenId, CollectionId};
+use alloc::vec::Vec;
+
+pub struct NftSponsorshipHandler<T>(PhantomData<T>);
+impl<T: Config> NftSponsorshipHandler<T> {
+	pub fn withdraw_create_item(
+		who: &T::AccountId,
+		collection_id: &CollectionId,
+		_properties: &CreateItemData,
+	) -> Option<T::AccountId> {
+	
+		let collection = CollectionById::<T>::get(collection_id)?;
+
+		// sponsor timeout
+		let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+
+		let limit = collection.limits.sponsor_transfer_timeout;
+		if CreateItemBasket::<T>::contains_key((collection_id, &who)) {
+			let last_tx_block = CreateItemBasket::<T>::get((collection_id, &who));
+			let limit_time = last_tx_block + limit.into();
+			if block_number <= limit_time {
+				return None;
+			}
+		}
+		CreateItemBasket::<T>::insert((collection_id, who.clone()), block_number);
+
+		// check free create limit
+		if collection.limits.sponsored_data_size >= (_properties.len() as u32) {
+			collection.sponsorship.sponsor()
+				.cloned()
+		} else {
+			None
+		}
+	}
+
+	pub fn withdraw_transfer(
+		who: &T::AccountId,
+		collection_id: &CollectionId,
+		item_id: &TokenId,
+	) -> Option<T::AccountId> {
+
+		let collection = CollectionById::<T>::get(collection_id)?;
+		let limits = ChainLimit::get();
+
+		let mut sponsor_transfer = false;
+		if collection.sponsorship.confirmed() {
+
+			let collection_limits = collection.limits.clone();
+			let collection_mode = collection.mode.clone();
+
+			// sponsor timeout
+			let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+			sponsor_transfer = match collection_mode {
+				CollectionMode::NFT => {
+
+					// get correct limit
+					let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
+						collection_limits.sponsor_transfer_timeout
+					} else {
+						limits.nft_sponsor_transfer_timeout
+					};
+
+					let mut sponsored = true;
+					if NftTransferBasket::<T>::contains_key(collection_id, item_id) {
+						let last_tx_block = NftTransferBasket::<T>::get(collection_id, item_id);
+						let limit_time = last_tx_block + limit.into();
+						if block_number <= limit_time {
+							sponsored = false;
+						}
+					}
+					if sponsored {
+						NftTransferBasket::<T>::insert(collection_id, item_id, block_number);
+					}
+
+					sponsored
+				}
+				CollectionMode::Fungible(_) => {
+
+					// get correct limit
+					let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
+						collection_limits.sponsor_transfer_timeout
+					} else {
+						limits.fungible_sponsor_transfer_timeout
+					};
+
+					let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+					let mut sponsored = true;
+					if FungibleTransferBasket::<T>::contains_key(collection_id, who) {
+						let last_tx_block = FungibleTransferBasket::<T>::get(collection_id, who);
+						let limit_time = last_tx_block + limit.into();
+						if block_number <= limit_time {
+							sponsored = false;
+						}
+					}
+					if sponsored {
+						FungibleTransferBasket::<T>::insert(collection_id, who, block_number);
+					}
+
+					sponsored
+				}
+				CollectionMode::ReFungible => {
+
+					// get correct limit
+					let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
+						collection_limits.sponsor_transfer_timeout
+					} else {
+						limits.refungible_sponsor_transfer_timeout
+					};
+
+					let mut sponsored = true;
+					if ReFungibleTransferBasket::<T>::contains_key(collection_id, item_id) {
+						let last_tx_block = ReFungibleTransferBasket::<T>::get(collection_id, item_id);
+						let limit_time = last_tx_block + limit.into();
+						if block_number <= limit_time {
+							sponsored = false;
+						}
+					}
+					if sponsored {
+						ReFungibleTransferBasket::<T>::insert(collection_id, item_id, block_number);
+					}
+
+					sponsored
+				}
+				_ => {
+					false
+				},
+			};
+		}
+
+		if !sponsor_transfer {
+			None
+		} else {
+			collection.sponsorship.sponsor()
+				.cloned()
+		}
+	}
+	
+	pub fn withdraw_set_variable_meta_data(
+		collection_id: &CollectionId,
+		item_id: &TokenId,
+		data: &Vec<u8>,
+	) -> Option<T::AccountId> {
+
+		let mut sponsor_metadata_changes = false;
+
+		let collection = CollectionById::<T>::get(collection_id)?;
+
+		if
+			collection.sponsorship.confirmed() &&
+			// Can't sponsor fungible collection, this tx will be rejected
+			// as invalid
+			!matches!(collection.mode, CollectionMode::Fungible(_)) &&
+			data.len() <= collection.limits.sponsored_data_size as usize
+		{
+			if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {
+				let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+
+				if VariableMetaDataBasket::<T>::get(collection_id, item_id)
+					.map(|last_block| block_number - last_block > rate_limit)
+					.unwrap_or(true) 
+				{
+					sponsor_metadata_changes = true;
+					VariableMetaDataBasket::<T>::insert(collection_id, item_id, block_number);
+				}
+			}
+		}
+
+		if !sponsor_metadata_changes {
+			None
+		} else {
+			collection.sponsorship.sponsor().cloned()
+		}
+
+	}
+}
+
+impl<T, C> SponsorshipHandler<T::AccountId, C> for NftSponsorshipHandler<T>
+where 
+    T: Config,
+    C: IsSubType<Call<T>>
+{
+    fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {
+        match IsSubType::<Call<T>>::is_sub_type(call)? {
+            Call::create_item(collection_id, _owner, _properties) => {
+                Self::withdraw_create_item(who, collection_id, &_properties)
+            },
+            Call::transfer(_new_owner, collection_id, item_id, _value) => {
+                Self::withdraw_transfer(who, collection_id, item_id)
+            },
+            Call::set_variable_meta_data(collection_id, item_id, data) => {
+                Self::withdraw_set_variable_meta_data(collection_id, item_id, &data)
+			},
+			_ => None,
+        }
+    }
+}
\ No newline at end of file
modifiedpallets/scheduler/Cargo.tomldiffbeforeafterboth
--- a/pallets/scheduler/Cargo.toml
+++ b/pallets/scheduler/Cargo.toml
@@ -14,15 +14,12 @@
 codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false }
 frame-support = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 frame-system = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
-pallet-contracts = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 sp-runtime = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 sp-std = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 sp-io = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 frame-benchmarking = { default-features = false, version = '3.0.0', optional = true, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
 
-pallet-nft-transaction-payment = { default-features = false, path = "../nft-transaction-payment" }
-pallet-nft = { default-features = false, path = "../nft" }
-nft-data-structs = { path = '../../primitives', default-features = false }
+up-sponsorship = { default-features = false, path = "../../primitives/sponsorship", version = "0.1.0" }
 log = { version = "0.4.14", default-features = false }
 
 [dev-dependencies]
@@ -37,10 +34,7 @@
 	"frame-benchmarking/std",
 	"frame-support/std",
 	"frame-system/std",
-	"pallet-nft-transaction-payment/std",
-	"pallet-nft/std",
-	"pallet-contracts/std",
-	"nft-data-structs/std",
+	"up-sponsorship/std",
 	"sp-io/std",
 	"sp-std/std",
 	"log/std",
modifiedpallets/scheduler/src/lib.rsdiffbeforeafterboth
--- a/pallets/scheduler/src/lib.rs
+++ b/pallets/scheduler/src/lib.rs
@@ -64,10 +64,8 @@
 	weights::{GetDispatchInfo, Weight},
 };
 use frame_system::{self as system, ensure_signed};
-use pallet_nft::*;
-// use pallet_nft_transaction_payment::{self as nft_transaction_payment};
-use nft_data_structs::*;
 pub use weights::WeightInfo;
+use up_sponsorship::SponsorshipHandler;
 
 /// Our pallet's configuration trait. All our types and constants go in here. If the
 /// pallet is dependent on specific other pallets, then their configuration traits
@@ -103,7 +101,7 @@
 	type MaxScheduledPerBlock: Get<u32>;
 
 	/// Sponsoring function
-	type Sponsoring: SponsoringResolve<Self::AccountId, <Self as Config>::Call>;
+	type SponsorshipHandler: SponsorshipHandler<Self::AccountId, <Self as Config>::Call>;
 
 	/// Weight information for extrinsics in this pallet.
 	type WeightInfo: WeightInfo;
@@ -405,8 +403,7 @@
 							s.origin.clone()
 						).into();
 						let sender = ensure_signed(origin).unwrap_or(T::AccountId::default());
-						let who_will_pay = T::Sponsoring::resolve(&sender, &s.call.clone()).unwrap_or(
-							sender);
+						let who_will_pay = T::SponsorshipHandler::get_sponsor(&sender, &s.call).unwrap_or(sender);
 						let sponsor = T::PalletsOrigin::from(system::RawOrigin::Signed(who_will_pay));
 						let r = s.call.clone().dispatch(sponsor.into());
 						let maybe_id = s.maybe_id.clone();
deletedprimitives/Cargo.tomldiffbeforeafterboth
--- a/primitives/Cargo.toml
+++ /dev/null
@@ -1,30 +0,0 @@
-[package]
-name = "nft-data-structs"
-authors = ['Substrate DevHub <https://github.com/substrate-developer-hub>']
-description = "Nft data structs definitions"
-edition = "2018"
-license = 'GPL-3.0'
-homepage = "https://substrate.dev"
-repository = 'https://github.com/clover-network/clover'
-version = '0.9.0'
-
-[dependencies]
-codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ['derive'] }
-serde = { version = "1.0.119", features = ['derive'], default-features = false }
-frame-support = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
-frame-system = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
-pallet-contracts = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
-sp-core = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
-sp-runtime = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
-
-[features]
-default = ["std"]
-std = [
-  "serde/std",
-  "codec/std",
-  "frame-system/std",
-  "frame-support/std",
-  "sp-runtime/std",
-  "sp-core/std",
-  "pallet-contracts/std",
-]
\ No newline at end of file
addedprimitives/nft/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/primitives/nft/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "nft-data-structs"
+authors = ['Substrate DevHub <https://github.com/substrate-developer-hub>']
+description = "Nft data structs definitions"
+edition = "2018"
+license = 'GPL-3.0'
+homepage = "https://substrate.dev"
+repository = 'https://github.com/clover-network/clover'
+version = '0.9.0'
+
+[dependencies]
+codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ['derive'] }
+serde = { version = "1.0.119", features = ['derive'], default-features = false }
+frame-support = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+frame-system = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+pallet-contracts = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-core = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-runtime = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+
+[features]
+default = ["std"]
+std = [
+  "serde/std",
+  "codec/std",
+  "frame-system/std",
+  "frame-support/std",
+  "sp-runtime/std",
+  "sp-core/std",
+  "pallet-contracts/std",
+]
\ No newline at end of file
addedprimitives/nft/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/primitives/nft/src/lib.rs
@@ -0,0 +1,285 @@
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+pub use serde::{Serialize, Deserialize};
+
+use frame_system;
+use sp_runtime::sp_std::prelude::Vec;
+use codec::{Decode, Encode};
+pub use frame_support::{
+    construct_runtime, decl_event, decl_module, decl_storage, decl_error,
+    dispatch::DispatchResult,
+    ensure, fail, parameter_types,
+    traits::{
+        Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,
+        Randomness, IsSubType, WithdrawReasons,
+    },
+    weights::{
+        constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},
+        DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,
+        WeightToFeePolynomial, DispatchClass,
+    },
+    StorageValue,
+    transactional,
+};
+
+pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;
+pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;
+pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;
+pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;
+
+pub type CollectionId = u32;
+pub type TokenId = u32;
+pub type DecimalPoints = u8;
+
+#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub enum CollectionMode {
+    Invalid,
+    NFT,
+    // decimal points
+    Fungible(DecimalPoints),
+    ReFungible,
+}
+
+impl Default for CollectionMode {
+    fn default() -> Self {
+        Self::Invalid
+    }
+}
+
+impl Into<u8> for CollectionMode {
+    fn into(self) -> u8 {
+        match self {
+            CollectionMode::Invalid => 0,
+            CollectionMode::NFT => 1,
+            CollectionMode::Fungible(_) => 2,
+            CollectionMode::ReFungible => 3,
+        }
+    }
+}
+
+pub trait SponsoringResolve<AccountId, Call> {
+    fn resolve(
+        who: &AccountId,
+		call: &Call) -> Option<AccountId>;
+}
+
+#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub enum AccessMode {
+    Normal,
+    WhiteList,
+}
+impl Default for AccessMode {
+    fn default() -> Self {
+        Self::Normal
+    }
+}
+
+#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub enum SchemaVersion {
+    ImageURL,
+    Unique,
+}
+impl Default for SchemaVersion {
+    fn default() -> Self {
+        Self::ImageURL
+    }
+}
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct Ownership<AccountId> {
+    pub owner: AccountId,
+    pub fraction: u128,
+}
+
+#[derive(Encode, Decode, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub enum SponsorshipState<AccountId> {
+    /// The fees are applied to the transaction sender
+    Disabled,
+    Unconfirmed(AccountId),
+    /// Transactions are sponsored by specified account
+    Confirmed(AccountId),
+}
+
+impl<AccountId> SponsorshipState<AccountId> {
+    pub fn sponsor(&self) -> Option<&AccountId> {
+        match self {
+            Self::Confirmed(sponsor) => Some(sponsor),
+            _ => None,
+        }
+    }
+
+    pub fn pending_sponsor(&self) -> Option<&AccountId> {
+        match self {
+            Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),
+            _ => None,
+        }
+    }
+
+    pub fn confirmed(&self) -> bool {
+        matches!(self, Self::Confirmed(_))
+    }
+}
+
+impl<T> Default for SponsorshipState<T> {
+    fn default() -> Self {
+        Self::Disabled
+    }
+}
+
+#[derive(Encode, Decode, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct Collection<T: frame_system::Config> {
+    pub owner: T::AccountId,
+    pub mode: CollectionMode,
+    pub access: AccessMode,
+    pub decimal_points: DecimalPoints,
+    pub name: Vec<u16>,        // 64 include null escape char
+    pub description: Vec<u16>, // 256 include null escape char
+    pub token_prefix: Vec<u8>, // 16 include null escape char
+    pub mint_mode: bool,
+    pub offchain_schema: Vec<u8>,
+    pub schema_version: SchemaVersion,
+    pub sponsorship: SponsorshipState<T::AccountId>,
+    pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions 
+    pub variable_on_chain_schema: Vec<u8>, //
+    pub const_on_chain_schema: Vec<u8>, //
+}
+
+#[derive(Encode, Decode, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct NftItemType<AccountId> {
+    pub owner: AccountId,
+    pub const_data: Vec<u8>,
+    pub variable_data: Vec<u8>,
+}
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct FungibleItemType {
+    pub value: u128,
+}
+
+#[derive(Encode, Decode, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct ReFungibleItemType<AccountId> {
+    pub owner: Vec<Ownership<AccountId>>,
+    pub const_data: Vec<u8>,
+    pub variable_data: Vec<u8>,
+}
+
+
+#[derive(Encode, Decode, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct CollectionLimits<BlockNumber: Encode + Decode> {
+    pub account_token_ownership_limit: u32,
+    pub sponsored_data_size: u32,
+    /// None - setVariableMetadata is not sponsored
+    /// Some(v) - setVariableMetadata is sponsored 
+    ///           if there is v block between txs
+    pub sponsored_data_rate_limit: Option<BlockNumber>,
+    pub token_limit: u32,
+
+    // Timeouts for item types in passed blocks
+    pub sponsor_transfer_timeout: u32,
+    pub owner_can_transfer: bool,
+    pub owner_can_destroy: bool,
+}
+
+impl<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {
+    fn default() -> Self {
+        Self { 
+            account_token_ownership_limit: 10_000_000, 
+            token_limit: u32::max_value(),
+            sponsored_data_size: u32::MAX, 
+            sponsored_data_rate_limit: None,
+            sponsor_transfer_timeout: 14400,
+            owner_can_transfer: true,
+            owner_can_destroy: true
+        }
+    }
+}
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct ChainLimits {
+    pub collection_numbers_limit: u32,
+    pub account_token_ownership_limit: u32,
+    pub collections_admins_limit: u64,
+    pub custom_data_limit: u32,
+
+    // Timeouts for item types in passed blocks
+    pub nft_sponsor_transfer_timeout: u32,
+    pub fungible_sponsor_transfer_timeout: u32,
+    pub refungible_sponsor_transfer_timeout: u32,
+
+    // Schema limits
+    pub offchain_schema_limit: u32,
+    pub variable_on_chain_schema_limit: u32,
+    pub const_on_chain_schema_limit: u32,
+}
+
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct CreateNftData {
+    pub const_data: Vec<u8>,
+    pub variable_data: Vec<u8>,
+}
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct CreateFungibleData {
+    pub value: u128,
+}
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct CreateReFungibleData {
+    pub const_data: Vec<u8>,
+    pub variable_data: Vec<u8>,
+    pub pieces: u128,
+}
+
+#[derive(Encode, Decode, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub enum CreateItemData {
+    NFT(CreateNftData),
+    Fungible(CreateFungibleData),
+    ReFungible(CreateReFungibleData),
+}
+
+impl CreateItemData {
+    pub fn len(&self) -> usize {
+        let len = match self {
+            CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),
+            CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),
+            _ => 0
+        };
+        
+        return len;
+    }
+}
+
+impl From<CreateNftData> for CreateItemData {
+    fn from(item: CreateNftData) -> Self {
+        CreateItemData::NFT(item)
+    }
+}
+
+impl From<CreateReFungibleData> for CreateItemData {
+    fn from(item: CreateReFungibleData) -> Self {
+        CreateItemData::ReFungible(item)
+    }
+}
+
+impl From<CreateFungibleData> for CreateItemData {
+    fn from(item: CreateFungibleData) -> Self {
+        CreateItemData::Fungible(item)
+    }
+}
addedprimitives/sponsorship/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/primitives/sponsorship/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "up-sponsorship"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+impl-trait-for-tuples = "0.2.1"
+
+[features]
+default = ["std"]
+std = []
\ No newline at end of file
addedprimitives/sponsorship/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/primitives/sponsorship/src/lib.rs
@@ -0,0 +1,42 @@
+#![no_std]
+
+pub trait SponsorshipHandler<AccountId, Call> {
+	fn get_sponsor(who: &AccountId, call: &Call) -> Option<AccountId>;
+}
+
+impl<A, C> SponsorshipHandler<A, C> for () {
+	fn get_sponsor(_who: &A, _call: &C) -> Option<A> {
+		None
+	}
+}
+
+macro_rules! impl_tuples {
+	($($ident:ident)+) => {
+		impl<AccountId, Call, $($ident),+> SponsorshipHandler<AccountId, Call> for ($($ident,)+)
+		where
+			$(
+				$ident: SponsorshipHandler<AccountId, Call>
+			),+
+		{
+			fn get_sponsor(who: &AccountId, call: &Call) -> Option<AccountId> {
+				$(
+					if let Some(account) = $ident::get_sponsor(who, call) {
+						return Some(account);
+					}
+				)+
+				None
+			}
+		}
+	}
+}
+
+impl_tuples! {A}
+impl_tuples! {A B}
+impl_tuples! {A B C}
+impl_tuples! {A B C D}
+impl_tuples! {A B C D E}
+impl_tuples! {A B C D E F}
+impl_tuples! {A B C D E F G}
+impl_tuples! {A B C D E F G H}
+impl_tuples! {A B C D E F G H I}
+impl_tuples! {A B C D E F G H I J}
deletedprimitives/src/lib.rsdiffbeforeafterboth
--- a/primitives/src/lib.rs
+++ /dev/null
@@ -1,285 +0,0 @@
-
-#![cfg_attr(not(feature = "std"), no_std)]
-
-pub use serde::{Serialize, Deserialize};
-
-use frame_system;
-use sp_runtime::sp_std::prelude::Vec;
-use codec::{Decode, Encode};
-pub use frame_support::{
-    construct_runtime, decl_event, decl_module, decl_storage, decl_error,
-    dispatch::DispatchResult,
-    ensure, fail, parameter_types,
-    traits::{
-        Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,
-        Randomness, IsSubType, WithdrawReasons,
-    },
-    weights::{
-        constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},
-        DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,
-        WeightToFeePolynomial, DispatchClass,
-    },
-    StorageValue,
-    transactional,
-};
-
-pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;
-pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;
-pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;
-pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;
-
-pub type CollectionId = u32;
-pub type TokenId = u32;
-pub type DecimalPoints = u8;
-
-#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub enum CollectionMode {
-    Invalid,
-    NFT,
-    // decimal points
-    Fungible(DecimalPoints),
-    ReFungible,
-}
-
-impl Default for CollectionMode {
-    fn default() -> Self {
-        Self::Invalid
-    }
-}
-
-impl Into<u8> for CollectionMode {
-    fn into(self) -> u8 {
-        match self {
-            CollectionMode::Invalid => 0,
-            CollectionMode::NFT => 1,
-            CollectionMode::Fungible(_) => 2,
-            CollectionMode::ReFungible => 3,
-        }
-    }
-}
-
-pub trait SponsoringResolve<AccountId, Call> {
-    fn resolve(
-        who: &AccountId,
-		call: &Call) -> Option<AccountId>;
-}
-
-#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub enum AccessMode {
-    Normal,
-    WhiteList,
-}
-impl Default for AccessMode {
-    fn default() -> Self {
-        Self::Normal
-    }
-}
-
-#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub enum SchemaVersion {
-    ImageURL,
-    Unique,
-}
-impl Default for SchemaVersion {
-    fn default() -> Self {
-        Self::ImageURL
-    }
-}
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct Ownership<AccountId> {
-    pub owner: AccountId,
-    pub fraction: u128,
-}
-
-#[derive(Encode, Decode, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub enum SponsorshipState<AccountId> {
-    /// The fees are applied to the transaction sender
-    Disabled,
-    Unconfirmed(AccountId),
-    /// Transactions are sponsored by specified account
-    Confirmed(AccountId),
-}
-
-impl<AccountId> SponsorshipState<AccountId> {
-    pub fn sponsor(&self) -> Option<&AccountId> {
-        match self {
-            Self::Confirmed(sponsor) => Some(sponsor),
-            _ => None,
-        }
-    }
-
-    pub fn pending_sponsor(&self) -> Option<&AccountId> {
-        match self {
-            Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),
-            _ => None,
-        }
-    }
-
-    pub fn confirmed(&self) -> bool {
-        matches!(self, Self::Confirmed(_))
-    }
-}
-
-impl<T> Default for SponsorshipState<T> {
-    fn default() -> Self {
-        Self::Disabled
-    }
-}
-
-#[derive(Encode, Decode, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct Collection<T: frame_system::Config> {
-    pub owner: T::AccountId,
-    pub mode: CollectionMode,
-    pub access: AccessMode,
-    pub decimal_points: DecimalPoints,
-    pub name: Vec<u16>,        // 64 include null escape char
-    pub description: Vec<u16>, // 256 include null escape char
-    pub token_prefix: Vec<u8>, // 16 include null escape char
-    pub mint_mode: bool,
-    pub offchain_schema: Vec<u8>,
-    pub schema_version: SchemaVersion,
-    pub sponsorship: SponsorshipState<T::AccountId>,
-    pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions 
-    pub variable_on_chain_schema: Vec<u8>, //
-    pub const_on_chain_schema: Vec<u8>, //
-}
-
-#[derive(Encode, Decode, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct NftItemType<AccountId> {
-    pub owner: AccountId,
-    pub const_data: Vec<u8>,
-    pub variable_data: Vec<u8>,
-}
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct FungibleItemType {
-    pub value: u128,
-}
-
-#[derive(Encode, Decode, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct ReFungibleItemType<AccountId> {
-    pub owner: Vec<Ownership<AccountId>>,
-    pub const_data: Vec<u8>,
-    pub variable_data: Vec<u8>,
-}
-
-
-#[derive(Encode, Decode, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct CollectionLimits<BlockNumber: Encode + Decode> {
-    pub account_token_ownership_limit: u32,
-    pub sponsored_data_size: u32,
-    /// None - setVariableMetadata is not sponsored
-    /// Some(v) - setVariableMetadata is sponsored 
-    ///           if there is v block between txs
-    pub sponsored_data_rate_limit: Option<BlockNumber>,
-    pub token_limit: u32,
-
-    // Timeouts for item types in passed blocks
-    pub sponsor_transfer_timeout: u32,
-    pub owner_can_transfer: bool,
-    pub owner_can_destroy: bool,
-}
-
-impl<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {
-    fn default() -> Self {
-        Self { 
-            account_token_ownership_limit: 10_000_000, 
-            token_limit: u32::max_value(),
-            sponsored_data_size: u32::MAX, 
-            sponsored_data_rate_limit: None,
-            sponsor_transfer_timeout: 14400,
-            owner_can_transfer: true,
-            owner_can_destroy: true
-        }
-    }
-}
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct ChainLimits {
-    pub collection_numbers_limit: u32,
-    pub account_token_ownership_limit: u32,
-    pub collections_admins_limit: u64,
-    pub custom_data_limit: u32,
-
-    // Timeouts for item types in passed blocks
-    pub nft_sponsor_transfer_timeout: u32,
-    pub fungible_sponsor_transfer_timeout: u32,
-    pub refungible_sponsor_transfer_timeout: u32,
-
-    // Schema limits
-    pub offchain_schema_limit: u32,
-    pub variable_on_chain_schema_limit: u32,
-    pub const_on_chain_schema_limit: u32,
-}
-
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct CreateNftData {
-    pub const_data: Vec<u8>,
-    pub variable_data: Vec<u8>,
-}
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct CreateFungibleData {
-    pub value: u128,
-}
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct CreateReFungibleData {
-    pub const_data: Vec<u8>,
-    pub variable_data: Vec<u8>,
-    pub pieces: u128,
-}
-
-#[derive(Encode, Decode, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub enum CreateItemData {
-    NFT(CreateNftData),
-    Fungible(CreateFungibleData),
-    ReFungible(CreateReFungibleData),
-}
-
-impl CreateItemData {
-    pub fn len(&self) -> usize {
-        let len = match self {
-            CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),
-            CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),
-            _ => 0
-        };
-        
-        return len;
-    }
-}
-
-impl From<CreateNftData> for CreateItemData {
-    fn from(item: CreateNftData) -> Self {
-        CreateItemData::NFT(item)
-    }
-}
-
-impl From<CreateReFungibleData> for CreateItemData {
-    fn from(item: CreateReFungibleData) -> Self {
-        CreateItemData::ReFungible(item)
-    }
-}
-
-impl From<CreateFungibleData> for CreateItemData {
-    fn from(item: CreateFungibleData) -> Self {
-        CreateItemData::Fungible(item)
-    }
-}
\ No newline at end of file
modifiedruntime/Cargo.tomldiffbeforeafterboth
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -46,6 +46,7 @@
     'pallet-contracts/std',
     'pallet-contracts-primitives/std',
     'pallet-contracts-rpc-runtime-api/std',
+    'pallet-contract-helpers/std',
     'pallet-randomness-collective-flip/std',
     'pallet-sudo/std',
     'pallet-timestamp/std',
@@ -372,8 +373,9 @@
 [dependencies]
 pallet-nft = { path = '../pallets/nft', default-features = false, version = '3.0.0' }
 pallet-inflation = { path = '../pallets/inflation', default-features = false, version = '3.0.0' }
-nft-data-structs = { path = '../primitives', default-features = false,  version = '0.9.0' }
+nft-data-structs = { path = '../primitives/nft', default-features = false,  version = '0.9.0' }
 pallet-scheduler = { path = '../pallets/scheduler', default-features = false, version = '3.0.0' }
+pallet-contract-helpers = { path = '../pallets/contract-helpers', default-features = false, version = '0.1.0' }
 pallet-nft-transaction-payment = { path = '../pallets/nft-transaction-payment', default-features = false, version = '3.0.0' }
 pallet-nft-charge-transaction = { path = '../pallets/nft-charge-transaction', default-features = false, version = '3.0.0' }
 
modifiedruntime/src/lib.rsdiffbeforeafterboth
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -734,6 +734,11 @@
 	}
 }
 
+type SponsorshipHandler = (
+	pallet_nft::NftSponsorshipHandler<Runtime>,
+    pallet_contract_helpers::ContractSponsorshipHandler<Runtime>,
+);
+
 impl pallet_scheduler::Config for Runtime {
 	type Event = Event;
 	type Origin = Origin;
@@ -742,16 +747,18 @@
 	type MaximumWeight = MaximumSchedulerWeight;
 	type ScheduleOrigin = EnsureSigned<AccountId>;
 	type MaxScheduledPerBlock = MaxScheduledPerBlock;
-	type Sponsoring = Sponsoring;
+	type SponsorshipHandler = SponsorshipHandler;
 	type WeightInfo = ();
 }
 
 impl pallet_nft_transaction_payment::Config for Runtime {
+	type SponsorshipHandler = SponsorshipHandler;
 }
 
-impl pallet_nft_charge_transaction::Config for Runtime {
-}
+impl pallet_nft_charge_transaction::Config for Runtime {}
 
+impl pallet_contract_helpers::Config for Runtime {}
+
 construct_runtime!(
     pub enum Runtime where
         Block = Block,
@@ -791,6 +798,7 @@
 		Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event<T>},
 		NftPayment: pallet_nft_transaction_payment::{Pallet, Call, Storage},
 		Charging: pallet_nft_charge_transaction::{Pallet, Call, Storage },
+		ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage},
     }
 );
 
@@ -829,6 +837,7 @@
     system::CheckNonce<Runtime>,
     system::CheckWeight<Runtime>,
     pallet_nft_charge_transaction::ChargeTransactionPayment<Runtime>,
+	pallet_contract_helpers::ContractHelpersExtension<Runtime>,
 );
 /// Unchecked extrinsic type as expected by this runtime.
 pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;