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
before · pallets/app-promotion/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//! # App promotion18//!19//! The app promotion pallet is designed to ... .20//!21//! ## Interface22//!23//! ### Dispatchable Functions24//!25//! * `start_inflation` - This method sets the inflation start date. Can be only called once.26//! Inflation start block can be backdated and will catch up. The method will create Treasury27//!	account if it does not exist and perform the first inflation deposit.2829// #![recursion_limit = "1024"]30#![cfg_attr(not(feature = "std"), no_std)]3132#[cfg(feature = "runtime-benchmarks")]33mod benchmarking;34#[cfg(test)]35mod tests;36pub mod types;37pub mod weights;3839use sp_std::{vec::Vec, iter::Sum, borrow::ToOwned};40use codec::EncodeLike;41use pallet_balances::BalanceLock;42pub use types::ExtendedLockableCurrency;4344// use up_common::constants::{DAYS, UNIQUE};45use up_data_structs::CollectionId;4647use frame_support::{48	dispatch::{DispatchResult},49	traits::{50		Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,51	},52	ensure,53};5455use weights::WeightInfo;5657pub use pallet::*;58use pallet_evm::account::CrossAccountId;59use sp_runtime::{60	Perbill,61	traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion},62	ArithmeticError,63};6465type BalanceOf<T> =66	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;6768// const SECONDS_TO_BLOCK: u32 = 6;69// const DAY: u32 = 60 * 60 * 24 / SECONDS_TO_BLOCK;70// const WEEK: u32 = 7 * DAY;71// const TWO_WEEK: u32 = 2 * WEEK;72// const YEAR: u32 = DAY * 365;7374pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";7576#[frame_support::pallet]77pub mod pallet {78	use super::*;79	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId};80	use frame_system::pallet_prelude::*;81	use types::CollectionHandler;8283	#[pallet::config]84	pub trait Config: frame_system::Config + pallet_evm::account::Config {85		type Currency: ExtendedLockableCurrency<Self::AccountId>;8687		type CollectionHandler: CollectionHandler<88			AccountId = Self::AccountId,89			CollectionId = CollectionId,90		>;9192		type TreasuryAccountId: Get<Self::AccountId>;9394		/// The app's pallet id, used for deriving its sovereign account ID.95		#[pallet::constant]96		type PalletId: Get<PalletId>;9798		/// In relay blocks.99		#[pallet::constant]100		type RecalculationInterval: Get<Self::BlockNumber>;101		/// In chain blocks.102		#[pallet::constant]103		type PendingInterval: Get<Self::BlockNumber>;104105		/// In chain blocks.106		#[pallet::constant]107		type Day: Get<Self::BlockNumber>; // useless108109		#[pallet::constant]110		type Nominal: Get<BalanceOf<Self>>;111112		#[pallet::constant]113		type IntervalIncome: Get<Perbill>;114115		/// Weight information for extrinsics in this pallet.116		type WeightInfo: WeightInfo;117118		// The relay block number provider119		type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;120121		/// Events compatible with [`frame_system::Config::Event`].122		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;123124		// /// Number of blocks that pass between treasury balance updates due to inflation125		// #[pallet::constant]126		// type InterestBlockInterval: Get<Self::BlockNumber>;127128		// // Weight information for functions of this pallet.129		// type WeightInfo: WeightInfo;130	}131132	#[pallet::pallet]133	#[pallet::generate_store(pub(super) trait Store)]134	pub struct Pallet<T>(_);135136	#[pallet::event]137	#[pallet::generate_deposit(fn deposit_event)]138	pub enum Event<T: Config> {139		StakingRecalculation(140			/// Base on which interest is calculated141			BalanceOf<T>,142			/// Amount of accrued interest143			BalanceOf<T>,144		),145	}146147	#[pallet::error]148	pub enum Error<T> {149		AdminNotSet,150		/// No permission to perform action151		NoPermission,152		/// Insufficient funds to perform an action153		NotSufficientFounds,154		InvalidArgument,155		AlreadySponsored,156	}157158	#[pallet::storage]159	pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;160161	#[pallet::storage]162	pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;163164	/// Amount of tokens staked by account in the blocknumber.165	#[pallet::storage]166	pub type Staked<T: Config> = StorageNMap<167		Key = (168			Key<Blake2_128Concat, T::AccountId>,169			Key<Twox64Concat, T::BlockNumber>,170		),171		Value = BalanceOf<T>,172		QueryKind = ValueQuery,173	>;174175	/// Amount of tokens pending unstake per user per block.176	#[pallet::storage]177	pub type PendingUnstake<T: Config> = StorageNMap<178		Key = (179			Key<Blake2_128Concat, T::AccountId>,180			Key<Twox64Concat, T::BlockNumber>,181		),182		Value = BalanceOf<T>,183		QueryKind = ValueQuery,184	>;185186	/// A block when app-promotion has started187	#[pallet::storage]188	pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;189190	/// Next target block when interest is recalculated191	#[pallet::storage]192	#[pallet::getter(fn get_interest_block)]193	pub type NextInterestBlock<T: Config> =194		StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;195196	#[pallet::hooks]197	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {198		fn on_initialize(current_block: T::BlockNumber) -> Weight199		where200			<T as frame_system::Config>::BlockNumber: From<u32>,201		{202			let mut consumed_weight = 0;203			let mut add_weight = |reads, writes, weight| {204				consumed_weight += T::DbWeight::get().reads_writes(reads, writes);205				consumed_weight += weight;206			};207208			PendingUnstake::<T>::iter()209				.filter_map(|((staker, block), amount)| {210					if block <= current_block {211						Some((staker, block, amount))212					} else {213						None214					}215				})216				.for_each(|(staker, block, amount)| {217					Self::unlock_balance_unchecked(&staker, amount); // TO-DO : Replace with a method that will check that the unstack is less than it was blocked, otherwise take the delta from the treasuries218					<PendingUnstake<T>>::remove((staker, block));219				});220221			let next_interest_block = Self::get_interest_block();222			let current_relay_block = T::RelayBlockNumberProvider::current_block_number();223			if next_interest_block != 0.into() && current_relay_block >= next_interest_block {224				let mut acc = <BalanceOf<T>>::default();225				let mut base_acc = <BalanceOf<T>>::default();226227				NextInterestBlock::<T>::set(228					NextInterestBlock::<T>::get() + T::RecalculationInterval::get(),229				);230				add_weight(0, 1, 0);231232				Staked::<T>::iter()233					.filter(|((_, block), _)| {234						*block + T::RecalculationInterval::get() <= current_relay_block235					})236					.for_each(|((staker, block), amount)| {237						Self::recalculate_stake(&staker, block, amount, &mut acc);238						add_weight(0, 0, T::WeightInfo::recalculate_stake());239						base_acc += amount;240					});241				<TotalStaked<T>>::get()242					.checked_add(&acc)243					.map(|res| <TotalStaked<T>>::set(res));244245				Self::deposit_event(Event::StakingRecalculation(base_acc, acc));246				add_weight(0, 1, 0);247			} else {248				add_weight(1, 0, 0)249			};250			consumed_weight251		}252	}253254	#[pallet::call]255	impl<T: Config> Pallet<T> {256		#[pallet::weight(T::WeightInfo::set_admin_address())]257		pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {258			ensure_root(origin)?;259			<Admin<T>>::set(Some(admin.as_sub().to_owned()));260261			Ok(())262		}263264		#[pallet::weight(T::WeightInfo::start_app_promotion())]265		pub fn start_app_promotion(266			origin: OriginFor<T>,267			promotion_start_relay_block: Option<T::BlockNumber>,268		) -> DispatchResult269		where270			<T as frame_system::Config>::BlockNumber: From<u32>,271		{272			ensure_root(origin)?;273274			// Start app-promotion mechanics if it has not been yet initialized275			if <StartBlock<T>>::get() == 0u32.into() {276				let start_block = promotion_start_relay_block277					.unwrap_or(T::RelayBlockNumberProvider::current_block_number());278279				// Set promotion global start block280				<StartBlock<T>>::set(start_block);281282				<NextInterestBlock<T>>::set(start_block + T::RecalculationInterval::get());283			}284285			Ok(())286		}287288		#[pallet::weight(T::WeightInfo::stake())]289		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {290			let staker_id = ensure_signed(staker)?;291292			ensure!(amount >= T::Nominal::get(), ArithmeticError::Underflow);293294			let balance =295				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);296297			ensure!(balance >= amount, ArithmeticError::Underflow);298299			<<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(300				&staker_id,301				amount,302				WithdrawReasons::all(),303				balance - amount,304			)?;305306			Self::add_lock_balance(&staker_id, amount)?;307308			let block_number = T::RelayBlockNumberProvider::current_block_number();309310			<Staked<T>>::insert(311				(&staker_id, block_number),312				<Staked<T>>::get((&staker_id, block_number))313					.checked_add(&amount)314					.ok_or(ArithmeticError::Overflow)?,315			);316317			<TotalStaked<T>>::set(318				<TotalStaked<T>>::get()319					.checked_add(&amount)320					.ok_or(ArithmeticError::Overflow)?,321			);322323			Ok(())324		}325326		#[pallet::weight(T::WeightInfo::unstake())]327		pub fn unstake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {328			let staker_id = ensure_signed(staker)?;329330			let mut stakes = Staked::<T>::iter_prefix((&staker_id,)).collect::<Vec<_>>();331332			let total_staked = stakes333				.iter()334				.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);335336			ensure!(total_staked >= amount, ArithmeticError::Underflow);337338			<TotalStaked<T>>::set(339				<TotalStaked<T>>::get()340					.checked_sub(&amount)341					.ok_or(ArithmeticError::Underflow)?,342			);343344			let block = frame_system::Pallet::<T>::block_number() + T::PendingInterval::get();345			<PendingUnstake<T>>::insert(346				(&staker_id, block),347				<PendingUnstake<T>>::get((&staker_id, block))348					.checked_add(&amount)349					.ok_or(ArithmeticError::Overflow)?,350			);351352			stakes.sort_by_key(|(block, _)| *block);353354			let mut acc_amount = amount;355			let new_state = stakes356				.into_iter()357				.map_while(|(block, balance_per_block)| {358					if acc_amount == <BalanceOf<T>>::default() {359						return None;360					}361					if acc_amount <= balance_per_block {362						let res = (block, balance_per_block - acc_amount, acc_amount);363						acc_amount = <BalanceOf<T>>::default();364						return Some(res);365					} else {366						acc_amount -= balance_per_block;367						return Some((block, <BalanceOf<T>>::default(), acc_amount));368					}369				})370				.collect::<Vec<_>>();371372			new_state373				.into_iter()374				.for_each(|(block, to_staked, _to_pending)| {375					if to_staked == <BalanceOf<T>>::default() {376						<Staked<T>>::remove((&staker_id, block));377					} else {378						<Staked<T>>::insert((&staker_id, block), to_staked);379					}380				});381382			Ok(())383		}384385		#[pallet::weight(0)]386		pub fn sponsor_collection(387			admin: OriginFor<T>,388			collection_id: CollectionId,389		) -> DispatchResult {390			let admin_id = ensure_signed(admin)?;391			ensure!(392				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,393				Error::<T>::NoPermission394			);395396			T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)397		}398		#[pallet::weight(0)]399		pub fn stop_sponsorign_collection(400			admin: OriginFor<T>,401			collection_id: CollectionId,402		) -> DispatchResult {403			let admin_id = ensure_signed(admin)?;404405			ensure!(406				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,407				Error::<T>::NoPermission408			);409410			ensure!(411				T::CollectionHandler::get_sponsor(collection_id)?412					.ok_or(<Error<T>>::InvalidArgument)?413					== Self::account_id(),414				<Error<T>>::NoPermission415			);416			T::CollectionHandler::remove_collection_sponsor(collection_id)417		}418	}419}420421impl<T: Config> Pallet<T> {422	// pub fn stake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {423	// 	let balance = <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(staker);424425	// 	ensure!(balance >= amount, ArithmeticError::Underflow);426427	// 	Self::set_lock_unchecked(staker, amount);428429	// 	let block_number = <T::BlockNumberProvider as BlockNumberProvider>::current_block_number();430431	// 	<Staked<T>>::insert(432	// 		(staker, block_number),433	// 		<Staked<T>>::get((staker, block_number))434	// 			.checked_add(&amount)435	// 			.ok_or(ArithmeticError::Overflow)?,436	// 	);437438	// 	<TotalStaked<T>>::set(439	// 		<TotalStaked<T>>::get()440	// 			.checked_add(&amount)441	// 			.ok_or(ArithmeticError::Overflow)?,442	// 	);443444	// 	Ok(())445	// }446447	// pub fn unstake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {448	// 	let mut stakes = Staked::<T>::iter_prefix((staker,)).collect::<Vec<_>>();449450	// 	let total_staked = stakes451	// 		.iter()452	// 		.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);453454	// 	ensure!(total_staked >= amount, ArithmeticError::Underflow);455456	// 	<TotalStaked<T>>::set(457	// 		<TotalStaked<T>>::get()458	// 			.checked_sub(&amount)459	// 			.ok_or(ArithmeticError::Underflow)?,460	// 	);461462	// 	let block = <T::BlockNumberProvider>::current_block_number() + WEEK.into();463	// 	<PendingUnstake<T>>::insert(464	// 		(staker, block),465	// 		<PendingUnstake<T>>::get((staker, block))466	// 			.checked_add(&amount)467	// 			.ok_or(ArithmeticError::Overflow)?,468	// 	);469470	// 	stakes.sort_by_key(|(block, _)| *block);471472	// 	let mut acc_amount = amount;473	// 	let new_state = stakes474	// 		.into_iter()475	// 		.map_while(|(block, balance_per_block)| {476	// 			if acc_amount == <BalanceOf<T>>::default() {477	// 				return None;478	// 			}479	// 			if acc_amount <= balance_per_block {480	// 				let res = (block, balance_per_block - acc_amount, acc_amount);481	// 				acc_amount = <BalanceOf<T>>::default();482	// 				return Some(res);483	// 			} else {484	// 				acc_amount -= balance_per_block;485	// 				return Some((block, <BalanceOf<T>>::default(), acc_amount));486	// 			}487	// 		})488	// 		.collect::<Vec<_>>();489490	// 	new_state491	// 		.into_iter()492	// 		.for_each(|(block, to_staked, _to_pending)| {493	// 			if to_staked == <BalanceOf<T>>::default() {494	// 				<Staked<T>>::remove((staker, block));495	// 			} else {496	// 				<Staked<T>>::insert((staker, block), to_staked);497	// 			}498	// 		});499500	// 	Ok(())501	// }502503	// pub fn sponsor_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {504	// 	Ok(())505	// }506507	// pub fn stop_sponsorign_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {508	// 	Ok(())509	// }510511	pub fn sponsor_conract(admin: T::AccountId, app_id: u32) -> DispatchResult {512		Ok(())513	}514515	pub fn stop_sponsorign_contract(admin: T::AccountId, app_id: u32) -> DispatchResult {516		Ok(())517	}518519	pub fn account_id() -> T::AccountId {520		T::PalletId::get().into_account_truncating()521	}522}523524impl<T: Config> Pallet<T> {525	fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {526		let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();527		locked_balance -= amount;528		Self::set_lock_unchecked(staker, locked_balance);529	}530531	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {532		Self::get_locked_balance(staker)533			.map_or(<BalanceOf<T>>::default(), |l| l.amount)534			.checked_add(&amount)535			.map(|new_lock| Self::set_lock_unchecked(staker, new_lock))536			.ok_or(ArithmeticError::Overflow.into())537	}538539	fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {540		<T::Currency as LockableCurrency<T::AccountId>>::set_lock(541			LOCK_IDENTIFIER,542			staker,543			amount,544			WithdrawReasons::all(),545		)546	}547548	pub fn get_locked_balance(549		staker: impl EncodeLike<T::AccountId>,550	) -> Option<BalanceLock<BalanceOf<T>>> {551		<T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)552			.into_iter()553			.find(|l| l.id == LOCK_IDENTIFIER)554	}555556	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {557		let staked = Staked::<T>::iter_prefix((staker,))558			.into_iter()559			.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + amount);560		if staked != <BalanceOf<T>>::default() {561			Some(staked)562		} else {563			None564		}565	}566567	pub fn total_staked_by_id_per_block(568		staker: impl EncodeLike<T::AccountId>,569	) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {570		let mut staked = Staked::<T>::iter_prefix((staker,))571			.into_iter()572			.map(|(block, amount)| (block, amount))573			.collect::<Vec<_>>();574		staked.sort_by_key(|(block, _)| *block);575		if !staked.is_empty() {576			Some(staked)577		} else {578			None579		}580	}581582	pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {583		staker.map_or(Some(<TotalStaked<T>>::get()), |s| {584			Self::total_staked_by_id(s.as_sub())585		})586		// Self::total_staked_by_id(staker.as_sub())587	}588589	pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {590		Self::get_locked_balance(staker.as_sub())591			.map(|l| l.amount)592			.unwrap_or_default()593	}594595	pub fn cross_id_total_staked_per_block(596		staker: T::CrossAccountId,597	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {598		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()599	}600601	fn recalculate_stake(602		staker: &T::AccountId,603		block: T::BlockNumber,604		base: BalanceOf<T>,605		income_acc: &mut BalanceOf<T>,606	) {607		let income = Self::calculate_income(base);608		base.checked_add(&income).map(|res| {609			<Staked<T>>::insert((staker, block), res);610			*income_acc += income;611			<T::Currency as Currency<T::AccountId>>::transfer(612				&T::TreasuryAccountId::get(),613				staker,614				income,615				ExistenceRequirement::KeepAlive,616			)617			.and_then(|_| Self::add_lock_balance(staker, income));618		});619	}620621	fn calculate_income<I>(base: I) -> I622	where623		I: EncodeLike<BalanceOf<T>> + Balance,624	{625		T::IntervalIncome::get() * base626	}627}628629impl<T: Config> Pallet<T>630where631	<<T as pallet::Config>::Currency as Currency<T::AccountId>>::Balance: Sum,632{633	pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {634		staker.map_or(PendingUnstake::<T>::iter_values().sum(), |s| {635			PendingUnstake::<T>::iter_prefix_values((s.as_sub(),)).sum()636		})637	}638639	pub fn cross_id_pending_unstake_per_block(640		staker: T::CrossAccountId,641	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {642		let mut unsorted_res = PendingUnstake::<T>::iter_prefix((staker.as_sub(),))643			.into_iter()644			.collect::<Vec<_>>();645		unsorted_res.sort_by_key(|(block, _)| *block);646		unsorted_res647	}648}
after · pallets/app-promotion/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//! # App promotion18//!19//! The app promotion pallet is designed to ... .20//!21//! ## Interface22//!23//! ### Dispatchable Functions24//!25//! * `start_inflation` - This method sets the inflation start date. Can be only called once.26//! Inflation start block can be backdated and will catch up. The method will create Treasury27//!	account if it does not exist and perform the first inflation deposit.2829// #![recursion_limit = "1024"]30#![cfg_attr(not(feature = "std"), no_std)]3132#[cfg(feature = "runtime-benchmarks")]33mod benchmarking;34#[cfg(test)]35mod tests;36pub mod types;37pub mod weights;3839use sp_std::{vec::Vec, iter::Sum, borrow::ToOwned};40use sp_core::H160;41use codec::EncodeLike;42use pallet_balances::BalanceLock;43pub use types::*;4445// use up_common::constants::{DAYS, UNIQUE};46use up_data_structs::CollectionId;4748use frame_support::{49	dispatch::{DispatchResult},50	traits::{51		Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,52	},53	ensure,54};5556use weights::WeightInfo;5758pub use pallet::*;59use pallet_evm::account::CrossAccountId;60use sp_runtime::{61	Perbill,62	traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion},63	ArithmeticError,64};6566type BalanceOf<T> =67	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;6869// const SECONDS_TO_BLOCK: u32 = 6;70// const DAY: u32 = 60 * 60 * 24 / SECONDS_TO_BLOCK;71// const WEEK: u32 = 7 * DAY;72// const TWO_WEEK: u32 = 2 * WEEK;73// const YEAR: u32 = DAY * 365;7475pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";7677#[frame_support::pallet]78pub mod pallet {79	use super::*;80	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId};81	use frame_system::pallet_prelude::*;8283	#[pallet::config]84	pub trait Config: frame_system::Config + pallet_evm::account::Config {85		type Currency: ExtendedLockableCurrency<Self::AccountId>;8687		type CollectionHandler: CollectionHandler<88			AccountId = Self::AccountId,89			CollectionId = CollectionId,90		>;9192		type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;9394		type TreasuryAccountId: Get<Self::AccountId>;9596		/// The app's pallet id, used for deriving its sovereign account ID.97		#[pallet::constant]98		type PalletId: Get<PalletId>;99100		/// In relay blocks.101		#[pallet::constant]102		type RecalculationInterval: Get<Self::BlockNumber>;103		/// In relay blocks.104		#[pallet::constant]105		type PendingInterval: Get<Self::BlockNumber>;106107		/// In chain blocks.108		#[pallet::constant]109		type Day: Get<Self::BlockNumber>; // useless110111		#[pallet::constant]112		type Nominal: Get<BalanceOf<Self>>;113114		#[pallet::constant]115		type IntervalIncome: Get<Perbill>;116117		/// Weight information for extrinsics in this pallet.118		type WeightInfo: WeightInfo;119120		// The relay block number provider121		type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;122123		/// Events compatible with [`frame_system::Config::Event`].124		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;125	}126127	#[pallet::pallet]128	#[pallet::generate_store(pub(super) trait Store)]129	pub struct Pallet<T>(_);130131	#[pallet::event]132	#[pallet::generate_deposit(fn deposit_event)]133	pub enum Event<T: Config> {134		StakingRecalculation(135			/// Base on which interest is calculated136			BalanceOf<T>,137			/// Amount of accrued interest138			BalanceOf<T>,139		),140	}141142	#[pallet::error]143	pub enum Error<T> {144		/// Error due to action requiring admin to be set145		AdminNotSet,146		/// No permission to perform an action147		NoPermission,148		/// Insufficient funds to perform an action149		NotSufficientFounds,150		/// An error related to the fact that an invalid argument was passed to perform an action151		InvalidArgument,152	}153154	#[pallet::storage]155	pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;156157	#[pallet::storage]158	pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;159160	/// Amount of tokens staked by account in the blocknumber.161	#[pallet::storage]162	pub type Staked<T: Config> = StorageNMap<163		Key = (164			Key<Blake2_128Concat, T::AccountId>,165			Key<Twox64Concat, T::BlockNumber>,166		),167		Value = BalanceOf<T>,168		QueryKind = ValueQuery,169	>;170171	/// Amount of tokens pending unstake per user per block.172	#[pallet::storage]173	pub type PendingUnstake<T: Config> = StorageNMap<174		Key = (175			Key<Blake2_128Concat, T::AccountId>,176			Key<Twox64Concat, T::BlockNumber>,177		),178		Value = BalanceOf<T>,179		QueryKind = ValueQuery,180	>;181182	/// A block when app-promotion has started .I think this is redundant, because we only need `NextInterestBlock`.183	#[pallet::storage]184	pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;185186	/// Next target block when interest is recalculated187	#[pallet::storage]188	#[pallet::getter(fn get_interest_block)]189	pub type NextInterestBlock<T: Config> =190		StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;191192	#[pallet::hooks]193	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {194		fn on_initialize(current_block: T::BlockNumber) -> Weight195		where196			<T as frame_system::Config>::BlockNumber: From<u32>,197		{198			let mut consumed_weight = 0;199			let mut add_weight = |reads, writes, weight| {200				consumed_weight += T::DbWeight::get().reads_writes(reads, writes);201				consumed_weight += weight;202			};203204			PendingUnstake::<T>::iter()205				.filter_map(|((staker, block), amount)| {206					if block <= current_block {207						Some((staker, block, amount))208					} else {209						None210					}211				})212				.for_each(|(staker, block, amount)| {213					Self::unlock_balance_unchecked(&staker, amount); // TO-DO : Replace with a method that will check that the unstack is less than it was blocked, otherwise take the delta from the treasuries214					<PendingUnstake<T>>::remove((staker, block));215				});216217			let next_interest_block = Self::get_interest_block();218			let current_relay_block = T::RelayBlockNumberProvider::current_block_number();219			if next_interest_block != 0.into() && current_relay_block >= next_interest_block {220				let mut acc = <BalanceOf<T>>::default();221				let mut base_acc = <BalanceOf<T>>::default();222223				NextInterestBlock::<T>::set(224					NextInterestBlock::<T>::get() + T::RecalculationInterval::get(),225				);226				add_weight(0, 1, 0);227228				Staked::<T>::iter()229					.filter(|((_, block), _)| {230						*block + T::RecalculationInterval::get() <= current_relay_block231					})232					.for_each(|((staker, block), amount)| {233						Self::recalculate_stake(&staker, block, amount, &mut acc);234						add_weight(0, 0, T::WeightInfo::recalculate_stake());235						base_acc += amount;236					});237				<TotalStaked<T>>::get()238					.checked_add(&acc)239					.map(|res| <TotalStaked<T>>::set(res));240241				Self::deposit_event(Event::StakingRecalculation(base_acc, acc));242				add_weight(0, 1, 0);243			} else {244				add_weight(1, 0, 0)245			};246			consumed_weight247		}248	}249250	#[pallet::call]251	impl<T: Config> Pallet<T> {252		#[pallet::weight(T::WeightInfo::set_admin_address())]253		pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {254			ensure_root(origin)?;255			<Admin<T>>::set(Some(admin.as_sub().to_owned()));256257			Ok(())258		}259260		#[pallet::weight(T::WeightInfo::start_app_promotion())]261		pub fn start_app_promotion(262			origin: OriginFor<T>,263			promotion_start_relay_block: Option<T::BlockNumber>,264		) -> DispatchResult265		where266			<T as frame_system::Config>::BlockNumber: From<u32>,267		{268			ensure_root(origin)?;269270			// Start app-promotion mechanics if it has not been yet initialized271			if <StartBlock<T>>::get() == 0u32.into() {272				let start_block = promotion_start_relay_block273					.unwrap_or(T::RelayBlockNumberProvider::current_block_number());274275				// Set promotion global start block276				<StartBlock<T>>::set(start_block);277278				<NextInterestBlock<T>>::set(start_block + T::RecalculationInterval::get());279			}280281			Ok(())282		}283284		#[pallet::weight(0)]285		pub fn stop_app_promotion(origin: OriginFor<T>) -> DispatchResult286		where287			<T as frame_system::Config>::BlockNumber: From<u32>,288		{289			ensure_root(origin)?;290291			if <StartBlock<T>>::get() != 0u32.into() {292				<StartBlock<T>>::set(T::BlockNumber::default());293				<NextInterestBlock<T>>::set(T::BlockNumber::default());294			}295296			Ok(())297		}298299		#[pallet::weight(T::WeightInfo::stake())]300		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {301			let staker_id = ensure_signed(staker)?;302303			ensure!(amount >= T::Nominal::get(), ArithmeticError::Underflow);304305			let balance =306				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);307308			ensure!(balance >= amount, ArithmeticError::Underflow);309310			<<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(311				&staker_id,312				amount,313				WithdrawReasons::all(),314				balance - amount,315			)?;316317			Self::add_lock_balance(&staker_id, amount)?;318319			let block_number = T::RelayBlockNumberProvider::current_block_number();320321			<Staked<T>>::insert(322				(&staker_id, block_number),323				<Staked<T>>::get((&staker_id, block_number))324					.checked_add(&amount)325					.ok_or(ArithmeticError::Overflow)?,326			);327328			<TotalStaked<T>>::set(329				<TotalStaked<T>>::get()330					.checked_add(&amount)331					.ok_or(ArithmeticError::Overflow)?,332			);333334			Ok(())335		}336337		#[pallet::weight(T::WeightInfo::unstake())]338		pub fn unstake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {339			let staker_id = ensure_signed(staker)?;340341			let mut stakes = Staked::<T>::iter_prefix((&staker_id,)).collect::<Vec<_>>();342343			let total_staked = stakes344				.iter()345				.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);346347			ensure!(total_staked >= amount, ArithmeticError::Underflow);348349			<TotalStaked<T>>::set(350				<TotalStaked<T>>::get()351					.checked_sub(&amount)352					.ok_or(ArithmeticError::Underflow)?,353			);354355			let block =356				T::RelayBlockNumberProvider::current_block_number() + T::PendingInterval::get();357			<PendingUnstake<T>>::insert(358				(&staker_id, block),359				<PendingUnstake<T>>::get((&staker_id, block))360					.checked_add(&amount)361					.ok_or(ArithmeticError::Overflow)?,362			);363364			stakes.sort_by_key(|(block, _)| *block);365366			let mut acc_amount = amount;367			let new_state = stakes368				.into_iter()369				.map_while(|(block, balance_per_block)| {370					if acc_amount == <BalanceOf<T>>::default() {371						return None;372					}373					if acc_amount <= balance_per_block {374						let res = (block, balance_per_block - acc_amount, acc_amount);375						acc_amount = <BalanceOf<T>>::default();376						return Some(res);377					} else {378						acc_amount -= balance_per_block;379						return Some((block, <BalanceOf<T>>::default(), acc_amount));380					}381				})382				.collect::<Vec<_>>();383384			new_state385				.into_iter()386				.for_each(|(block, to_staked, _to_pending)| {387					if to_staked == <BalanceOf<T>>::default() {388						<Staked<T>>::remove((&staker_id, block));389					} else {390						<Staked<T>>::insert((&staker_id, block), to_staked);391					}392				});393394			Ok(())395		}396397		#[pallet::weight(0)]398		pub fn sponsor_collection(399			admin: OriginFor<T>,400			collection_id: CollectionId,401		) -> DispatchResult {402			let admin_id = ensure_signed(admin)?;403			ensure!(404				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,405				Error::<T>::NoPermission406			);407408			T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)409		}410		#[pallet::weight(0)]411		pub fn stop_sponsorign_collection(412			admin: OriginFor<T>,413			collection_id: CollectionId,414		) -> DispatchResult {415			let admin_id = ensure_signed(admin)?;416417			ensure!(418				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,419				Error::<T>::NoPermission420			);421422			ensure!(423				T::CollectionHandler::get_sponsor(collection_id)?424					.ok_or(<Error<T>>::InvalidArgument)?425					== Self::account_id(),426				<Error<T>>::NoPermission427			);428			T::CollectionHandler::remove_collection_sponsor(collection_id)429		}430431		#[pallet::weight(0)]432		pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {433			let admin_id = ensure_signed(admin)?;434435			ensure!(436				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,437				Error::<T>::NoPermission438			);439440			T::ContractHandler::set_sponsor(441				T::CrossAccountId::from_sub(Self::account_id()),442				contract_id,443			)444		}445446		#[pallet::weight(0)]447		pub fn stop_sponsorign_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {448			let admin_id = ensure_signed(admin)?;449450			ensure!(451				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,452				Error::<T>::NoPermission453			);454455			ensure!(456				T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?457					== T::CrossAccountId::from_sub(Self::account_id()),458				<Error<T>>::NoPermission459			);460			T::ContractHandler::remove_contract_sponsor(contract_id)461		}462	}463}464465impl<T: Config> Pallet<T> {466	pub fn account_id() -> T::AccountId {467		T::PalletId::get().into_account_truncating()468	}469470	fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {471		let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();472		locked_balance -= amount;473		Self::set_lock_unchecked(staker, locked_balance);474	}475476	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {477		Self::get_locked_balance(staker)478			.map_or(<BalanceOf<T>>::default(), |l| l.amount)479			.checked_add(&amount)480			.map(|new_lock| Self::set_lock_unchecked(staker, new_lock))481			.ok_or(ArithmeticError::Overflow.into())482	}483484	fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {485		<T::Currency as LockableCurrency<T::AccountId>>::set_lock(486			LOCK_IDENTIFIER,487			staker,488			amount,489			WithdrawReasons::all(),490		)491	}492493	pub fn get_locked_balance(494		staker: impl EncodeLike<T::AccountId>,495	) -> Option<BalanceLock<BalanceOf<T>>> {496		<T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)497			.into_iter()498			.find(|l| l.id == LOCK_IDENTIFIER)499	}500501	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {502		let staked = Staked::<T>::iter_prefix((staker,))503			.into_iter()504			.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + amount);505		if staked != <BalanceOf<T>>::default() {506			Some(staked)507		} else {508			None509		}510	}511512	pub fn total_staked_by_id_per_block(513		staker: impl EncodeLike<T::AccountId>,514	) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {515		let mut staked = Staked::<T>::iter_prefix((staker,))516			.into_iter()517			.map(|(block, amount)| (block, amount))518			.collect::<Vec<_>>();519		staked.sort_by_key(|(block, _)| *block);520		if !staked.is_empty() {521			Some(staked)522		} else {523			None524		}525	}526527	pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {528		staker.map_or(Some(<TotalStaked<T>>::get()), |s| {529			Self::total_staked_by_id(s.as_sub())530		})531		// Self::total_staked_by_id(staker.as_sub())532	}533534	pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {535		Self::get_locked_balance(staker.as_sub())536			.map(|l| l.amount)537			.unwrap_or_default()538	}539540	pub fn cross_id_total_staked_per_block(541		staker: T::CrossAccountId,542	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {543		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()544	}545546	fn recalculate_stake(547		staker: &T::AccountId,548		block: T::BlockNumber,549		base: BalanceOf<T>,550		income_acc: &mut BalanceOf<T>,551	) {552		let income = Self::calculate_income(base);553		base.checked_add(&income).map(|res| {554			<Staked<T>>::insert((staker, block), res);555			*income_acc += income;556			<T::Currency as Currency<T::AccountId>>::transfer(557				&T::TreasuryAccountId::get(),558				staker,559				income,560				ExistenceRequirement::KeepAlive,561			)562			.and_then(|_| Self::add_lock_balance(staker, income));563		});564	}565566	fn calculate_income<I>(base: I) -> I567	where568		I: EncodeLike<BalanceOf<T>> + Balance,569	{570		T::IntervalIncome::get() * base571	}572}573574impl<T: Config> Pallet<T>575where576	<<T as pallet::Config>::Currency as Currency<T::AccountId>>::Balance: Sum,577{578	pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {579		staker.map_or(PendingUnstake::<T>::iter_values().sum(), |s| {580			PendingUnstake::<T>::iter_prefix_values((s.as_sub(),)).sum()581		})582	}583584	pub fn cross_id_pending_unstake_per_block(585		staker: T::CrossAccountId,586	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {587		let mut unsorted_res = PendingUnstake::<T>::iter_prefix((staker.as_sub(),))588			.into_iter()589			.collect::<Vec<_>>();590		unsorted_res.sort_by_key(|(block, _)| *block);591		unsorted_res592	}593}
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
--- a/pallets/evm-contract-helpers/src/lib.rs
+++ b/pallets/evm-contract-helpers/src/lib.rs
@@ -75,7 +75,7 @@
 	/// * **Key** - contract address.
 	/// * **Value** - sponsorship state.
 	#[pallet::storage]
-	pub(super) type Sponsoring<T: Config> = StorageMap<
+	pub type Sponsoring<T: Config> = StorageMap<
 		Hasher = Twox64Concat,
 		Key = H160,
 		Value = SponsorshipState<T::CrossAccountId>,
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) */