git.delta.rocks / unique-network / refs/commits / 982476ef99fe

difftreelog

feautres: Pending interval is now tied to relay blocks, contract sponsors and `stopAppPromotion` added. Preparing to integrate the `app-promotion` palette to integrate with Unique and Quartz.

PraetorP2022-08-29parent: #9d0f344.patch.diff
in: master

13 files changed

modifiedpallets/app-promotion/src/lib.rsdiffbeforeafterboth
--- a/pallets/app-promotion/src/lib.rs
+++ b/pallets/app-promotion/src/lib.rs
@@ -37,9 +37,10 @@
 pub mod weights;
 
 use sp_std::{vec::Vec, iter::Sum, borrow::ToOwned};
+use sp_core::H160;
 use codec::EncodeLike;
 use pallet_balances::BalanceLock;
-pub use types::ExtendedLockableCurrency;
+pub use types::*;
 
 // use up_common::constants::{DAYS, UNIQUE};
 use up_data_structs::CollectionId;
@@ -78,7 +79,6 @@
 	use super::*;
 	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId};
 	use frame_system::pallet_prelude::*;
-	use types::CollectionHandler;
 
 	#[pallet::config]
 	pub trait Config: frame_system::Config + pallet_evm::account::Config {
@@ -89,6 +89,8 @@
 			CollectionId = CollectionId,
 		>;
 
+		type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;
+
 		type TreasuryAccountId: Get<Self::AccountId>;
 
 		/// The app's pallet id, used for deriving its sovereign account ID.
@@ -98,7 +100,7 @@
 		/// In relay blocks.
 		#[pallet::constant]
 		type RecalculationInterval: Get<Self::BlockNumber>;
-		/// In chain blocks.
+		/// In relay blocks.
 		#[pallet::constant]
 		type PendingInterval: Get<Self::BlockNumber>;
 
@@ -120,13 +122,6 @@
 
 		/// Events compatible with [`frame_system::Config::Event`].
 		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;
-
-		// /// Number of blocks that pass between treasury balance updates due to inflation
-		// #[pallet::constant]
-		// type InterestBlockInterval: Get<Self::BlockNumber>;
-
-		// // Weight information for functions of this pallet.
-		// type WeightInfo: WeightInfo;
 	}
 
 	#[pallet::pallet]
@@ -146,13 +141,14 @@
 
 	#[pallet::error]
 	pub enum Error<T> {
+		/// Error due to action requiring admin to be set
 		AdminNotSet,
-		/// No permission to perform action
+		/// No permission to perform an action
 		NoPermission,
 		/// Insufficient funds to perform an action
 		NotSufficientFounds,
+		/// An error related to the fact that an invalid argument was passed to perform an action
 		InvalidArgument,
-		AlreadySponsored,
 	}
 
 	#[pallet::storage]
@@ -183,7 +179,7 @@
 		QueryKind = ValueQuery,
 	>;
 
-	/// A block when app-promotion has started
+	/// A block when app-promotion has started .I think this is redundant, because we only need `NextInterestBlock`.
 	#[pallet::storage]
 	pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;
 
@@ -285,6 +281,21 @@
 			Ok(())
 		}
 
+		#[pallet::weight(0)]
+		pub fn stop_app_promotion(origin: OriginFor<T>) -> DispatchResult
+		where
+			<T as frame_system::Config>::BlockNumber: From<u32>,
+		{
+			ensure_root(origin)?;
+
+			if <StartBlock<T>>::get() != 0u32.into() {
+				<StartBlock<T>>::set(T::BlockNumber::default());
+				<NextInterestBlock<T>>::set(T::BlockNumber::default());
+			}
+
+			Ok(())
+		}
+
 		#[pallet::weight(T::WeightInfo::stake())]
 		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {
 			let staker_id = ensure_signed(staker)?;
@@ -341,7 +352,8 @@
 					.ok_or(ArithmeticError::Underflow)?,
 			);
 
-			let block = frame_system::Pallet::<T>::block_number() + T::PendingInterval::get();
+			let block =
+				T::RelayBlockNumberProvider::current_block_number() + T::PendingInterval::get();
 			<PendingUnstake<T>>::insert(
 				(&staker_id, block),
 				<PendingUnstake<T>>::get((&staker_id, block))
@@ -415,113 +427,46 @@
 			);
 			T::CollectionHandler::remove_collection_sponsor(collection_id)
 		}
-	}
-}
-
-impl<T: Config> Pallet<T> {
-	// pub fn stake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {
-	// 	let balance = <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(staker);
-
-	// 	ensure!(balance >= amount, ArithmeticError::Underflow);
-
-	// 	Self::set_lock_unchecked(staker, amount);
-
-	// 	let block_number = <T::BlockNumberProvider as BlockNumberProvider>::current_block_number();
 
-	// 	<Staked<T>>::insert(
-	// 		(staker, block_number),
-	// 		<Staked<T>>::get((staker, block_number))
-	// 			.checked_add(&amount)
-	// 			.ok_or(ArithmeticError::Overflow)?,
-	// 	);
-
-	// 	<TotalStaked<T>>::set(
-	// 		<TotalStaked<T>>::get()
-	// 			.checked_add(&amount)
-	// 			.ok_or(ArithmeticError::Overflow)?,
-	// 	);
-
-	// 	Ok(())
-	// }
-
-	// pub fn unstake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {
-	// 	let mut stakes = Staked::<T>::iter_prefix((staker,)).collect::<Vec<_>>();
-
-	// 	let total_staked = stakes
-	// 		.iter()
-	// 		.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);
-
-	// 	ensure!(total_staked >= amount, ArithmeticError::Underflow);
-
-	// 	<TotalStaked<T>>::set(
-	// 		<TotalStaked<T>>::get()
-	// 			.checked_sub(&amount)
-	// 			.ok_or(ArithmeticError::Underflow)?,
-	// 	);
-
-	// 	let block = <T::BlockNumberProvider>::current_block_number() + WEEK.into();
-	// 	<PendingUnstake<T>>::insert(
-	// 		(staker, block),
-	// 		<PendingUnstake<T>>::get((staker, block))
-	// 			.checked_add(&amount)
-	// 			.ok_or(ArithmeticError::Overflow)?,
-	// 	);
-
-	// 	stakes.sort_by_key(|(block, _)| *block);
-
-	// 	let mut acc_amount = amount;
-	// 	let new_state = stakes
-	// 		.into_iter()
-	// 		.map_while(|(block, balance_per_block)| {
-	// 			if acc_amount == <BalanceOf<T>>::default() {
-	// 				return None;
-	// 			}
-	// 			if acc_amount <= balance_per_block {
-	// 				let res = (block, balance_per_block - acc_amount, acc_amount);
-	// 				acc_amount = <BalanceOf<T>>::default();
-	// 				return Some(res);
-	// 			} else {
-	// 				acc_amount -= balance_per_block;
-	// 				return Some((block, <BalanceOf<T>>::default(), acc_amount));
-	// 			}
-	// 		})
-	// 		.collect::<Vec<_>>();
-
-	// 	new_state
-	// 		.into_iter()
-	// 		.for_each(|(block, to_staked, _to_pending)| {
-	// 			if to_staked == <BalanceOf<T>>::default() {
-	// 				<Staked<T>>::remove((staker, block));
-	// 			} else {
-	// 				<Staked<T>>::insert((staker, block), to_staked);
-	// 			}
-	// 		});
+		#[pallet::weight(0)]
+		pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {
+			let admin_id = ensure_signed(admin)?;
 
-	// 	Ok(())
-	// }
+			ensure!(
+				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,
+				Error::<T>::NoPermission
+			);
 
-	// pub fn sponsor_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {
-	// 	Ok(())
-	// }
+			T::ContractHandler::set_sponsor(
+				T::CrossAccountId::from_sub(Self::account_id()),
+				contract_id,
+			)
+		}
 
-	// pub fn stop_sponsorign_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {
-	// 	Ok(())
-	// }
+		#[pallet::weight(0)]
+		pub fn stop_sponsorign_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {
+			let admin_id = ensure_signed(admin)?;
 
-	pub fn sponsor_conract(admin: T::AccountId, app_id: u32) -> DispatchResult {
-		Ok(())
-	}
+			ensure!(
+				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,
+				Error::<T>::NoPermission
+			);
 
-	pub fn stop_sponsorign_contract(admin: T::AccountId, app_id: u32) -> DispatchResult {
-		Ok(())
+			ensure!(
+				T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?
+					== T::CrossAccountId::from_sub(Self::account_id()),
+				<Error<T>>::NoPermission
+			);
+			T::ContractHandler::remove_contract_sponsor(contract_id)
+		}
 	}
+}
 
+impl<T: Config> Pallet<T> {
 	pub fn account_id() -> T::AccountId {
 		T::PalletId::get().into_account_truncating()
 	}
-}
 
-impl<T: Config> Pallet<T> {
 	fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {
 		let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();
 		locked_balance -= amount;
modifiedpallets/app-promotion/src/types.rsdiffbeforeafterboth
--- a/pallets/app-promotion/src/types.rs
+++ b/pallets/app-promotion/src/types.rs
@@ -9,7 +9,7 @@
 use sp_runtime::DispatchError;
 use up_data_structs::{CollectionId, SponsorshipState};
 use sp_std::borrow::ToOwned;
-
+use pallet_evm_contract_helpers::{Pallet as EvmHelpersPallet, Config as EvmHelpersConfig, Sponsoring};
 
 pub trait ExtendedLockableCurrency<AccountId: Parameter>: LockableCurrency<AccountId> {
 	fn locks<KArg>(who: KArg) -> WeakBoundedVec<BalanceLock<Self::Balance>, Self::MaxLocks>
@@ -94,3 +94,40 @@
 			.map(|acc| acc.to_owned()))
 	}
 }
+
+pub trait ContractHandler {
+	type ContractId;
+	type AccountId;
+
+	fn set_sponsor(sponsor_id: Self::AccountId, contract_id: Self::ContractId) -> DispatchResult;
+
+	fn remove_contract_sponsor(collection_id: Self::ContractId) -> DispatchResult;
+
+	fn get_sponsor(contract_id: Self::ContractId)
+		-> Result<Option<Self::AccountId>, DispatchError>;
+}
+
+impl<T: EvmHelpersConfig> ContractHandler for EvmHelpersPallet<T> {
+	type ContractId = sp_core::H160;
+
+	type AccountId = T::CrossAccountId;
+
+	fn set_sponsor(sponsor_id: Self::AccountId, contract_id: Self::ContractId) -> DispatchResult {
+		Sponsoring::<T>::insert(
+			contract_id,
+			SponsorshipState::<T::CrossAccountId>::Confirmed(sponsor_id),
+		);
+		Ok(())
+	}
+
+	fn remove_contract_sponsor(contract_id: Self::ContractId) -> DispatchResult {
+		Sponsoring::<T>::remove(contract_id);
+		Ok(())
+	}
+
+	fn get_sponsor(
+		contract_id: Self::ContractId,
+	) -> Result<Option<Self::AccountId>, DispatchError> {
+		Ok(Self::get_sponsor(contract_id))
+	}
+}
modifiedpallets/evm-contract-helpers/src/lib.rsdiffbeforeafterboth
before · pallets/evm-contract-helpers/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![doc = include_str!("../README.md")]18#![cfg_attr(not(feature = "std"), no_std)]19#![deny(missing_docs)]2021use codec::{Decode, Encode, MaxEncodedLen};22pub use pallet::*;23pub use eth::*;24use scale_info::TypeInfo;25pub mod eth;2627#[frame_support::pallet]28pub mod pallet {29	pub use super::*;30	use frame_support::pallet_prelude::*;31	use pallet_evm_coder_substrate::DispatchResult;32	use sp_core::H160;33	use pallet_evm::account::CrossAccountId;34	use up_data_structs::SponsorshipState;3536	#[pallet::config]37	pub trait Config:38		frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::account::Config39	{40		/// Address, under which magic contract will be available41		type ContractAddress: Get<H160>;42		/// In case of enabled sponsoring, but no sponsoring rate limit set,43		/// this value will be used implicitly44		type DefaultSponsoringRateLimit: Get<Self::BlockNumber>;45	}4647	#[pallet::error]48	pub enum Error<T> {49		/// This method is only executable by contract owner50		NoPermission,5152		/// No pending sponsor for contract.53		NoPendingSponsor,54	}5556	#[pallet::pallet]57	#[pallet::generate_store(pub(super) trait Store)]58	pub struct Pallet<T>(_);5960	/// Store owner for contract.61	///62	/// * **Key** - contract address.63	/// * **Value** - owner for contract.64	#[pallet::storage]65	pub(super) type Owner<T: Config> =66		StorageMap<Hasher = Twox128, Key = H160, Value = H160, QueryKind = ValueQuery>;6768	#[pallet::storage]69	#[deprecated]70	pub(super) type SelfSponsoring<T: Config> =71		StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;7273	/// Store for contract sponsorship state.74	///75	/// * **Key** - contract address.76	/// * **Value** - sponsorship state.77	#[pallet::storage]78	pub(super) type Sponsoring<T: Config> = StorageMap<79		Hasher = Twox64Concat,80		Key = H160,81		Value = SponsorshipState<T::CrossAccountId>,82		QueryKind = ValueQuery,83	>;8485	/// Store for sponsoring mode.86	///87	/// ### Usage88	/// Prefer to delete collection from storage if mode chaged to [`Disabled`](SponsoringModeT::Disabled).89	///90	/// * **Key** - contract address.91	/// * **Value** - [`sponsoring mode`](SponsoringModeT).92	#[pallet::storage]93	pub(super) type SponsoringMode<T: Config> =94		StorageMap<Hasher = Twox128, Key = H160, Value = SponsoringModeT, QueryKind = OptionQuery>;9596	/// Storage for sponsoring rate limit in blocks.97	///98	/// * **Key** - contract address.99	/// * **Value** - amount of sponsored blocks.100	#[pallet::storage]101	pub(super) type SponsoringRateLimit<T: Config> = StorageMap<102		Hasher = Twox128,103		Key = H160,104		Value = T::BlockNumber,105		QueryKind = ValueQuery,106		OnEmpty = T::DefaultSponsoringRateLimit,107	>;108109	/// Storage for last sponsored block.110	///111	/// * **Key1** - contract address.112	/// * **Key2** - sponsored user address.113	/// * **Value** - last sponsored block number.114	#[pallet::storage]115	pub(super) type SponsorBasket<T: Config> = StorageDoubleMap<116		Hasher1 = Twox128,117		Key1 = H160,118		Hasher2 = Twox128,119		Key2 = H160,120		Value = T::BlockNumber,121		QueryKind = OptionQuery,122	>;123124	/// Storege for contracts with [`Allowlisted`](SponsoringModeT::Allowlisted) sponsoring mode.125	///126	/// ### Usage127	/// Prefer to delete collection from storage if mode chaged to non `Allowlisted`, than set **Value** to **false**.128	///129	/// * **Key** - contract address.130	/// * **Value** - is contract in [`Allowlisted`](SponsoringModeT::Allowlisted) mode.131	#[pallet::storage]132	pub(super) type AllowlistEnabled<T: Config> =133		StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;134135	/// Storage for users that allowed for sponsorship.136	///137	/// ### Usage138	/// Prefer to delete record from storage if user no more allowed for sponsorship.139	///140	/// * **Key1** - contract address.141	/// * **Key2** - user that allowed for sponsorship.142	/// * **Value** - allowance for sponsorship.143	#[pallet::storage]144	pub(super) type Allowlist<T: Config> = StorageDoubleMap<145		Hasher1 = Twox128,146		Key1 = H160,147		Hasher2 = Twox128,148		Key2 = H160,149		Value = bool,150		QueryKind = ValueQuery,151	>;152153	impl<T: Config> Pallet<T> {154		/// Get contract owner.155		pub fn contract_owner(contract: H160) -> H160 {156			<Owner<T>>::get(contract)157		}158159		/// Set `sponsor` for `contract`.160		///161		/// `sender` must be owner of contract.162		pub fn set_sponsor(163			sender: &T::CrossAccountId,164			contract: H160,165			sponsor: &T::CrossAccountId,166		) -> DispatchResult {167			Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;168			Sponsoring::<T>::insert(169				contract,170				SponsorshipState::<T::CrossAccountId>::Unconfirmed(sponsor.clone()),171			);172			Ok(())173		}174175		/// Set `contract` as self sponsored.176		///177		/// `sender` must be owner of contract.178		pub fn self_sponsored_enable(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {179			Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;180			Sponsoring::<T>::insert(181				contract,182				SponsorshipState::<T::CrossAccountId>::Confirmed(T::CrossAccountId::from_eth(183					contract,184				)),185			);186			Ok(())187		}188189		/// Remove sponsor for `contract`.190		///191		/// `sender` must be owner of contract.192		pub fn remove_sponsor(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {193			Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;194			Sponsoring::<T>::remove(contract);195			Ok(())196		}197198		/// Confirm sponsorship.199		///200		/// `sender` must be same that set via [`set_sponsor`].201		pub fn confirm_sponsorship(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {202			match Sponsoring::<T>::get(contract) {203				SponsorshipState::Unconfirmed(sponsor) => {204					ensure!(sponsor == *sender, Error::<T>::NoPermission);205					Sponsoring::<T>::insert(206						contract,207						SponsorshipState::<T::CrossAccountId>::Confirmed(sponsor),208					);209					Ok(())210				}211				SponsorshipState::Disabled | SponsorshipState::Confirmed(_) => {212					Err(Error::<T>::NoPendingSponsor.into())213				}214			}215		}216217		/// Get sponsor.218		pub fn get_sponsor(contract: H160) -> Option<T::CrossAccountId> {219			match Sponsoring::<T>::get(contract) {220				SponsorshipState::Disabled | SponsorshipState::Unconfirmed(_) => None,221				SponsorshipState::Confirmed(sponsor) => Some(sponsor),222			}223		}224225		/// Get current sponsoring mode, performing lazy migration from legacy storage226		pub fn sponsoring_mode(contract: H160) -> SponsoringModeT {227			<SponsoringMode<T>>::get(contract)228				.or_else(|| {229					<SelfSponsoring<T>>::get(contract).then(|| SponsoringModeT::Allowlisted)230				})231				.unwrap_or_default()232		}233234		/// Reconfigure contract sponsoring mode235		pub fn set_sponsoring_mode(contract: H160, mode: SponsoringModeT) {236			if mode == SponsoringModeT::Disabled {237				<SponsoringMode<T>>::remove(contract);238			} else {239				<SponsoringMode<T>>::insert(contract, mode);240			}241			<SelfSponsoring<T>>::remove(contract)242		}243244		/// Set duration between two sponsored contract calls245		pub fn set_sponsoring_rate_limit(contract: H160, rate_limit: T::BlockNumber) {246			<SponsoringRateLimit<T>>::insert(contract, rate_limit);247		}248249		/// Is user added to allowlist, or he is owner of specified contract250		pub fn allowed(contract: H160, user: H160) -> bool {251			<Allowlist<T>>::get(&contract, &user) || <Owner<T>>::get(&contract) == user252		}253254		/// Toggle contract allowlist access255		pub fn toggle_allowlist(contract: H160, enabled: bool) {256			<AllowlistEnabled<T>>::insert(contract, enabled)257		}258259		/// Toggle user presence in contract's allowlist260		pub fn toggle_allowed(contract: H160, user: H160, allowed: bool) {261			<Allowlist<T>>::insert(contract, user, allowed);262		}263264		/// Throw error if user is not allowed to reconfigure target contract265		pub fn ensure_owner(contract: H160, user: H160) -> DispatchResult {266			ensure!(<Owner<T>>::get(&contract) == user, Error::<T>::NoPermission);267			Ok(())268		}269	}270}271272/// Available contract sponsoring modes273#[derive(Encode, Decode, PartialEq, TypeInfo, MaxEncodedLen, Default)]274pub enum SponsoringModeT {275	/// Sponsoring is disabled276	#[default]277	Disabled,278	/// Only users from allowlist will be sponsored279	Allowlisted,280	/// All users will be sponsored281	Generous,282}283284impl SponsoringModeT {285	fn from_eth(v: u8) -> Option<Self> {286		Some(match v {287			0 => Self::Disabled,288			1 => Self::Allowlisted,289			2 => Self::Generous,290			_ => return None,291		})292	}293	fn to_eth(self) -> u8 {294		match self {295			SponsoringModeT::Disabled => 0,296			SponsoringModeT::Allowlisted => 1,297			SponsoringModeT::Generous => 2,298		}299	}300}
after · pallets/evm-contract-helpers/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![doc = include_str!("../README.md")]18#![cfg_attr(not(feature = "std"), no_std)]19#![deny(missing_docs)]2021use codec::{Decode, Encode, MaxEncodedLen};22pub use pallet::*;23pub use eth::*;24use scale_info::TypeInfo;25pub mod eth;2627#[frame_support::pallet]28pub mod pallet {29	pub use super::*;30	use frame_support::pallet_prelude::*;31	use pallet_evm_coder_substrate::DispatchResult;32	use sp_core::H160;33	use pallet_evm::account::CrossAccountId;34	use up_data_structs::SponsorshipState;3536	#[pallet::config]37	pub trait Config:38		frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::account::Config39	{40		/// Address, under which magic contract will be available41		type ContractAddress: Get<H160>;42		/// In case of enabled sponsoring, but no sponsoring rate limit set,43		/// this value will be used implicitly44		type DefaultSponsoringRateLimit: Get<Self::BlockNumber>;45	}4647	#[pallet::error]48	pub enum Error<T> {49		/// This method is only executable by contract owner50		NoPermission,5152		/// No pending sponsor for contract.53		NoPendingSponsor,54	}5556	#[pallet::pallet]57	#[pallet::generate_store(pub(super) trait Store)]58	pub struct Pallet<T>(_);5960	/// Store owner for contract.61	///62	/// * **Key** - contract address.63	/// * **Value** - owner for contract.64	#[pallet::storage]65	pub(super) type Owner<T: Config> =66		StorageMap<Hasher = Twox128, Key = H160, Value = H160, QueryKind = ValueQuery>;6768	#[pallet::storage]69	#[deprecated]70	pub(super) type SelfSponsoring<T: Config> =71		StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;7273	/// Store for contract sponsorship state.74	///75	/// * **Key** - contract address.76	/// * **Value** - sponsorship state.77	#[pallet::storage]78	pub type Sponsoring<T: Config> = StorageMap<79		Hasher = Twox64Concat,80		Key = H160,81		Value = SponsorshipState<T::CrossAccountId>,82		QueryKind = ValueQuery,83	>;8485	/// Store for sponsoring mode.86	///87	/// ### Usage88	/// Prefer to delete collection from storage if mode chaged to [`Disabled`](SponsoringModeT::Disabled).89	///90	/// * **Key** - contract address.91	/// * **Value** - [`sponsoring mode`](SponsoringModeT).92	#[pallet::storage]93	pub(super) type SponsoringMode<T: Config> =94		StorageMap<Hasher = Twox128, Key = H160, Value = SponsoringModeT, QueryKind = OptionQuery>;9596	/// Storage for sponsoring rate limit in blocks.97	///98	/// * **Key** - contract address.99	/// * **Value** - amount of sponsored blocks.100	#[pallet::storage]101	pub(super) type SponsoringRateLimit<T: Config> = StorageMap<102		Hasher = Twox128,103		Key = H160,104		Value = T::BlockNumber,105		QueryKind = ValueQuery,106		OnEmpty = T::DefaultSponsoringRateLimit,107	>;108109	/// Storage for last sponsored block.110	///111	/// * **Key1** - contract address.112	/// * **Key2** - sponsored user address.113	/// * **Value** - last sponsored block number.114	#[pallet::storage]115	pub(super) type SponsorBasket<T: Config> = StorageDoubleMap<116		Hasher1 = Twox128,117		Key1 = H160,118		Hasher2 = Twox128,119		Key2 = H160,120		Value = T::BlockNumber,121		QueryKind = OptionQuery,122	>;123124	/// Storege for contracts with [`Allowlisted`](SponsoringModeT::Allowlisted) sponsoring mode.125	///126	/// ### Usage127	/// Prefer to delete collection from storage if mode chaged to non `Allowlisted`, than set **Value** to **false**.128	///129	/// * **Key** - contract address.130	/// * **Value** - is contract in [`Allowlisted`](SponsoringModeT::Allowlisted) mode.131	#[pallet::storage]132	pub(super) type AllowlistEnabled<T: Config> =133		StorageMap<Hasher = Twox128, Key = H160, Value = bool, QueryKind = ValueQuery>;134135	/// Storage for users that allowed for sponsorship.136	///137	/// ### Usage138	/// Prefer to delete record from storage if user no more allowed for sponsorship.139	///140	/// * **Key1** - contract address.141	/// * **Key2** - user that allowed for sponsorship.142	/// * **Value** - allowance for sponsorship.143	#[pallet::storage]144	pub(super) type Allowlist<T: Config> = StorageDoubleMap<145		Hasher1 = Twox128,146		Key1 = H160,147		Hasher2 = Twox128,148		Key2 = H160,149		Value = bool,150		QueryKind = ValueQuery,151	>;152153	impl<T: Config> Pallet<T> {154		/// Get contract owner.155		pub fn contract_owner(contract: H160) -> H160 {156			<Owner<T>>::get(contract)157		}158159		/// Set `sponsor` for `contract`.160		///161		/// `sender` must be owner of contract.162		pub fn set_sponsor(163			sender: &T::CrossAccountId,164			contract: H160,165			sponsor: &T::CrossAccountId,166		) -> DispatchResult {167			Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;168			Sponsoring::<T>::insert(169				contract,170				SponsorshipState::<T::CrossAccountId>::Unconfirmed(sponsor.clone()),171			);172			Ok(())173		}174175		/// Set `contract` as self sponsored.176		///177		/// `sender` must be owner of contract.178		pub fn self_sponsored_enable(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {179			Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;180			Sponsoring::<T>::insert(181				contract,182				SponsorshipState::<T::CrossAccountId>::Confirmed(T::CrossAccountId::from_eth(183					contract,184				)),185			);186			Ok(())187		}188189		/// Remove sponsor for `contract`.190		///191		/// `sender` must be owner of contract.192		pub fn remove_sponsor(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {193			Pallet::<T>::ensure_owner(contract, *sender.as_eth())?;194			Sponsoring::<T>::remove(contract);195			Ok(())196		}197198		/// Confirm sponsorship.199		///200		/// `sender` must be same that set via [`set_sponsor`].201		pub fn confirm_sponsorship(sender: &T::CrossAccountId, contract: H160) -> DispatchResult {202			match Sponsoring::<T>::get(contract) {203				SponsorshipState::Unconfirmed(sponsor) => {204					ensure!(sponsor == *sender, Error::<T>::NoPermission);205					Sponsoring::<T>::insert(206						contract,207						SponsorshipState::<T::CrossAccountId>::Confirmed(sponsor),208					);209					Ok(())210				}211				SponsorshipState::Disabled | SponsorshipState::Confirmed(_) => {212					Err(Error::<T>::NoPendingSponsor.into())213				}214			}215		}216217		/// Get sponsor.218		pub fn get_sponsor(contract: H160) -> Option<T::CrossAccountId> {219			match Sponsoring::<T>::get(contract) {220				SponsorshipState::Disabled | SponsorshipState::Unconfirmed(_) => None,221				SponsorshipState::Confirmed(sponsor) => Some(sponsor),222			}223		}224225		/// Get current sponsoring mode, performing lazy migration from legacy storage226		pub fn sponsoring_mode(contract: H160) -> SponsoringModeT {227			<SponsoringMode<T>>::get(contract)228				.or_else(|| {229					<SelfSponsoring<T>>::get(contract).then(|| SponsoringModeT::Allowlisted)230				})231				.unwrap_or_default()232		}233234		/// Reconfigure contract sponsoring mode235		pub fn set_sponsoring_mode(contract: H160, mode: SponsoringModeT) {236			if mode == SponsoringModeT::Disabled {237				<SponsoringMode<T>>::remove(contract);238			} else {239				<SponsoringMode<T>>::insert(contract, mode);240			}241			<SelfSponsoring<T>>::remove(contract)242		}243244		/// Set duration between two sponsored contract calls245		pub fn set_sponsoring_rate_limit(contract: H160, rate_limit: T::BlockNumber) {246			<SponsoringRateLimit<T>>::insert(contract, rate_limit);247		}248249		/// Is user added to allowlist, or he is owner of specified contract250		pub fn allowed(contract: H160, user: H160) -> bool {251			<Allowlist<T>>::get(&contract, &user) || <Owner<T>>::get(&contract) == user252		}253254		/// Toggle contract allowlist access255		pub fn toggle_allowlist(contract: H160, enabled: bool) {256			<AllowlistEnabled<T>>::insert(contract, enabled)257		}258259		/// Toggle user presence in contract's allowlist260		pub fn toggle_allowed(contract: H160, user: H160, allowed: bool) {261			<Allowlist<T>>::insert(contract, user, allowed);262		}263264		/// Throw error if user is not allowed to reconfigure target contract265		pub fn ensure_owner(contract: H160, user: H160) -> DispatchResult {266			ensure!(<Owner<T>>::get(&contract) == user, Error::<T>::NoPermission);267			Ok(())268		}269	}270}271272/// Available contract sponsoring modes273#[derive(Encode, Decode, PartialEq, TypeInfo, MaxEncodedLen, Default)]274pub enum SponsoringModeT {275	/// Sponsoring is disabled276	#[default]277	Disabled,278	/// Only users from allowlist will be sponsored279	Allowlisted,280	/// All users will be sponsored281	Generous,282}283284impl SponsoringModeT {285	fn from_eth(v: u8) -> Option<Self> {286		Some(match v {287			0 => Self::Disabled,288			1 => Self::Allowlisted,289			2 => Self::Generous,290			_ => return None,291		})292	}293	fn to_eth(self) -> u8 {294		match self {295			SponsoringModeT::Disabled => 0,296			SponsoringModeT::Allowlisted => 1,297			SponsoringModeT::Generous => 2,298		}299	}300}
modifiedprimitives/common/src/constants.rsdiffbeforeafterboth
--- a/primitives/common/src/constants.rs
+++ b/primitives/common/src/constants.rs
@@ -22,6 +22,7 @@
 use crate::types::{BlockNumber, Balance};
 
 pub const MILLISECS_PER_BLOCK: u64 = 12000;
+pub const MILLISECS_PER_RELAY_BLOCK: u64 = 6000;
 
 pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;
 
@@ -30,6 +31,11 @@
 pub const HOURS: BlockNumber = MINUTES * 60;
 pub const DAYS: BlockNumber = HOURS * 24;
 
+// These time units are defined in number of relay blocks.
+pub const RELAY_MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_RELAY_BLOCK as BlockNumber);
+pub const RELAY_HOURS: BlockNumber = RELAY_MINUTES * 60;
+pub const RELAY_DAYS: BlockNumber = RELAY_HOURS * 24;
+
 pub const MICROUNIQUE: Balance = 1_000_000_000_000;
 pub const MILLIUNIQUE: Balance = 1_000 * MICROUNIQUE;
 pub const CENTIUNIQUE: Balance = 10 * MILLIUNIQUE;
modifiedruntime/common/config/pallets/app_promotion.rsdiffbeforeafterboth
--- a/runtime/common/config/pallets/app_promotion.rs
+++ b/runtime/common/config/pallets/app_promotion.rs
@@ -16,28 +16,40 @@
 
 use crate::{
 	runtime_common::config::pallets::{TreasuryAccountId, RelayChainBlockNumberProvider},
-	Runtime, Balances, BlockNumber, Unique, Event,
+	Runtime, Balances, BlockNumber, Unique, Event, EvmContractHelpers,
 };
 
 use frame_support::{parameter_types, PalletId};
 use sp_arithmetic::Perbill;
 use up_common::{
-	constants::{DAYS, UNIQUE},
+	constants::{DAYS, UNIQUE, RELAY_DAYS},
 	types::Balance,
 };
 
+#[cfg(all(not(feature = "unique-runtime"), not(feature = "quartz-runtime")))]
 parameter_types! {
 	pub const AppPromotionId: PalletId = PalletId(*b"appstake");
 	pub const RecalculationInterval: BlockNumber = 20;
-	pub const PendingInterval: BlockNumber = 10;
+	pub const PendingInterval: BlockNumber = 20;
 	pub const Nominal: Balance = UNIQUE;
 	pub const Day: BlockNumber = DAYS;
-	pub IntervalIncome: Perbill = Perbill::from_rational(RecalculationInterval::get(), 2 * DAYS) * Perbill::from_rational(5u32, 10_000);
+	pub IntervalIncome: Perbill = Perbill::from_rational(RecalculationInterval::get(), RELAY_DAYS) * Perbill::from_rational(5u32, 10_000);
+}
+
+#[cfg(any(feature = "unique-runtime", feature = "quartz-runtime"))]
+parameter_types! {
+	pub const AppPromotionId: PalletId = PalletId(*b"appstake");
+	pub const RecalculationInterval: BlockNumber = RELAY_DAYS;
+	pub const PendingInterval: BlockNumber = 7 * RELAY_DAYS;
+	pub const Nominal: Balance = UNIQUE;
+	pub const Day: BlockNumber = RELAY_DAYS;
+	pub IntervalIncome: Perbill = Perbill::from_rational(5u32, 10_000);
 }
 
 impl pallet_app_promotion::Config for Runtime {
 	type PalletId = AppPromotionId;
 	type CollectionHandler = Unique;
+	type ContractHandler = EvmContractHelpers;
 	type Currency = Balances;
 	type WeightInfo = pallet_app_promotion::weights::SubstrateWeight<Self>;
 	type TreasuryAccountId = TreasuryAccountId;
modifiedtests/src/app-promotion.test.tsdiffbeforeafterboth
--- a/tests/src/app-promotion.test.ts
+++ b/tests/src/app-promotion.test.ts
@@ -706,6 +706,12 @@
       nominal = helper.balance.getOneTokenNominal();
     });
   });
+
+  after(async function () {
+    await usingPlaygrounds(async (helper) => {
+      await helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.stopAppPromotion()));
+    });
+  });
   
   it('will credit 0.05% for staking period', async () => {
     // arrange: bob.stake(10000);
@@ -770,39 +776,24 @@
       const staker = await createUser(40n * nominal);
       
       await waitForRecalculationBlock(helper.api!);
-      // const foo = await helper.api!.registry.getChainProperties().
+      
 
       await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.fulfilled;
-      // await waitNewBlocks(helper.api!, 1);
+      
       await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.fulfilled;
-      // await waitNewBlocks(helper.api!, 1);
+      
       await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.fulfilled;
-      // console.log(await helper.balance.getSubstrate(staker.address));
-      // await waitNewBlocks(helper.api!, 17);
+      
       await waitForRelayBlock(helper.api!, 34);
       expect((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker)))
         .map(([_, amount]) => amount.toBigInt()))
         .to.be.deep.equal([calculateIncome(10n * nominal, 10n), calculateIncome(10n * nominal, 10n), calculateIncome(10n * nominal, 10n)]);
       
-      // console.log(await getBlockNumber(helper.api!));
-      // console.log((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker))).map(([block, amount]) => [block.toBigInt(), amount.toBigInt()]));
-      // console.log(`${calculateIncome(10n * nominal, 10n)} || ${calculateIncome(10n * nominal, 10n, 2)}`);
-      // await waitNewBlocks(helper.api!, 10);
       await waitForRelayBlock(helper.api!, 20);
-      // console.log((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker))).map(([_, amount]) => amount.toBigInt()));
-      // console.log(await helper.balance.getSubstrate(staker.address));
       await expect(helper.signTransaction(staker, helper.api!.tx.promotion.unstake(calculateIncome(10n * nominal, 10n, 2) - 10n * nominal))).to.be.eventually.fulfilled;
-      // console.log(calculateIncome(10n * nominal, 10n, 2));
-      // console.log(calculateIncome(10n * nominal, 10n, 3));
-      // console.log(calculateIncome(10n * nominal, 10n, 4));
-      // console.log(calculateIncome(10n * nominal, 10n, 5));
       expect((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker)))
         .map(([_, amount]) => amount.toBigInt()))
         .to.be.deep.equal([10n * nominal, calculateIncome(10n * nominal, 10n, 2), calculateIncome(10n * nominal, 10n, 2)]);
-      
-      // console.log((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker))).map(([_, amount]) => amount.toBigInt()));
-      
-      // console.log(await helper.balance.getSubstrate(staker.address));
     });
     
   });
modifiedtests/src/interfaces/augment-api-consts.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-consts.ts
+++ b/tests/src/interfaces/augment-api-consts.ts
@@ -78,7 +78,7 @@
        **/
       palletId: FrameSupportPalletId & AugmentedConst<ApiType>;
       /**
-       * In chain blocks.
+       * In relay blocks.
        **/
       pendingInterval: u32 & AugmentedConst<ApiType>;
       /**
modifiedtests/src/interfaces/augment-api-errors.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-errors.ts
+++ b/tests/src/interfaces/augment-api-errors.ts
@@ -430,11 +430,16 @@
       [key: string]: AugmentedError<ApiType>;
     };
     promotion: {
+      /**
+       * Error due to action requiring admin to be set
+       **/
       AdminNotSet: AugmentedError<ApiType>;
-      AlreadySponsored: AugmentedError<ApiType>;
+      /**
+       * An error related to the fact that an invalid argument was passed to perform an action
+       **/
       InvalidArgument: AugmentedError<ApiType>;
       /**
-       * No permission to perform action
+       * No permission to perform an action
        **/
       NoPermission: AugmentedError<ApiType>;
       /**
modifiedtests/src/interfaces/augment-api-query.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-query.ts
+++ b/tests/src/interfaces/augment-api-query.ts
@@ -525,7 +525,7 @@
        **/
       staked: AugmentedQuery<ApiType, (arg1: AccountId32 | string | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<u128>, [AccountId32, u32]> & QueryableStorageEntry<ApiType, [AccountId32, u32]>;
       /**
-       * A block when app-promotion has started
+       * A block when app-promotion has started .I think this is redundant, because we only need `NextInterestBlock`.
        **/
       startBlock: AugmentedQuery<ApiType, () => Observable<u32>, []> & QueryableStorageEntry<ApiType, []>;
       totalStaked: AugmentedQuery<ApiType, () => Observable<u128>, []> & QueryableStorageEntry<ApiType, []>;
modifiedtests/src/interfaces/augment-api-tx.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-tx.ts
+++ b/tests/src/interfaces/augment-api-tx.ts
@@ -365,9 +365,12 @@
     promotion: {
       setAdminAddress: AugmentedSubmittable<(admin: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [PalletEvmAccountBasicCrossAccountIdRepr]>;
       sponsorCollection: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32]>;
+      sponsorConract: AugmentedSubmittable<(contractId: H160 | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [H160]>;
       stake: AugmentedSubmittable<(amount: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u128]>;
       startAppPromotion: AugmentedSubmittable<(promotionStartRelayBlock: Option<u32> | null | Uint8Array | u32 | AnyNumber) => SubmittableExtrinsic<ApiType>, [Option<u32>]>;
+      stopAppPromotion: AugmentedSubmittable<() => SubmittableExtrinsic<ApiType>, []>;
       stopSponsorignCollection: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32]>;
+      stopSponsorignContract: AugmentedSubmittable<(contractId: H160 | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [H160]>;
       unstake: AugmentedSubmittable<(amount: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u128]>;
       /**
        * Generic tx
modifiedtests/src/interfaces/default/types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/default/types.ts
+++ b/tests/src/interfaces/default/types.ts
@@ -816,6 +816,7 @@
   readonly asStartAppPromotion: {
     readonly promotionStartRelayBlock: Option<u32>;
   } & Struct;
+  readonly isStopAppPromotion: boolean;
   readonly isStake: boolean;
   readonly asStake: {
     readonly amount: u128;
@@ -832,7 +833,15 @@
   readonly asStopSponsorignCollection: {
     readonly collectionId: u32;
   } & Struct;
-  readonly type: 'SetAdminAddress' | 'StartAppPromotion' | 'Stake' | 'Unstake' | 'SponsorCollection' | 'StopSponsorignCollection';
+  readonly isSponsorConract: boolean;
+  readonly asSponsorConract: {
+    readonly contractId: H160;
+  } & Struct;
+  readonly isStopSponsorignContract: boolean;
+  readonly asStopSponsorignContract: {
+    readonly contractId: H160;
+  } & Struct;
+  readonly type: 'SetAdminAddress' | 'StartAppPromotion' | 'StopAppPromotion' | 'Stake' | 'Unstake' | 'SponsorCollection' | 'StopSponsorignCollection' | 'SponsorConract' | 'StopSponsorignContract';
 }
 
 /** @name PalletAppPromotionError */
@@ -841,8 +850,7 @@
   readonly isNoPermission: boolean;
   readonly isNotSufficientFounds: boolean;
   readonly isInvalidArgument: boolean;
-  readonly isAlreadySponsored: boolean;
-  readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFounds' | 'InvalidArgument' | 'AlreadySponsored';
+  readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFounds' | 'InvalidArgument';
 }
 
 /** @name PalletAppPromotionEvent */
modifiedtests/src/interfaces/lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/lookup.ts
+++ b/tests/src/interfaces/lookup.ts
@@ -2463,6 +2463,7 @@
       start_app_promotion: {
         promotionStartRelayBlock: 'Option<u32>',
       },
+      stop_app_promotion: 'Null',
       stake: {
         amount: 'u128',
       },
@@ -2473,7 +2474,13 @@
         collectionId: 'u32',
       },
       stop_sponsorign_collection: {
-        collectionId: 'u32'
+        collectionId: 'u32',
+      },
+      sponsor_conract: {
+        contractId: 'H160',
+      },
+      stop_sponsorign_contract: {
+        contractId: 'H160'
       }
     }
   },
@@ -3098,7 +3105,7 @@
    * Lookup410: pallet_app_promotion::pallet::Error<T>
    **/
   PalletAppPromotionError: {
-    _enum: ['AdminNotSet', 'NoPermission', 'NotSufficientFounds', 'InvalidArgument', 'AlreadySponsored']
+    _enum: ['AdminNotSet', 'NoPermission', 'NotSufficientFounds', 'InvalidArgument']
   },
   /**
    * Lookup413: pallet_evm::pallet::Error<T>
modifiedtests/src/interfaces/types-lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/types-lookup.ts
+++ b/tests/src/interfaces/types-lookup.ts
@@ -2669,6 +2669,7 @@
     readonly asStartAppPromotion: {
       readonly promotionStartRelayBlock: Option<u32>;
     } & Struct;
+    readonly isStopAppPromotion: boolean;
     readonly isStake: boolean;
     readonly asStake: {
       readonly amount: u128;
@@ -2685,7 +2686,15 @@
     readonly asStopSponsorignCollection: {
       readonly collectionId: u32;
     } & Struct;
-    readonly type: 'SetAdminAddress' | 'StartAppPromotion' | 'Stake' | 'Unstake' | 'SponsorCollection' | 'StopSponsorignCollection';
+    readonly isSponsorConract: boolean;
+    readonly asSponsorConract: {
+      readonly contractId: H160;
+    } & Struct;
+    readonly isStopSponsorignContract: boolean;
+    readonly asStopSponsorignContract: {
+      readonly contractId: H160;
+    } & Struct;
+    readonly type: 'SetAdminAddress' | 'StartAppPromotion' | 'StopAppPromotion' | 'Stake' | 'Unstake' | 'SponsorCollection' | 'StopSponsorignCollection' | 'SponsorConract' | 'StopSponsorignContract';
   }
 
   /** @name PalletEvmCall (305) */
@@ -3288,8 +3297,7 @@
     readonly isNoPermission: boolean;
     readonly isNotSufficientFounds: boolean;
     readonly isInvalidArgument: boolean;
-    readonly isAlreadySponsored: boolean;
-    readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFounds' | 'InvalidArgument' | 'AlreadySponsored';
+    readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFounds' | 'InvalidArgument';
   }
 
   /** @name PalletEvmError (413) */