git.delta.rocks / unique-network / refs/commits / 999c24f384bf

difftreelog

Merge pull request #469 from UniqueNetwork/feature/refactor_contract_sponsoring

Yaroslav Bolyukin2022-08-25parents: #cdbbf09 #e80fd26.patch.diff
in: master
Feature/refactor contract sponsoring

19 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2345,7 +2345,7 @@
 [[package]]
 name = "fc-consensus"
 version = "2.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "async-trait",
  "fc-db",
@@ -2364,7 +2364,7 @@
 [[package]]
 name = "fc-db"
 version = "2.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "fp-storage",
  "kvdb-rocksdb",
@@ -2380,7 +2380,7 @@
 [[package]]
 name = "fc-mapping-sync"
 version = "2.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "fc-db",
  "fp-consensus",
@@ -2397,7 +2397,7 @@
 [[package]]
 name = "fc-rpc"
 version = "2.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "ethereum",
  "ethereum-types",
@@ -2437,7 +2437,7 @@
 [[package]]
 name = "fc-rpc-core"
 version = "1.1.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "ethereum",
  "ethereum-types",
@@ -2578,7 +2578,7 @@
 [[package]]
 name = "fp-consensus"
 version = "2.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "ethereum",
  "parity-scale-codec 3.1.5",
@@ -2590,7 +2590,7 @@
 [[package]]
 name = "fp-evm"
 version = "3.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "evm",
  "frame-support",
@@ -2604,7 +2604,7 @@
 [[package]]
 name = "fp-evm-mapping"
 version = "0.1.0"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "frame-support",
  "sp-core",
@@ -2613,7 +2613,7 @@
 [[package]]
 name = "fp-rpc"
 version = "3.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "ethereum",
  "ethereum-types",
@@ -2630,7 +2630,7 @@
 [[package]]
 name = "fp-self-contained"
 version = "1.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "ethereum",
  "frame-support",
@@ -2646,7 +2646,7 @@
 [[package]]
 name = "fp-storage"
 version = "2.0.0"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "parity-scale-codec 3.1.5",
 ]
@@ -5445,7 +5445,7 @@
 [[package]]
 name = "pallet-base-fee"
 version = "1.0.0"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "fp-evm",
  "frame-support",
@@ -5552,7 +5552,7 @@
 
 [[package]]
 name = "pallet-common"
-version = "0.1.6"
+version = "0.1.7"
 dependencies = [
  "ethereum",
  "evm-coder",
@@ -5660,7 +5660,7 @@
 [[package]]
 name = "pallet-ethereum"
 version = "4.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "ethereum",
  "ethereum-types",
@@ -5689,7 +5689,7 @@
 [[package]]
 name = "pallet-evm"
 version = "6.0.0-dev"
-source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#64915c0986fcf2bbe942794b838c0cf359c93c21"
+source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27#69945f20e8dc06ab9e0dd636848a8db6a408c71c"
 dependencies = [
  "evm",
  "fp-evm",
@@ -5733,7 +5733,7 @@
 
 [[package]]
 name = "pallet-evm-contract-helpers"
-version = "0.1.2"
+version = "0.2.0"
 dependencies = [
  "evm-coder",
  "fp-evm-mapping",
@@ -12130,7 +12130,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if 0.1.10",
  "digest 0.10.3",
  "rand 0.8.5",
  "static_assertions",
modifiedcrates/evm-coder/CHANGELOG.mddiffbeforeafterboth
--- a/crates/evm-coder/CHANGELOG.md
+++ b/crates/evm-coder/CHANGELOG.md
@@ -1,4 +1,16 @@
-<!-- bureaucrate goes here -->
+# Change Log
+
+All notable changes to this project will be documented in this file.
+## [v0.1.2] 2022-08-19
+
+### Added
+
+ - Implementation `AbiWrite` for tuples.
+
+ ### Fixes
+
+ - Tuple generation for solidity.
+
 ## [v0.1.1] 2022-08-16
 
 ### Other changes
modifiedcrates/evm-coder/src/abi.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/abi.rs
+++ b/crates/evm-coder/src/abi.rs
@@ -342,6 +342,16 @@
 				))
 			}
 		}
+		#[allow(non_snake_case)]
+		impl<$($ident),+> AbiWrite for &($($ident,)+)
+		where
+			$($ident: AbiWrite,)+
+		{
+			fn abi_write(&self, writer: &mut AbiWriter) {
+				let ($($ident,)+) = self;
+				$($ident.abi_write(writer);)+
+			}
+		}
 	};
 }
 
modifiedcrates/evm-coder/src/solidity.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -180,9 +180,16 @@
 			fn is_simple() -> bool {
 				false
 			}
+			#[allow(unused_assignments)]
 			fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
 				write!(writer, "{}(", tc.collect_tuple::<Self>())?;
+				let mut first = true;
 				$(
+					if !first {
+						write!(writer, ",")?;
+					} else {
+						first = false;
+					}
 					<$ident>::solidity_default(writer, tc)?;
 				)*
 				write!(writer, ")")
modifiedpallets/common/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/common/CHANGELOG.md
+++ b/pallets/common/CHANGELOG.md
@@ -2,6 +2,13 @@
 
 All notable changes to this project will be documented in this file.
 
+## [0.1.7] - 2022-08-19
+
+### Added
+
+ - Add convert funtion from `CrossAccountId` to eth `uint256`.
+
+ 
 ## [0.1.6] - 2022-08-16
 
 ### Added
modifiedpallets/common/Cargo.tomldiffbeforeafterboth
--- a/pallets/common/Cargo.toml
+++ b/pallets/common/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pallet-common"
-version = "0.1.6"
+version = "0.1.7"
 license = "GPLv3"
 edition = "2021"
 
modifiedpallets/common/src/eth.rsdiffbeforeafterboth
--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -16,13 +16,11 @@
 
 //! The module contains a number of functions for converting and checking ethereum identifiers.
 
-use evm_coder::{types::*};
-pub use pallet_evm::account::CrossAccountId;
+use evm_coder::types::uint256;
+pub use pallet_evm::account::{Config, CrossAccountId};
 use sp_core::H160;
 use up_data_structs::CollectionId;
 
-use crate::Config;
-
 // 0x17c4e6453Cc49AAAaEACA894e6D9683e00000001 - collection 1
 // TODO: Unhardcode prefix
 const ETH_COLLECTION_PREFIX: [u8; 16] = [
@@ -52,6 +50,15 @@
 	address[0..16] == ETH_COLLECTION_PREFIX
 }
 
+/// Convert `CrossAccountId` to `uint256`.
+pub fn convert_cross_account_to_uint256<T: Config>(from: &T::CrossAccountId) -> uint256
+where
+	T::AccountId: AsRef<[u8; 32]>,
+{
+	let slice = from.as_sub().as_ref();
+	uint256::from_big_endian(slice)
+}
+
 /// Converts Substrate address to CrossAccountId
 pub fn convert_substrate_address_to_cross_account_id<T: Config>(
 	address: uint256,
modifiedpallets/evm-contract-helpers/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/evm-contract-helpers/CHANGELOG.md
+++ b/pallets/evm-contract-helpers/CHANGELOG.md
@@ -1,4 +1,24 @@
-<!-- bureaucrate goes here -->
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [v0.2.0] - 2022-08-19
+
+### Added
+
+ - Set arbitrary evm address as contract sponsor.
+ - Ability to remove current sponsor.
+
+### Removed
+ - Remove methods
+   + sponsoring_enabled
+   + toggle_sponsoring
+
+ ### Changed
+
+ - Change `toggle_sponsoring` to `self_sponsored_enable`.
+
+
 ## [v0.1.2] 2022-08-16
 
 ### Other changes
modifiedpallets/evm-contract-helpers/Cargo.tomldiffbeforeafterboth
--- a/pallets/evm-contract-helpers/Cargo.toml
+++ b/pallets/evm-contract-helpers/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pallet-evm-contract-helpers"
-version = "0.1.2"
+version = "0.2.0"
 license = "GPLv3"
 edition = "2021"
 
modifiedpallets/evm-contract-helpers/src/eth.rsdiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/eth.rs
+++ b/pallets/evm-contract-helpers/src/eth.rs
@@ -16,14 +16,16 @@
 
 use core::marker::PhantomData;
 use evm_coder::{abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*};
-use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
+use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder, dispatch_to_evm};
 use pallet_evm::{
 	ExitRevert, OnCreate, OnMethodCall, PrecompileResult, PrecompileFailure, PrecompileHandle,
 	account::CrossAccountId,
 };
 use sp_core::H160;
+use up_data_structs::SponsorshipState;
 use crate::{
 	AllowlistEnabled, Config, Owner, Pallet, SponsorBasket, SponsoringRateLimit, SponsoringModeT,
+	Sponsoring,
 };
 use frame_support::traits::Get;
 use up_sponsorship::SponsorshipHandler;
@@ -41,36 +43,135 @@
 }
 
 #[solidity_interface(name = "ContractHelpers")]
-impl<T: Config> ContractHelpers<T> {
+impl<T: Config> ContractHelpers<T>
+where
+	T::AccountId: AsRef<[u8; 32]>,
+{
+	/// Get contract ovner
+	///
+	/// @param Contract_address contract for which the owner is being determined.
+	/// @return Contract owner.
 	fn contract_owner(&self, contract_address: address) -> Result<address> {
 		Ok(<Owner<T>>::get(contract_address))
 	}
 
-	fn sponsoring_enabled(&self, contract_address: address) -> Result<bool> {
-		Ok(<Pallet<T>>::sponsoring_mode(contract_address) != SponsoringModeT::Disabled)
-	}
-
-	/// Deprecated
-	fn toggle_sponsoring(
+	/// Set sponsor.
+	///
+	/// @param contract_address Contract for which a sponsor is being established.
+	/// @param sponsor User address who set as pending sponsor.
+	fn set_sponsor(
 		&mut self,
 		caller: caller,
 		contract_address: address,
-		enabled: bool,
+		sponsor: address,
 	) -> Result<void> {
-		<Pallet<T>>::ensure_owner(contract_address, caller)?;
-		<Pallet<T>>::toggle_sponsoring(contract_address, enabled);
+		self.recorder().consume_sload()?;
+		self.recorder().consume_sstore()?;
+
+		Pallet::<T>::set_sponsor(
+			&T::CrossAccountId::from_eth(caller),
+			contract_address,
+			&T::CrossAccountId::from_eth(sponsor),
+		)
+		.map_err(dispatch_to_evm::<T>)?;
+
+		Ok(())
+	}
+
+	/// Set contract as self sponsored.
+	///
+	/// @param contract_address Contract for which a self sponsoring is being enabled.
+	fn self_sponsored_enable(&mut self, caller: caller, contract_address: address) -> Result<void> {
+		self.recorder().consume_sload()?;
+		self.recorder().consume_sstore()?;
+
+		Pallet::<T>::self_sponsored_enable(&T::CrossAccountId::from_eth(caller), contract_address)
+			.map_err(dispatch_to_evm::<T>)?;
+
+		Ok(())
+	}
+
+	/// Remove sponsor.
+	///
+	/// @param contract_address Contract for which a sponsorship is being removed.
+	fn remove_sponsor(&mut self, caller: caller, contract_address: address) -> Result<void> {
+		self.recorder().consume_sload()?;
+		self.recorder().consume_sstore()?;
+
+		Pallet::<T>::remove_sponsor(&T::CrossAccountId::from_eth(caller), contract_address)
+			.map_err(dispatch_to_evm::<T>)?;
+
+		Ok(())
+	}
+
+	/// Confirm sponsorship.
+	///
+	/// @dev Caller must be same that set via [`set_sponsor`].
+	///
+	/// @param contract_address Сontract for which need to confirm sponsorship.
+	fn confirm_sponsorship(&mut self, caller: caller, contract_address: address) -> Result<void> {
+		self.recorder().consume_sload()?;
+		self.recorder().consume_sstore()?;
+
+		Pallet::<T>::confirm_sponsorship(&T::CrossAccountId::from_eth(caller), contract_address)
+			.map_err(dispatch_to_evm::<T>)?;
+
 		Ok(())
 	}
 
+	/// Get current sponsor.
+	///
+	/// @param contract_address The contract for which a sponsor is requested.
+	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
+	fn get_sponsor(&self, contract_address: address) -> Result<(address, uint256)> {
+		let sponsor =
+			Pallet::<T>::get_sponsor(contract_address).ok_or("Contract has no sponsor")?;
+		let result: (address, uint256) = if sponsor.is_canonical_substrate() {
+			let sponsor = pallet_common::eth::convert_cross_account_to_uint256::<T>(&sponsor);
+			(Default::default(), sponsor)
+		} else {
+			let sponsor = *sponsor.as_eth();
+			(sponsor, Default::default())
+		};
+		Ok(result)
+	}
+
+	/// Check tat contract has confirmed sponsor.
+	///
+	/// @param contract_address The contract for which the presence of a confirmed sponsor is checked.
+	/// @return **true** if contract has confirmed sponsor.
+	fn has_sponsor(&self, contract_address: address) -> Result<bool> {
+		Ok(Pallet::<T>::get_sponsor(contract_address).is_some())
+	}
+
+	/// Check tat contract has pending sponsor.
+	///
+	/// @param contract_address The contract for which the presence of a pending sponsor is checked.
+	/// @return **true** if contract has pending sponsor.
+	fn has_pending_sponsor(&self, contract_address: address) -> Result<bool> {
+		Ok(match Sponsoring::<T>::get(contract_address) {
+			SponsorshipState::Disabled | SponsorshipState::Confirmed(_) => false,
+			SponsorshipState::Unconfirmed(_) => true,
+		})
+	}
+
+	fn sponsoring_enabled(&self, contract_address: address) -> Result<bool> {
+		Ok(<Pallet<T>>::sponsoring_mode(contract_address) != SponsoringModeT::Disabled)
+	}
+
 	fn set_sponsoring_mode(
 		&mut self,
 		caller: caller,
 		contract_address: address,
 		mode: uint8,
 	) -> Result<void> {
-		<Pallet<T>>::ensure_owner(contract_address, caller)?;
+		self.recorder().consume_sload()?;
+		self.recorder().consume_sstore()?;
+
+		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;
 		let mode = SponsoringModeT::from_eth(mode).ok_or("unknown mode")?;
 		<Pallet<T>>::set_sponsoring_mode(contract_address, mode);
+
 		Ok(())
 	}
 
@@ -84,8 +185,12 @@
 		contract_address: address,
 		rate_limit: uint32,
 	) -> Result<void> {
-		<Pallet<T>>::ensure_owner(contract_address, caller)?;
+		self.recorder().consume_sload()?;
+		self.recorder().consume_sstore()?;
+
+		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;
 		<Pallet<T>>::set_sponsoring_rate_limit(contract_address, rate_limit.into());
+
 		Ok(())
 	}
 
@@ -110,8 +215,12 @@
 		contract_address: address,
 		enabled: bool,
 	) -> Result<void> {
-		<Pallet<T>>::ensure_owner(contract_address, caller)?;
+		self.recorder().consume_sload()?;
+		self.recorder().consume_sstore()?;
+
+		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;
 		<Pallet<T>>::toggle_allowlist(contract_address, enabled);
+
 		Ok(())
 	}
 
@@ -122,14 +231,21 @@
 		user: address,
 		allowed: bool,
 	) -> Result<void> {
-		<Pallet<T>>::ensure_owner(contract_address, caller)?;
+		self.recorder().consume_sload()?;
+		self.recorder().consume_sstore()?;
+
+		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;
 		<Pallet<T>>::toggle_allowed(contract_address, user, allowed);
+
 		Ok(())
 	}
 }
 
 pub struct HelpersOnMethodCall<T: Config>(PhantomData<*const T>);
-impl<T: Config> OnMethodCall<T> for HelpersOnMethodCall<T> {
+impl<T: Config> OnMethodCall<T> for HelpersOnMethodCall<T>
+where
+	T::AccountId: AsRef<[u8; 32]>,
+{
 	fn is_reserved(contract: &sp_core::H160) -> bool {
 		contract == &T::ContractAddress::get()
 	}
@@ -179,18 +295,26 @@
 	for HelpersContractSponsoring<T>
 {
 	fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec<u8>)) -> Option<T::CrossAccountId> {
-		let mode = <Pallet<T>>::sponsoring_mode(call.0);
+		let (contract_address, _) = call;
+		let mode = <Pallet<T>>::sponsoring_mode(*contract_address);
 		if mode == SponsoringModeT::Disabled {
 			return None;
 		}
 
-		if mode == SponsoringModeT::Allowlisted && !<Pallet<T>>::allowed(call.0, *who.as_eth()) {
+		let sponsor = match <Pallet<T>>::get_sponsor(*contract_address) {
+			Some(sponsor) => sponsor,
+			None => return None,
+		};
+
+		if mode == SponsoringModeT::Allowlisted
+			&& !<Pallet<T>>::allowed(*contract_address, *who.as_eth())
+		{
 			return None;
 		}
 		let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
 
-		if let Some(last_tx_block) = <SponsorBasket<T>>::get(&call.0, who.as_eth()) {
-			let limit = <SponsoringRateLimit<T>>::get(&call.0);
+		if let Some(last_tx_block) = <SponsorBasket<T>>::get(contract_address, who.as_eth()) {
+			let limit = <SponsoringRateLimit<T>>::get(contract_address);
 
 			let timeout = last_tx_block + limit;
 			if block_number < timeout {
@@ -198,9 +322,8 @@
 			}
 		}
 
-		<SponsorBasket<T>>::insert(&call.0, who.as_eth(), block_number);
+		<SponsorBasket<T>>::insert(contract_address, who.as_eth(), block_number);
 
-		let sponsor = T::CrossAccountId::from_eth(call.0);
 		Some(sponsor)
 	}
 }
modifiedpallets/evm-contract-helpers/src/lib.rsdiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/lib.rs
+++ b/pallets/evm-contract-helpers/src/lib.rs
@@ -25,9 +25,11 @@
 #[frame_support::pallet]
 pub mod pallet {
 	pub use super::*;
-	use evm_coder::execution::Result;
 	use frame_support::pallet_prelude::*;
+	use pallet_evm_coder_substrate::DispatchResult;
 	use sp_core::H160;
+	use pallet_evm::account::CrossAccountId;
+	use up_data_structs::SponsorshipState;
 
 	#[pallet::config]
 	pub trait Config:
@@ -39,14 +41,21 @@
 
 	#[pallet::error]
 	pub enum Error<T> {
-		/// This method is only executable by owner
+		/// This method is only executable by owner.
 		NoPermission,
+
+		/// No pending sponsor for contract.
+		NoPendingSponsor,
 	}
 
 	#[pallet::pallet]
 	#[pallet::generate_store(pub(super) trait Store)]
 	pub struct Pallet<T>(_);
 
+	/// Store owner for contract.
+	///
+	/// * **Key** - contract address.
+	/// * **Value** - owner for contract.
 	#[pallet::storage]
 	pub(super) type Owner<T: Config> =
 		StorageMap<Hasher = Twox128, Key = H160, Value = H160, QueryKind = ValueQuery>;
@@ -56,10 +65,33 @@
 	pub(super) type SelfSponsoring<T: Config> =
 		StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;
 
+	/// Store for contract sponsorship state.
+	///
+	/// * **Key** - contract address.
+	/// * **Value** - sponsorship state.
+	#[pallet::storage]
+	pub(super) type Sponsoring<T: Config> = StorageMap<
+		Hasher = Twox64Concat,
+		Key = H160,
+		Value = SponsorshipState<T::CrossAccountId>,
+		QueryKind = ValueQuery,
+	>;
+
+	/// Store for sponsoring mode.
+	///
+	/// ### Usage
+	/// Prefer to delete collection from storage if mode chaged to [`Disabled`](SponsoringModeT::Disabled).
+	///
+	/// * **Key** - contract address.
+	/// * **Value** - [`sponsoring mode`](SponsoringModeT).
 	#[pallet::storage]
 	pub(super) type SponsoringMode<T: Config> =
 		StorageMap<Hasher = Twox128, Key = H160, Value = SponsoringModeT, QueryKind = OptionQuery>;
 
+	/// Storage for sponsoring rate limit in blocks.
+	///
+	/// * **Key** - contract address.
+	/// * **Value** - amount of sponsored blocks.
 	#[pallet::storage]
 	pub(super) type SponsoringRateLimit<T: Config> = StorageMap<
 		Hasher = Twox128,
@@ -69,6 +101,11 @@
 		OnEmpty = T::DefaultSponsoringRateLimit,
 	>;
 
+	/// Storage for last sponsored block.
+	///
+	/// * **Key1** - contract address.
+	/// * **Key2** - sponsored user address.
+	/// * **Value** - last sponsored block number.
 	#[pallet::storage]
 	pub(super) type SponsorBasket<T: Config> = StorageDoubleMap<
 		Hasher1 = Twox128,
@@ -79,10 +116,25 @@
 		QueryKind = OptionQuery,
 	>;
 
+	/// Storege for contracts with [`Allowlisted`](SponsoringModeT::Allowlisted) sponsoring mode.
+	///
+	/// ### Usage
+	/// Prefer to delete collection from storage if mode chaged to non `Allowlisted`, than set **Value** to **false**.
+	///
+	/// * **Key** - contract address.
+	/// * **Value** - is contract in [`Allowlisted`](SponsoringModeT::Allowlisted) mode.
 	#[pallet::storage]
 	pub(super) type AllowlistEnabled<T: Config> =
 		StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;
 
+	/// Storage for users that allowed for sponsorship.
+	///
+	/// ### Usage
+	/// Prefer to delete record from storage if user no more allowed for sponsorship.
+	///
+	/// * **Key1** - contract address.
+	/// * **Key2** - user that allowed for sponsorship.
+	/// * **Value** - allowance for sponsorship.
 	#[pallet::storage]
 	pub(super) type Allowlist<T: Config> = StorageDoubleMap<
 		Hasher1 = Twox128,
@@ -94,6 +146,77 @@
 	>;
 
 	impl<T: Config> Pallet<T> {
+		/// Get contract owner.
+		pub fn contract_owner(contract: H160) -> H160 {
+			<Owner<T>>::get(contract)
+		}
+
+		/// Set `sponsor` for `contract`.
+		///
+		/// `sender` must be owner of contract.
+		pub fn set_sponsor(
+			sender: &T::CrossAccountId,
+			contract: H160,
+			sponsor: &T::CrossAccountId,
+		) -> DispatchResult {
+			Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;
+			Sponsoring::<T>::insert(
+				contract,
+				SponsorshipState::<T::CrossAccountId>::Unconfirmed(sponsor.clone()),
+			);
+			Ok(())
+		}
+
+		/// Set `contract` as self sponsored.
+		///
+		/// `sender` must be owner of contract.
+		pub fn self_sponsored_enable(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {
+			Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;
+			Sponsoring::<T>::insert(
+				contract,
+				SponsorshipState::<T::CrossAccountId>::Confirmed(T::CrossAccountId::from_eth(
+					contract,
+				)),
+			);
+			Ok(())
+		}
+
+		/// Remove sponsor for `contract`.
+		///
+		/// `sender` must be owner of contract.
+		pub fn remove_sponsor(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {
+			Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;
+			Sponsoring::<T>::remove(contract);
+			Ok(())
+		}
+
+		/// Confirm sponsorship.
+		///
+		/// `sender` must be same that set via [`set_sponsor`].
+		pub fn confirm_sponsorship(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {
+			match Sponsoring::<T>::get(contract) {
+				SponsorshipState::Unconfirmed(sponsor) => {
+					ensure!(sponsor == *sender, Error::<T>::NoPermission);
+					Sponsoring::<T>::insert(
+						contract,
+						SponsorshipState::<T::CrossAccountId>::Confirmed(sponsor),
+					);
+					Ok(())
+				}
+				SponsorshipState::Disabled | SponsorshipState::Confirmed(_) => {
+					Err(Error::<T>::NoPendingSponsor.into())
+				}
+			}
+		}
+
+		/// Get sponsor.
+		pub fn get_sponsor(contract: H160) -> Option<T::CrossAccountId> {
+			match Sponsoring::<T>::get(contract) {
+				SponsorshipState::Disabled | SponsorshipState::Unconfirmed(_) => None,
+				SponsorshipState::Confirmed(sponsor) => Some(sponsor),
+			}
+		}
+
 		pub fn sponsoring_mode(contract: H160) -> SponsoringModeT {
 			<SponsoringMode<T>>::get(contract)
 				.or_else(|| {
@@ -101,6 +224,7 @@
 				})
 				.unwrap_or_default()
 		}
+
 		pub fn set_sponsoring_mode(contract: H160, mode: SponsoringModeT) {
 			if mode == SponsoringModeT::Disabled {
 				<SponsoringMode<T>>::remove(contract);
@@ -110,17 +234,6 @@
 			<SelfSponsoring<T>>::remove(contract)
 		}
 
-		pub fn toggle_sponsoring(contract: H160, enabled: bool) {
-			Self::set_sponsoring_mode(
-				contract,
-				if enabled {
-					SponsoringModeT::Allowlisted
-				} else {
-					SponsoringModeT::Disabled
-				},
-			)
-		}
-
 		pub fn set_sponsoring_rate_limit(contract: H160, rate_limit: T::BlockNumber) {
 			<SponsoringRateLimit<T>>::insert(contract, rate_limit);
 		}
@@ -137,8 +250,8 @@
 			<Allowlist<T>>::insert(contract, user, allowed);
 		}
 
-		pub fn ensure_owner(contract: H160, user: H160) -> Result<()> {
-			ensure!(<Owner<T>>::get(&contract) == user, "no permission");
+		pub fn ensure_owner(contract: H160, user: H160) -> DispatchResult {
+			ensure!(<Owner<T>>::get(&contract) == user, Error::<T>::NoPermission);
 			Ok(())
 		}
 	}
modifiedpallets/evm-contract-helpers/src/stubs/ContractHelpers.soldiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol
+++ b/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol
@@ -3,6 +3,12 @@
 
 pragma solidity >=0.8.0 <0.9.0;
 
+// Anonymous struct
+struct Tuple0 {
+	address field_0;
+	uint256 field_1;
+}
+
 // Common stubs holder
 contract Dummy {
 	uint8 dummy;
@@ -21,8 +27,13 @@
 	}
 }
 
-// Selector: 7b4866f9
+// Selector: 6073d917
 contract ContractHelpers is Dummy, ERC165 {
+	// Get contract ovner
+	//
+	// @param Contract_address contract for which the owner is being determined.
+	// @return Contract owner.
+	//
 	// Selector: contractOwner(address) 5152b14c
 	function contractOwner(address contractAddress)
 		public
@@ -35,26 +46,111 @@
 		return 0x0000000000000000000000000000000000000000;
 	}
 
-	// Selector: sponsoringEnabled(address) 6027dc61
-	function sponsoringEnabled(address contractAddress)
+	// Set sponsor.
+	//
+	// @param contract_address Contract for which a sponsor is being established.
+	// @param sponsor User address who set as pending sponsor.
+	//
+	// Selector: setSponsor(address,address) f01fba93
+	function setSponsor(address contractAddress, address sponsor) public {
+		require(false, stub_error);
+		contractAddress;
+		sponsor;
+		dummy = 0;
+	}
+
+	// Set contract as self sponsored.
+	//
+	// @param contract_address Contract for which a self sponsoring is being enabled.
+	//
+	// Selector: selfSponsoredEnable(address) 89f7d9ae
+	function selfSponsoredEnable(address contractAddress) public {
+		require(false, stub_error);
+		contractAddress;
+		dummy = 0;
+	}
+
+	// Remove sponsor.
+	//
+	// @param contract_address Contract for which a sponsorship is being removed.
+	//
+	// Selector: removeSponsor(address) ef784250
+	function removeSponsor(address contractAddress) public {
+		require(false, stub_error);
+		contractAddress;
+		dummy = 0;
+	}
+
+	// Confirm sponsorship.
+	//
+	// @dev Caller must be same that set via [`set_sponsor`].
+	//
+	// @param contract_address Сontract for which need to confirm sponsorship.
+	//
+	// Selector: confirmSponsorship(address) abc00001
+	function confirmSponsorship(address contractAddress) public {
+		require(false, stub_error);
+		contractAddress;
+		dummy = 0;
+	}
+
+	// Get current sponsor.
+	//
+	// @param contract_address The contract for which a sponsor is requested.
+	// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
+	//
+	// Selector: getSponsor(address) 743fc745
+	function getSponsor(address contractAddress)
 		public
 		view
-		returns (bool)
+		returns (Tuple0 memory)
 	{
 		require(false, stub_error);
 		contractAddress;
 		dummy;
+		return Tuple0(0x0000000000000000000000000000000000000000, 0);
+	}
+
+	// Check tat contract has confirmed sponsor.
+	//
+	// @param contract_address The contract for which the presence of a confirmed sponsor is checked.
+	// @return **true** if contract has confirmed sponsor.
+	//
+	// Selector: hasSponsor(address) 97418603
+	function hasSponsor(address contractAddress) public view returns (bool) {
+		require(false, stub_error);
+		contractAddress;
+		dummy;
 		return false;
 	}
 
-	// Deprecated
+	// Check tat contract has pending sponsor.
+	//
+	// @param contract_address The contract for which the presence of a pending sponsor is checked.
+	// @return **true** if contract has pending sponsor.
 	//
-	// Selector: toggleSponsoring(address,bool) fcac6d86
-	function toggleSponsoring(address contractAddress, bool enabled) public {
+	// Selector: hasPendingSponsor(address) 39b9b242
+	function hasPendingSponsor(address contractAddress)
+		public
+		view
+		returns (bool)
+	{
+		require(false, stub_error);
+		contractAddress;
+		dummy;
+		return false;
+	}
+
+	// Selector: sponsoringEnabled(address) 6027dc61
+	function sponsoringEnabled(address contractAddress)
+		public
+		view
+		returns (bool)
+	{
 		require(false, stub_error);
 		contractAddress;
-		enabled;
-		dummy = 0;
+		dummy;
+		return false;
 	}
 
 	// Selector: setSponsoringMode(address,uint8) fde8a560
modifiedruntime/tests/src/lib.rsdiffbeforeafterboth
--- a/runtime/tests/src/lib.rs
+++ b/runtime/tests/src/lib.rs
@@ -167,7 +167,7 @@
 }
 
 #[derive(Encode, Decode, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, TypeInfo, MaxEncodedLen)]
-pub struct TestCrossAccountId(u64, sp_core::H160);
+pub struct TestCrossAccountId(u64, sp_core::H160, bool);
 impl CrossAccountId<u64> for TestCrossAccountId {
 	fn as_sub(&self) -> &u64 {
 		&self.0
@@ -178,17 +178,20 @@
 	fn from_sub(sub: u64) -> Self {
 		let mut eth = [0; 20];
 		eth[12..20].copy_from_slice(&sub.to_be_bytes());
-		Self(sub, sp_core::H160(eth))
+		Self(sub, sp_core::H160(eth), true)
 	}
 	fn from_eth(eth: sp_core::H160) -> Self {
 		let mut sub_raw = [0; 8];
 		sub_raw.copy_from_slice(&eth.0[0..8]);
 		let sub = u64::from_be_bytes(sub_raw);
-		Self(sub, eth)
+		Self(sub, eth, false)
 	}
 	fn conv_eq(&self, other: &Self) -> bool {
 		self.as_sub() == other.as_sub()
 	}
+	fn is_canonical_substrate(&self) -> bool {
+		self.2
+	}
 }
 
 impl Default for TestCrossAccountId {
modifiedtests/src/eth/api/ContractHelpers.soldiffbeforeafterboth
--- a/tests/src/eth/api/ContractHelpers.sol
+++ b/tests/src/eth/api/ContractHelpers.sol
@@ -3,6 +3,12 @@
 
 pragma solidity >=0.8.0 <0.9.0;
 
+// Anonymous struct
+struct Tuple0 {
+	address field_0;
+	uint256 field_1;
+}
+
 // Common stubs holder
 interface Dummy {
 
@@ -12,24 +18,85 @@
 	function supportsInterface(bytes4 interfaceID) external view returns (bool);
 }
 
-// Selector: 7b4866f9
+// Selector: 6073d917
 interface ContractHelpers is Dummy, ERC165 {
+	// Get contract ovner
+	//
+	// @param Contract_address contract for which the owner is being determined.
+	// @return Contract owner.
+	//
 	// Selector: contractOwner(address) 5152b14c
 	function contractOwner(address contractAddress)
 		external
 		view
 		returns (address);
 
+	// Set sponsor.
+	//
+	// @param contract_address Contract for which a sponsor is being established.
+	// @param sponsor User address who set as pending sponsor.
+	//
+	// Selector: setSponsor(address,address) f01fba93
+	function setSponsor(address contractAddress, address sponsor) external;
+
+	// Set contract as self sponsored.
+	//
+	// @param contract_address Contract for which a self sponsoring is being enabled.
+	//
+	// Selector: selfSponsoredEnable(address) 89f7d9ae
+	function selfSponsoredEnable(address contractAddress) external;
+
+	// Remove sponsor.
+	//
+	// @param contract_address Contract for which a sponsorship is being removed.
+	//
+	// Selector: removeSponsor(address) ef784250
+	function removeSponsor(address contractAddress) external;
+
+	// Confirm sponsorship.
+	//
+	// @dev Caller must be same that set via [`set_sponsor`].
+	//
+	// @param contract_address Сontract for which need to confirm sponsorship.
+	//
+	// Selector: confirmSponsorship(address) abc00001
+	function confirmSponsorship(address contractAddress) external;
+
+	// Get current sponsor.
+	//
+	// @param contract_address The contract for which a sponsor is requested.
+	// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
+	//
+	// Selector: getSponsor(address) 743fc745
+	function getSponsor(address contractAddress)
+		external
+		view
+		returns (Tuple0 memory);
+
+	// Check tat contract has confirmed sponsor.
+	//
+	// @param contract_address The contract for which the presence of a confirmed sponsor is checked.
+	// @return **true** if contract has confirmed sponsor.
+	//
+	// Selector: hasSponsor(address) 97418603
+	function hasSponsor(address contractAddress) external view returns (bool);
+
+	// Check tat contract has pending sponsor.
+	//
+	// @param contract_address The contract for which the presence of a pending sponsor is checked.
+	// @return **true** if contract has pending sponsor.
+	//
+	// Selector: hasPendingSponsor(address) 39b9b242
+	function hasPendingSponsor(address contractAddress)
+		external
+		view
+		returns (bool);
+
 	// Selector: sponsoringEnabled(address) 6027dc61
 	function sponsoringEnabled(address contractAddress)
 		external
 		view
 		returns (bool);
-
-	// Deprecated
-	//
-	// Selector: toggleSponsoring(address,bool) fcac6d86
-	function toggleSponsoring(address contractAddress, bool enabled) external;
 
 	// Selector: setSponsoringMode(address,uint8) fde8a560
 	function setSponsoringMode(address contractAddress, uint8 mode) external;
modifiedtests/src/eth/collectionSponsoring.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collectionSponsoring.test.ts
+++ b/tests/src/eth/collectionSponsoring.test.ts
@@ -1,7 +1,8 @@
-import {addToAllowListExpectSuccess, confirmSponsorshipExpectSuccess, createCollectionExpectSuccess, enablePublicMintingExpectSuccess, setCollectionSponsorExpectSuccess} from '../util/helpers';
-import {itWeb3, createEthAccount, collectionIdToAddress, GAS_ARGS, normalizeEvents} from './util/helpers';
+import {addToAllowListExpectSuccess, confirmSponsorshipExpectSuccess, createCollectionExpectSuccess, enablePublicMintingExpectSuccess, getDetailedCollectionInfo, setCollectionSponsorExpectSuccess} from '../util/helpers';
+import {itWeb3, createEthAccount, collectionIdToAddress, GAS_ARGS, normalizeEvents, createEthAccountWithBalance, evmCollectionHelpers, getCollectionAddressFromResult, evmCollection, ethBalanceViaSub} from './util/helpers';
 import nonFungibleAbi from './nonFungibleAbi.json';
 import {expect} from 'chai';
+import {evmToAddress} from '@polkadot/util-crypto';
 
 describe('evm collection sponsoring', () => {
   itWeb3('sponsors mint transactions', async ({web3, privateKeyWrapper}) => {
@@ -36,4 +37,130 @@
       },
     ]);
   });
+
+  itWeb3('Sponsoring collection from evm address via access list', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionHelpers = evmCollectionHelpers(web3, owner);
+    let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    result = await collectionEvm.methods.setCollectionSponsor(sponsor).send({from: owner});
+    let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+    const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;
+    expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
+    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
+    await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
+
+    await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});
+    collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+    expect(collectionSub.sponsorship.isConfirmed).to.be.true;
+    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
+
+    const user = createEthAccount(web3);
+    const nextTokenId = await collectionEvm.methods.nextTokenId().call();
+    expect(nextTokenId).to.be.equal('1');
+
+    const oldPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();
+    expect(oldPermissions.mintMode).to.be.false;
+    expect(oldPermissions.access).to.be.equal('Normal');
+
+    await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});
+    await collectionEvm.methods.addToCollectionAllowList(user).send({from: owner});
+    await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});
+
+    const newPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();
+    expect(newPermissions.mintMode).to.be.true;
+    expect(newPermissions.access).to.be.equal('AllowList');
+
+    const ownerBalanceBefore = await ethBalanceViaSub(api, owner);
+    const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);
+
+    {
+      const nextTokenId = await collectionEvm.methods.nextTokenId().call();
+      expect(nextTokenId).to.be.equal('1');
+      const result = await collectionEvm.methods.mintWithTokenURI(
+        user,
+        nextTokenId,
+        'Test URI',
+      ).send({from: user});
+      const events = normalizeEvents(result.events);
+
+      expect(events).to.be.deep.equal([
+        {
+          address: collectionIdAddress,
+          event: 'Transfer',
+          args: {
+            from: '0x0000000000000000000000000000000000000000',
+            to: user,
+            tokenId: nextTokenId,
+          },
+        },
+      ]);
+
+      const ownerBalanceAfter = await ethBalanceViaSub(api, owner);
+      const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);
+
+      expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+      expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);
+      expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;
+    }
+  });
+
+  itWeb3('Check that transaction via EVM spend money from sponsor address', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionHelpers = evmCollectionHelpers(web3, owner);
+    let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    result = await collectionEvm.methods.setCollectionSponsor(sponsor).send();
+    let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+    const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;
+    expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
+    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
+    await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
+    const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);
+    await sponsorCollection.methods.confirmCollectionSponsorship().send();
+    collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
+    expect(collectionSub.sponsorship.isConfirmed).to.be.true;
+    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
+
+    const user = createEthAccount(web3);
+    await collectionEvm.methods.addCollectionAdmin(user).send();
+    
+    const ownerBalanceBefore = await ethBalanceViaSub(api, owner);
+    const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);
+    
+  
+    const userCollectionEvm = evmCollection(web3, user, collectionIdAddress);
+    const nextTokenId = await userCollectionEvm.methods.nextTokenId().call();
+    expect(nextTokenId).to.be.equal('1');
+    result = await userCollectionEvm.methods.mintWithTokenURI(
+      user,
+      nextTokenId,
+      'Test URI',
+    ).send();
+
+    const events = normalizeEvents(result.events);
+    const address = collectionIdToAddress(collectionId);
+
+    expect(events).to.be.deep.equal([
+      {
+        address,
+        event: 'Transfer',
+        args: {
+          from: '0x0000000000000000000000000000000000000000',
+          to: user,
+          tokenId: nextTokenId,
+        },
+      },
+    ]);
+    expect(await userCollectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+  
+    const ownerBalanceAfter = await ethBalanceViaSub(api, owner);
+    expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);
+    const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);
+    expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;
+  });
 });
modifiedtests/src/eth/contractSponsoring.test.tsdiffbeforeafterboth
23 itWeb3,23 itWeb3,
24 SponsoringMode,24 SponsoringMode,
25 createEthAccount,25 createEthAccount,
26 collectionIdToAddress,
27 GAS_ARGS,
28 normalizeEvents,
29 subToEth,
30 executeEthTxOnSub,
31 evmCollectionHelpers,
32 getCollectionAddressFromResult,
33 evmCollection,
34 ethBalanceViaSub,26 ethBalanceViaSub,
35} from './util/helpers';27} from './util/helpers';
36import {
37 addCollectionAdminExpectSuccess,
38 createCollectionExpectSuccess,
39 getDetailedCollectionInfo,
40 transferBalanceTo,
41} from '../util/helpers';
42import nonFungibleAbi from './nonFungibleAbi.json';
43import getBalance from '../substrate/get-balance';
44import {evmToAddress} from '@polkadot/util-crypto';
4528
46describe('Sponsoring EVM contracts', () => {29describe('Sponsoring EVM contracts', () => {
30 itWeb3('Self sponsored can be set by the address that deployed the contract', async ({api, web3, privateKeyWrapper}) => {
31 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
32 const flipper = await deployFlipper(web3, owner);
33 const helpers = contractHelpers(web3, owner);
34 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;
35 await expect(helpers.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.not.rejected;
36 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true;
37 });
38
39 itWeb3('Self sponsored can not be set by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => {
40 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
41 const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
42 const flipper = await deployFlipper(web3, owner);
43 const helpers = contractHelpers(web3, owner);
44 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;
45 await expect(helpers.methods.selfSponsoredEnable(flipper.options.address).call({from: notOwner})).to.be.rejectedWith('NoPermission');
46 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;
47 });
48
47 itWeb3('Sponsoring can be set by the address that has deployed the contract', async ({api, web3, privateKeyWrapper}) => {49 itWeb3('Sponsoring can be set by the address that has deployed the contract', async ({api, web3, privateKeyWrapper}) => {
48 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);50 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
49 const flipper = await deployFlipper(web3, owner);51 const flipper = await deployFlipper(web3, owner);
50 const helpers = contractHelpers(web3, owner);52 const helpers = contractHelpers(web3, owner);
51 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;53 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
52 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});54 await expect(helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner})).to.be.not.rejected;
53 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;55 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;
54 });56 });
5557
59 const flipper = await deployFlipper(web3, owner);61 const flipper = await deployFlipper(web3, owner);
60 const helpers = contractHelpers(web3, owner);62 const helpers = contractHelpers(web3, owner);
61 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;63 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
62 await expect(helpers.methods.setSponsoringMode(notOwner, SponsoringMode.Allowlisted).send({from: notOwner})).to.rejected;64 await expect(helpers.methods.setSponsoringMode(notOwner, SponsoringMode.Allowlisted).call({from: notOwner})).to.be.rejectedWith('NoPermission');
63 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;65 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
64 });66 });
67
68 itWeb3('Sponsor can be set by the address that deployed the contract', async ({api, web3, privateKeyWrapper}) => {
69 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
70 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
71 const flipper = await deployFlipper(web3, owner);
72 const helpers = contractHelpers(web3, owner);
73 expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.false;
74 await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected;
75 expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.true;
76 });
77
78 itWeb3('Sponsor can not be set by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => {
79 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
80 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
81 const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
82 const flipper = await deployFlipper(web3, owner);
83 const helpers = contractHelpers(web3, owner);
84 expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.false;
85 await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).call({from: notOwner})).to.be.rejectedWith('NoPermission');
86 expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.false;
87 });
6588
89 itWeb3('Sponsorship can be confirmed by the address that pending as sponsor', async ({api, web3, privateKeyWrapper}) => {
90 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
91 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
92 const flipper = await deployFlipper(web3, owner);
93 const helpers = contractHelpers(web3, owner);
94 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;
95 await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected;
96 await expect(helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor})).to.be.not.rejected;
97 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true;
98 });
99
100 itWeb3('Sponsorship can not be confirmed by the address that not pending as sponsor', async ({api, web3, privateKeyWrapper}) => {
101 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
102 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
103 const notSponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
104 const flipper = await deployFlipper(web3, owner);
105 const helpers = contractHelpers(web3, owner);
106 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;
107 await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected;
108 await expect(helpers.methods.confirmSponsorship(flipper.options.address).call({from: notSponsor})).to.be.rejectedWith('NoPermission');
109 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;
110 });
111
112 itWeb3('Sponsorship can not be confirmed by the address that not set as sponsor', async ({api, web3, privateKeyWrapper}) => {
113 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
114 const notSponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
115 const flipper = await deployFlipper(web3, owner);
116 const helpers = contractHelpers(web3, owner);
117 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;
118 await expect(helpers.methods.confirmSponsorship(flipper.options.address).call({from: notSponsor})).to.be.rejectedWith('NoPendingSponsor');
119 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;
120 });
121
122 itWeb3('Get self sponsored sponsor', async ({api, web3, privateKeyWrapper}) => {
123 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
124 const flipper = await deployFlipper(web3, owner);
125 const helpers = contractHelpers(web3, owner);
126 await helpers.methods.selfSponsoredEnable(flipper.options.address).send();
127
128 const result = await helpers.methods.getSponsor(flipper.options.address).call();
129
130 expect(result[0]).to.be.eq(flipper.options.address);
131 expect(result[1]).to.be.eq('0');
132 });
133
134 itWeb3('Get confirmed sponsor', async ({api, web3, privateKeyWrapper}) => {
135 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
136 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
137 const flipper = await deployFlipper(web3, owner);
138 const helpers = contractHelpers(web3, owner);
139 await helpers.methods.setSponsor(flipper.options.address, sponsor).send();
140 await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});
141
142 const result = await helpers.methods.getSponsor(flipper.options.address).call();
143
144 expect(result[0]).to.be.eq(sponsor);
145 expect(result[1]).to.be.eq('0');
146 });
147
148 itWeb3('Sponsor can be removed by the address that deployed the contract', async ({api, web3, privateKeyWrapper}) => {
149 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
150 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
151 const flipper = await deployFlipper(web3, owner);
152 const helpers = contractHelpers(web3, owner);
153
154 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;
155 await helpers.methods.setSponsor(flipper.options.address, sponsor).send();
156 await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});
157 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true;
158
159 await helpers.methods.removeSponsor(flipper.options.address).send();
160 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;
161 });
162
163 itWeb3('Sponsor can not be removed by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => {
164 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
165 const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
166 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
167 const flipper = await deployFlipper(web3, owner);
168 const helpers = contractHelpers(web3, owner);
169
170 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false;
171 await helpers.methods.setSponsor(flipper.options.address, sponsor).send();
172 await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});
173 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true;
174
175 await expect(helpers.methods.removeSponsor(flipper.options.address).call({from: notOwner})).to.be.rejectedWith('NoPermission');
176 expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true;
177 });
178
66 itWeb3('In generous mode, non-allowlisted user transaction will be sponsored', async ({api, web3, privateKeyWrapper}) => {179 itWeb3('In generous mode, non-allowlisted user transaction will be sponsored', async ({api, web3, privateKeyWrapper}) => {
180 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
181 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
182 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
183
184 const flipper = await deployFlipper(web3, owner);
185
186 const helpers = contractHelpers(web3, owner);
187
188 await helpers.methods.setSponsor(flipper.options.address, sponsor).send();
189 await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});
190
191 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: owner});
192 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});
193
194 const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);
195 const callerBalanceBefore = await ethBalanceViaSub(api, caller);
196
197 await flipper.methods.flip().send({from: caller});
198 expect(await flipper.methods.getValue().call()).to.be.true;
199
200 // Balance should be taken from sponsor instead of caller
201 const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);
202 const callerBalanceAfter = await ethBalanceViaSub(api, caller);
203 expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;
204 expect(callerBalanceAfter).to.be.eq(callerBalanceBefore);
205 });
206
207 itWeb3('In generous mode, non-allowlisted user transaction will be self sponsored', async ({api, web3, privateKeyWrapper}) => {
67 const alice = privateKeyWrapper('//Alice');208 const alice = privateKeyWrapper('//Alice');
68209
69 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);210 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
73214
74 const helpers = contractHelpers(web3, owner);215 const helpers = contractHelpers(web3, owner);
75216
76 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;217 await helpers.methods.selfSponsoredEnable(flipper.options.address).send();
218
77 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: owner});219 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: owner});
78 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});220 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});
79 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;
80221
81 await transferBalanceToEth(api, alice, flipper.options.address);222 await transferBalanceToEth(api, alice, flipper.options.address);
82223
83 const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);224 const contractBalanceBefore = await ethBalanceViaSub(api, flipper.options.address);
84 expect(originalFlipperBalance).to.be.not.equal('0');225 const callerBalanceBefore = await ethBalanceViaSub(api, caller);
85226
86 await flipper.methods.flip().send({from: caller});227 await flipper.methods.flip().send({from: caller});
87 expect(await flipper.methods.getValue().call()).to.be.true;228 expect(await flipper.methods.getValue().call()).to.be.true;
88229
89 // Balance should be taken from flipper instead of caller230 // Balance should be taken from sponsor instead of caller
90 const balanceAfter = await web3.eth.getBalance(flipper.options.address);231 const contractBalanceAfter = await ethBalanceViaSub(api, flipper.options.address);
232 const callerBalanceAfter = await ethBalanceViaSub(api, caller);
233 expect(contractBalanceAfter < contractBalanceBefore).to.be.true;
91 expect(+balanceAfter).to.be.lessThan(+originalFlipperBalance);234 expect(callerBalanceAfter).to.be.eq(callerBalanceBefore);
92 });235 });
93236
94 itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should decrease (allowlisted)', async ({api, web3, privateKeyWrapper}) => {237 itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should decrease (allowlisted)', async ({api, web3, privateKeyWrapper}) => {
95 const alice = privateKeyWrapper('//Alice');
96
97 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);238 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
239 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
98 const caller = createEthAccount(web3);240 const caller = createEthAccount(web3);
99241
100 const flipper = await deployFlipper(web3, owner);242 const flipper = await deployFlipper(web3, owner);
103 await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});245 await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});
104 await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});246 await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});
105247
106 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
107 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});248 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});
108 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});249 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});
109 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;
110250
111 await transferBalanceToEth(api, alice, flipper.options.address);251 await helpers.methods.setSponsor(flipper.options.address, sponsor).send();
252 await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});
112253
113 const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);254 const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);
114 expect(originalFlipperBalance).to.be.not.equal('0');255 expect(sponsorBalanceBefore).to.be.not.equal('0');
115256
116 await flipper.methods.flip().send({from: caller});257 await flipper.methods.flip().send({from: caller});
117 expect(await flipper.methods.getValue().call()).to.be.true;258 expect(await flipper.methods.getValue().call()).to.be.true;
118259
119 // Balance should be taken from flipper instead of caller260 // Balance should be taken from flipper instead of caller
120 const balanceAfter = await web3.eth.getBalance(flipper.options.address);261 const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);
121 expect(+balanceAfter).to.be.lessThan(+originalFlipperBalance);262 expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;
122 });263 });
123264
124 itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should not decrease (non-allowlisted)', async ({api, web3, privateKeyWrapper}) => {265 itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should not decrease (non-allowlisted)', async ({api, web3, privateKeyWrapper}) => {
131272
132 const helpers = contractHelpers(web3, owner);273 const helpers = contractHelpers(web3, owner);
133274
134 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
135 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});275 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});
136 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});276 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});
137 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;
138277
139 await transferBalanceToEth(api, alice, flipper.options.address);278 await transferBalanceToEth(api, alice, flipper.options.address);
140279
150 });289 });
151290
152 itWeb3('Sponsoring is set, an address that has UNQ can send a transaction and it works. User balance should not change', async ({api, web3, privateKeyWrapper}) => {291 itWeb3('Sponsoring is set, an address that has UNQ can send a transaction and it works. User balance should not change', async ({api, web3, privateKeyWrapper}) => {
153 const alice = privateKeyWrapper('//Alice');
154
155 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);292 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
293 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
156 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);294 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
157 const originalCallerBalance = await web3.eth.getBalance(caller);
158295
159 const flipper = await deployFlipper(web3, owner);296 const flipper = await deployFlipper(web3, owner);
160297
161 const helpers = contractHelpers(web3, owner);298 const helpers = contractHelpers(web3, owner);
162 await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});299 await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});
163 await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});300 await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});
164301
165 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
166 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});302 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});
167 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});303 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});
168 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;
169304
170 await transferBalanceToEth(api, alice, flipper.options.address);305 await helpers.methods.setSponsor(flipper.options.address, sponsor).send();
306 await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});
171307
172 const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);308 const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);
173 expect(originalFlipperBalance).to.be.not.equal('0');309 const callerBalanceBefore = await ethBalanceViaSub(api, caller);
174310
175 await flipper.methods.flip().send({from: caller});311 await flipper.methods.flip().send({from: caller});
176 expect(await flipper.methods.getValue().call()).to.be.true;312 expect(await flipper.methods.getValue().call()).to.be.true;
177313
178 expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance);314 const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);
315 const callerBalanceAfter = await ethBalanceViaSub(api, caller);
316 expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;
317 expect(callerBalanceAfter).to.be.equals(callerBalanceBefore);
179 });318 });
180319
181 itWeb3('Sponsoring is limited, with setContractRateLimit. The limitation is working if transactions are sent more often, the sender pays the commission.', async ({api, web3, privateKeyWrapper}) => {320 itWeb3('Sponsoring is limited, with setContractRateLimit. The limitation is working if transactions are sent more often, the sender pays the commission.', async ({api, web3, privateKeyWrapper}) => {
182 const alice = privateKeyWrapper('//Alice');
183
184 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);321 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
322 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
185 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);323 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
186 const originalCallerBalance = await web3.eth.getBalance(caller);324 const originalCallerBalance = await web3.eth.getBalance(caller);
187325
191 await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});329 await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});
192 await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});330 await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});
193331
194 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
195 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});332 await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});
196 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 10).send({from: owner});333 await helpers.methods.setSponsoringRateLimit(flipper.options.address, 10).send({from: owner});
197 expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;
198334
199 await transferBalanceToEth(api, alice, flipper.options.address);335 await helpers.methods.setSponsor(flipper.options.address, sponsor).send();
336 await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});
200337
201 const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);338 const originalFlipperBalance = await web3.eth.getBalance(sponsor);
202 expect(originalFlipperBalance).to.be.not.equal('0');339 expect(originalFlipperBalance).to.be.not.equal('0');
203340
204 await flipper.methods.flip().send({from: caller});341 await flipper.methods.flip().send({from: caller});
205 expect(await flipper.methods.getValue().call()).to.be.true;342 expect(await flipper.methods.getValue().call()).to.be.true;
206 expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance);343 expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance);
207344
208 const newFlipperBalance = await web3.eth.getBalance(flipper.options.address);345 const newFlipperBalance = await web3.eth.getBalance(sponsor);
209 expect(newFlipperBalance).to.be.not.equals(originalFlipperBalance);346 expect(newFlipperBalance).to.be.not.equals(originalFlipperBalance);
210347
211 await flipper.methods.flip().send({from: caller});348 await flipper.methods.flip().send({from: caller});
212 expect(await web3.eth.getBalance(flipper.options.address)).to.be.equal(newFlipperBalance);349 expect(await web3.eth.getBalance(sponsor)).to.be.equal(newFlipperBalance);
213 expect(await web3.eth.getBalance(caller)).to.be.not.equals(originalCallerBalance);350 expect(await web3.eth.getBalance(caller)).to.be.not.equals(originalCallerBalance);
214 });351 });
215352
219 const flipper = await deployFlipper(web3, owner);356 const flipper = await deployFlipper(web3, owner);
220 const helpers = contractHelpers(web3, owner);357 const helpers = contractHelpers(web3, owner);
221 expect(await helpers.methods.getSponsoringRateLimit(flipper.options.address).call()).to.be.equals('7200');358 expect(await helpers.methods.getSponsoringRateLimit(flipper.options.address).call()).to.be.equals('7200');
222 });
223
224 itWeb3('Sponsoring collection from evm address via access list', async ({api, web3, privateKeyWrapper}) => {
225 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
226 const collectionHelpers = evmCollectionHelpers(web3, owner);
227 let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();
228 const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
229 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
230 const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
231 result = await collectionEvm.methods.setCollectionSponsor(sponsor).send({from: owner});
232 let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
233 const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;
234 expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
235 expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
236 await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
237
238 await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor});
239 collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
240 expect(collectionSub.sponsorship.isConfirmed).to.be.true;
241 expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
242
243 const user = createEthAccount(web3);
244 const nextTokenId = await collectionEvm.methods.nextTokenId().call();
245 expect(nextTokenId).to.be.equal('1');
246
247 const oldPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();
248 expect(oldPermissions.mintMode).to.be.false;
249 expect(oldPermissions.access).to.be.equal('Normal');
250
251 await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});
252 await collectionEvm.methods.addToCollectionAllowList(user).send({from: owner});
253 await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});
254
255 const newPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();
256 expect(newPermissions.mintMode).to.be.true;
257 expect(newPermissions.access).to.be.equal('AllowList');
258
259 const ownerBalanceBefore = await ethBalanceViaSub(api, owner);
260 const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);
261
262 {
263 const nextTokenId = await collectionEvm.methods.nextTokenId().call();
264 expect(nextTokenId).to.be.equal('1');
265 const result = await collectionEvm.methods.mintWithTokenURI(
266 user,
267 nextTokenId,
268 'Test URI',
269 ).send({from: user});
270 const events = normalizeEvents(result.events);
271
272 expect(events).to.be.deep.equal([
273 {
274 address: collectionIdAddress,
275 event: 'Transfer',
276 args: {
277 from: '0x0000000000000000000000000000000000000000',
278 to: user,
279 tokenId: nextTokenId,
280 },
281 },
282 ]);
283
284 const ownerBalanceAfter = await ethBalanceViaSub(api, owner);
285 const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);
286
287 expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
288 expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);
289 expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;
290 }
291 });
292
293 itWeb3('Check that transaction via EVM spend money from sponsor address', async ({api, web3, privateKeyWrapper}) => {
294 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
295 const collectionHelpers = evmCollectionHelpers(web3, owner);
296 let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();
297 const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
298 const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
299 const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
300 result = await collectionEvm.methods.setCollectionSponsor(sponsor).send();
301 let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
302 const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;
303 expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;
304 expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
305 await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor');
306 const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);
307 await sponsorCollection.methods.confirmCollectionSponsorship().send();
308 collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;
309 expect(collectionSub.sponsorship.isConfirmed).to.be.true;
310 expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format)));
311
312 const user = createEthAccount(web3);
313 await collectionEvm.methods.addCollectionAdmin(user).send();
314
315 const ownerBalanceBefore = await ethBalanceViaSub(api, owner);
316 const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);
317
318
319 const userCollectionEvm = evmCollection(web3, user, collectionIdAddress);
320 const nextTokenId = await userCollectionEvm.methods.nextTokenId().call();
321 expect(nextTokenId).to.be.equal('1');
322 result = await userCollectionEvm.methods.mintWithTokenURI(
323 user,
324 nextTokenId,
325 'Test URI',
326 ).send();
327
328 const events = normalizeEvents(result.events);
329 const address = collectionIdToAddress(collectionId);
330
331 expect(events).to.be.deep.equal([
332 {
333 address,
334 event: 'Transfer',
335 args: {
336 from: '0x0000000000000000000000000000000000000000',
337 to: user,
338 tokenId: nextTokenId,
339 },
340 },
341 ]);
342 expect(await userCollectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
343
344 const ownerBalanceAfter = await ethBalanceViaSub(api, owner);
345 expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);
346 const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);
347 expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;
348 });359 });
349});360});
350361
modifiedtests/src/eth/marketplace/marketplace.test.tsdiffbeforeafterboth
--- a/tests/src/eth/marketplace/marketplace.test.ts
+++ b/tests/src/eth/marketplace/marketplace.test.ts
@@ -39,6 +39,7 @@
 describe('Matcher contract usage', () => {
   itWeb3('With UNQ', async ({api, web3, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const matcherOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlace.abi`)).toString()), undefined, {
       from: matcherOwner,
@@ -48,7 +49,9 @@
     const helpers = contractHelpers(web3, matcherOwner);
     await helpers.methods.setSponsoringMode(matcher.options.address, SponsoringMode.Allowlisted).send({from: matcherOwner});
     await helpers.methods.setSponsoringRateLimit(matcher.options.address, 1).send({from: matcherOwner});
-    await transferBalanceToEth(api, alice, matcher.options.address);
+    
+    await helpers.methods.setSponsor(matcher.options.address, sponsor).send({from: matcherOwner});
+    await helpers.methods.confirmSponsorship(matcher.options.address).send({from: sponsor});
 
     const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
     await setCollectionLimitsExpectSuccess(alice, collectionId, {sponsorApproveTimeout: 1});
@@ -100,6 +103,7 @@
 
   itWeb3('With escrow', async ({api, web3, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const matcherOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const escrow = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlace.abi`)).toString()), undefined, {
@@ -111,7 +115,9 @@
     const helpers = contractHelpers(web3, matcherOwner);
     await helpers.methods.setSponsoringMode(matcher.options.address, SponsoringMode.Allowlisted).send({from: matcherOwner});
     await helpers.methods.setSponsoringRateLimit(matcher.options.address, 1).send({from: matcherOwner});
-    await transferBalanceToEth(api, alice, matcher.options.address);
+    
+    await helpers.methods.setSponsor(matcher.options.address, sponsor).send({from: matcherOwner});
+    await helpers.methods.confirmSponsorship(matcher.options.address).send({from: sponsor});
 
     const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
     await setCollectionLimitsExpectSuccess(alice, collectionId, {sponsorApproveTimeout: 1});
modifiedtests/src/eth/sponsoring.test.tsdiffbeforeafterboth
--- a/tests/src/eth/sponsoring.test.ts
+++ b/tests/src/eth/sponsoring.test.ts
@@ -15,13 +15,12 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {expect} from 'chai';
-import {contractHelpers, createEthAccount, createEthAccountWithBalance, deployCollector, deployFlipper, itWeb3, SponsoringMode, transferBalanceToEth} from './util/helpers';
+import {contractHelpers, createEthAccount, createEthAccountWithBalance, deployCollector, deployFlipper, itWeb3, SponsoringMode} from './util/helpers';
 
 describe('EVM sponsoring', () => {
   itWeb3('Fee is deducted from contract if sponsoring is enabled', async ({api, web3, privateKeyWrapper}) => {
-    const alice = privateKeyWrapper('//Alice');
-
     const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const caller = createEthAccount(web3);
     const originalCallerBalance = await web3.eth.getBalance(caller);
     expect(originalCallerBalance).to.be.equal('0');
@@ -31,28 +30,29 @@
     const helpers = contractHelpers(web3, owner);
     await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});
     await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});
+    
+    await helpers.methods.setSponsor(flipper.options.address, sponsor).send({from: owner});
+    await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor});
 
     expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;
     await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});
     await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});
     expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;
 
-    await transferBalanceToEth(api, alice, flipper.options.address);
-
-    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);
-    expect(originalFlipperBalance).to.be.not.equal('0');
+    const originalSponsorBalance = await web3.eth.getBalance(sponsor);
+    expect(originalSponsorBalance).to.be.not.equal('0');
 
     await flipper.methods.flip().send({from: caller});
     expect(await flipper.methods.getValue().call()).to.be.true;
 
     // Balance should be taken from flipper instead of caller
     expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance);
-    expect(await web3.eth.getBalance(flipper.options.address)).to.be.not.equals(originalFlipperBalance);
+    expect(await web3.eth.getBalance(sponsor)).to.be.not.equals(originalSponsorBalance);
   });
+
   itWeb3('...but this doesn\'t applies to payable value', async ({api, web3, privateKeyWrapper}) => {
-    const alice = privateKeyWrapper('//Alice');
-
     const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const originalCallerBalance = await web3.eth.getBalance(caller);
     expect(originalCallerBalance).to.be.not.equal('0');
@@ -68,16 +68,17 @@
     await helpers.methods.setSponsoringRateLimit(collector.options.address, 0).send({from: owner});
     expect(await helpers.methods.sponsoringEnabled(collector.options.address).call()).to.be.true;
 
-    await transferBalanceToEth(api, alice, collector.options.address);
+    await helpers.methods.setSponsor(collector.options.address, sponsor).send({from: owner});
+    await helpers.methods.confirmSponsorship(collector.options.address).send({from: sponsor});
 
-    const originalCollectorBalance = await web3.eth.getBalance(collector.options.address);
-    expect(originalCollectorBalance).to.be.not.equal('0');
+    const originalSponsorBalance = await web3.eth.getBalance(sponsor);
+    expect(originalSponsorBalance).to.be.not.equal('0');
 
     await collector.methods.giveMoney().send({from: caller, value: '10000'});
 
     // Balance will be taken from both caller (value) and from collector (fee)
     expect(await web3.eth.getBalance(caller)).to.be.equals((BigInt(originalCallerBalance) - 10000n).toString());
-    expect(await web3.eth.getBalance(collector.options.address)).to.be.not.equals(originalCollectorBalance);
+    expect(await web3.eth.getBalance(sponsor)).to.be.not.equals(originalSponsorBalance);
     expect(await collector.methods.getCollected().call()).to.be.equal('10000');
   });
 });
modifiedtests/src/eth/util/contractHelpersAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/util/contractHelpersAbi.json
+++ b/tests/src/eth/util/contractHelpersAbi.json
@@ -34,6 +34,19 @@
         "type": "address"
       }
     ],
+    "name": "confirmSponsorship",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      }
+    ],
     "name": "contractOwner",
     "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
     "stateMutability": "view",
@@ -47,6 +60,29 @@
         "type": "address"
       }
     ],
+    "name": "getSponsor",
+    "outputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple0",
+        "name": "",
+        "type": "tuple"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      }
+    ],
     "name": "getSponsoringRateLimit",
     "outputs": [{ "internalType": "uint32", "name": "", "type": "uint32" }],
     "stateMutability": "view",
@@ -58,6 +94,72 @@
         "internalType": "address",
         "name": "contractAddress",
         "type": "address"
+      }
+    ],
+    "name": "hasPendingSponsor",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      }
+    ],
+    "name": "hasSponsor",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      }
+    ],
+    "name": "removeSponsor",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      }
+    ],
+    "name": "selfSponsoredEnable",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
+      },
+      { "internalType": "address", "name": "sponsor", "type": "address" }
+    ],
+    "name": "setSponsor",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "contractAddress",
+        "type": "address"
       },
       { "internalType": "uint8", "name": "mode", "type": "uint8" }
     ],
@@ -140,20 +242,6 @@
       { "internalType": "bool", "name": "enabled", "type": "bool" }
     ],
     "name": "toggleAllowlist",
-    "outputs": [],
-    "stateMutability": "nonpayable",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "internalType": "address",
-        "name": "contractAddress",
-        "type": "address"
-      },
-      { "internalType": "bool", "name": "enabled", "type": "bool" }
-    ],
-    "name": "toggleSponsoring",
     "outputs": [],
     "stateMutability": "nonpayable",
     "type": "function"