git.delta.rocks / unique-network / refs/commits / 834c75ed844b

difftreelog

change unstake and on_initrialize logicc and + added `Reserved`

PraetorP2022-09-02parent: #492b651.patch.diff
in: master

7 files changed

modifiedclient/rpc/src/lib.rsdiffbeforeafterboth
--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -250,14 +250,17 @@
 
 mod app_promotion_unique_rpc {
 	use super::*;
-	
+
 	#[rpc(server)]
 	#[async_trait]
 	pub trait AppPromotionApi<BlockHash, BlockNumber, CrossAccountId, AccountId> {
 		/// Returns the total amount of staked tokens.
 		#[method(name = "appPromotion_totalStaked")]
-		fn total_staked(&self, staker: Option<CrossAccountId>, at: Option<BlockHash>)
-			-> Result<String>;
+		fn total_staked(
+			&self,
+			staker: Option<CrossAccountId>,
+			at: Option<BlockHash>,
+		) -> Result<String>;
 
 		///Returns the total amount of staked tokens per block when staked.
 		#[method(name = "appPromotion_totalStakedPerBlock")]
@@ -269,8 +272,11 @@
 
 		/// Returns the total amount locked by staking tokens.
 		#[method(name = "appPromotion_totalStakingLocked")]
-		fn total_staking_locked(&self, staker: CrossAccountId, at: Option<BlockHash>)
-			-> Result<String>;
+		fn total_staking_locked(
+			&self,
+			staker: CrossAccountId,
+			at: Option<BlockHash>,
+		) -> Result<String>;
 
 		/// Returns the total amount of tokens pending withdrawal from staking.
 		#[method(name = "appPromotion_pendingUnstake")]
@@ -590,8 +596,12 @@
 }
 
 impl<C, Block, BlockNumber, CrossAccountId, AccountId>
- 	app_promotion_unique_rpc::AppPromotionApiServer<<Block as BlockT>::Hash, BlockNumber, CrossAccountId, AccountId>
-	for AppPromotion<C, Block>
+	app_promotion_unique_rpc::AppPromotionApiServer<
+		<Block as BlockT>::Hash,
+		BlockNumber,
+		CrossAccountId,
+		AccountId,
+	> for AppPromotion<C, Block>
 where
 	Block: BlockT,
 	BlockNumber: Decode + Member + AtLeast32BitUnsigned,
modifiednode/rpc/src/lib.rsdiffbeforeafterboth
--- a/node/rpc/src/lib.rs
+++ b/node/rpc/src/lib.rs
@@ -148,7 +148,12 @@
 	C::Api: fp_rpc::ConvertTransactionRuntimeApi<Block>,
 	C::Api:
 		up_rpc::UniqueApi<Block, BlockNumber, <R as RuntimeInstance>::CrossAccountId, AccountId>,
-	C::Api: app_promotion_rpc::AppPromotionApi<Block, BlockNumber, <R as RuntimeInstance>::CrossAccountId, AccountId>,
+	C::Api: app_promotion_rpc::AppPromotionApi<
+		Block,
+		BlockNumber,
+		<R as RuntimeInstance>::CrossAccountId,
+		AccountId,
+	>,
 	C::Api: rmrk_rpc::RmrkApi<
 		Block,
 		AccountId,
modifiedpallets/app-promotion/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/app-promotion/src/benchmarking.rs
+++ b/pallets/app-promotion/src/benchmarking.rs
@@ -39,13 +39,13 @@
 		T::BlockNumber: From<u32> + Into<u32>,
 		<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>
 	}
-	start_app_promotion {
+	// start_app_promotion {
 
-	} : {PromototionPallet::<T>::start_app_promotion(RawOrigin::Root.into(), None)?}
+	// } : {PromototionPallet::<T>::start_app_promotion(RawOrigin::Root.into(), None)?}
 
-	stop_app_promotion{
-		PromototionPallet::<T>::start_app_promotion(RawOrigin::Root.into(), Some(25.into()))?;
-	} : {PromototionPallet::<T>::stop_app_promotion(RawOrigin::Root.into())?}
+	// stop_app_promotion{
+	// 	PromototionPallet::<T>::start_app_promotion(RawOrigin::Root.into(), Some(25.into()))?;
+	// } : {PromototionPallet::<T>::stop_app_promotion(RawOrigin::Root.into())?}
 
 	set_admin_address {
 		let pallet_admin = account::<T::AccountId>("admin", 0, SEED);
@@ -58,6 +58,10 @@
 		PromototionPallet::<T>::set_admin_address(RawOrigin::Root.into(), T::CrossAccountId::from_sub(pallet_admin.clone()))?;
 		let _ = <T as Config>::Currency::make_free_balance_be(&pallet_admin,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
 		let staker: T::AccountId = account("caller", 0, SEED);
+		let stakers: Vec<T::AccountId> = (0..100).map(|index| account("staker", index, SEED)).collect();
+		stakers.iter().for_each(|staker| {
+			<T as Config>::Currency::make_free_balance_be(&staker,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
+		});
 		let _ = <T as Config>::Currency::make_free_balance_be(&staker,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
 		let _ = PromototionPallet::<T>::stake(RawOrigin::Signed(staker.clone()).into(), share * <T as Config>::Currency::total_balance(&staker))?;
 	} : {PromototionPallet::<T>::payout_stakers(RawOrigin::Signed(pallet_admin.clone()).into(), Some(1))?}
@@ -70,9 +74,9 @@
 
 	unstake {
 		let caller = account::<T::AccountId>("caller", 0, SEED);
-		let share = Perbill::from_rational(1u32, 10);
+		let share = Perbill::from_rational(1u32, 20);
 		let _ = <T as Config>::Currency::make_free_balance_be(&caller,  Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
-		let _ = PromototionPallet::<T>::stake(RawOrigin::Signed(caller.clone()).into(), share * <T as Config>::Currency::total_balance(&caller))?;
+		(0..10).map(|_| PromototionPallet::<T>::stake(RawOrigin::Signed(caller.clone()).into(), share * <T as Config>::Currency::total_balance(&caller))).collect::<Result<Vec<_>, _>>()?;
 
 	} : {PromototionPallet::<T>::unstake(RawOrigin::Signed(caller.clone()).into())?}
 
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 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, Zero},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			/// An recalculated staker136			T::AccountId,137			/// Base on which interest is calculated138			BalanceOf<T>,139			/// Amount of accrued interest140			BalanceOf<T>,141		),142	}143144	#[pallet::error]145	pub enum Error<T> {146		/// Error due to action requiring admin to be set147		AdminNotSet,148		/// No permission to perform an action149		NoPermission,150		/// Insufficient funds to perform an action151		NotSufficientFounds,152		/// An error related to the fact that an invalid argument was passed to perform an action153		InvalidArgument,154	}155156	#[pallet::storage]157	pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;158159	#[pallet::storage]160	pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;161162	/// Amount of tokens staked by account in the blocknumber.163	#[pallet::storage]164	pub type Staked<T: Config> = StorageNMap<165		Key = (166			Key<Blake2_128Concat, T::AccountId>,167			Key<Twox64Concat, T::BlockNumber>,168		),169		Value = (BalanceOf<T>, T::BlockNumber),170		QueryKind = ValueQuery,171	>;172	/// Amount of stakes for an Account173	#[pallet::storage]174	pub type StakesPerAccount<T: Config> =175		StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;176177	/// Amount of tokens pending unstake per user per block.178	#[pallet::storage]179	pub type PendingUnstake<T: Config> = StorageNMap<180		Key = (181			Key<Blake2_128Concat, T::AccountId>,182			Key<Twox64Concat, T::BlockNumber>,183		),184		Value = BalanceOf<T>,185		QueryKind = ValueQuery,186	>;187188	/// A block when app-promotion has started .I think this is redundant, because we only need `NextInterestBlock`.189	#[pallet::storage]190	pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;191192	/// Next target block when interest is recalculated193	#[pallet::storage]194	#[pallet::getter(fn get_interest_block)]195	pub type NextInterestBlock<T: Config> =196		StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;197198	/// Stores hash a record for which the last revenue recalculation was performed.199	/// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.200	#[pallet::storage]201	#[pallet::getter(fn get_next_calculated_record)]202	pub type NextCalculatedRecord<T: Config> =203		StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;204205	#[pallet::hooks]206	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {207		fn on_initialize(current_block: T::BlockNumber) -> Weight208		where209			<T as frame_system::Config>::BlockNumber: From<u32>,210		{211			let mut consumed_weight = 0;212			// let mut add_weight = |reads, writes, weight| {213			// 	consumed_weight += T::DbWeight::get().reads_writes(reads, writes);214			// 	consumed_weight += weight;215			// };216217			let current_relay_block = T::RelayBlockNumberProvider::current_block_number();218			PendingUnstake::<T>::iter()219				.filter_map(|((staker, block), amount)| {220					if block <= current_relay_block {221						Some((staker, block, amount))222					} else {223						None224					}225				})226				.for_each(|(staker, block, amount)| {227					Self::unlock_balance_unchecked(&staker, amount);228					<PendingUnstake<T>>::remove((staker, block));229				});230231			// let next_interest_block = Self::get_interest_block();232			// let current_relay_block = T::RelayBlockNumberProvider::current_block_number();233			// if next_interest_block != 0.into() && current_relay_block >= next_interest_block {234			// 	let mut acc = <BalanceOf<T>>::default();235			// 	let mut base_acc = <BalanceOf<T>>::default();236237			// 	NextInterestBlock::<T>::set(238			// 		NextInterestBlock::<T>::get() + T::RecalculationInterval::get(),239			// 	);240			// 	add_weight(0, 1, 0);241242			// 	Staked::<T>::iter()243			// 		.filter(|((_, block), _)| {244			// 			*block + T::RecalculationInterval::get() <= current_relay_block245			// 		})246			// 		.for_each(|((staker, block), amount)| {247			// 			Self::recalculate_stake(&staker, block, amount, &mut acc);248			// 			add_weight(0, 0, T::WeightInfo::recalculate_stake());249			// 			base_acc += amount;250			// 		});251			// 	<TotalStaked<T>>::get()252			// 		.checked_add(&acc)253			// 		.map(|res| <TotalStaked<T>>::set(res));254255			// 	Self::deposit_event(Event::StakingRecalculation(base_acc, acc));256			// 	add_weight(0, 1, 0);257			// } else {258			// 	add_weight(1, 0, 0)259			// };260			consumed_weight261		}262	}263264	#[pallet::call]265	impl<T: Config> Pallet<T>266	where267		T::BlockNumber: From<u32> + Into<u32>,268		<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,269	{270		#[pallet::weight(T::WeightInfo::set_admin_address())]271		pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {272			ensure_root(origin)?;273			<Admin<T>>::set(Some(admin.as_sub().to_owned()));274275			Ok(())276		}277278		#[pallet::weight(T::WeightInfo::start_app_promotion())]279		pub fn start_app_promotion(280			origin: OriginFor<T>,281			promotion_start_relay_block: Option<T::BlockNumber>,282		) -> DispatchResult283		where284			<T as frame_system::Config>::BlockNumber: From<u32>,285		{286			ensure_root(origin)?;287288			// Start app-promotion mechanics if it has not been yet initialized289			if <StartBlock<T>>::get() == 0u32.into() {290				let start_block = promotion_start_relay_block291					.unwrap_or(T::RelayBlockNumberProvider::current_block_number());292293				// Set promotion global start block294				<StartBlock<T>>::set(start_block);295296				<NextInterestBlock<T>>::set(start_block + T::RecalculationInterval::get());297			}298299			Ok(())300		}301302		#[pallet::weight(T::WeightInfo::stop_app_promotion())]303		pub fn stop_app_promotion(origin: OriginFor<T>) -> DispatchResult304		where305			<T as frame_system::Config>::BlockNumber: From<u32>,306		{307			ensure_root(origin)?;308309			if <StartBlock<T>>::get() != 0u32.into() {310				<StartBlock<T>>::set(T::BlockNumber::default());311				<NextInterestBlock<T>>::set(T::BlockNumber::default());312			}313314			Ok(())315		}316317		#[pallet::weight(T::WeightInfo::stake())]318		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {319			let staker_id = ensure_signed(staker)?;320321			ensure!(322				StakesPerAccount::<T>::get(&staker_id) < 10,323				Error::<T>::NoPermission324			);325326			ensure!(327				amount >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(),328				ArithmeticError::Underflow329			);330331			let balance =332				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);333334			ensure!(balance >= amount, ArithmeticError::Underflow);335336			<<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(337				&staker_id,338				amount,339				WithdrawReasons::all(),340				balance - amount,341			)?;342343			Self::add_lock_balance(&staker_id, amount)?;344345			let block_number = T::RelayBlockNumberProvider::current_block_number();346			let recalc_block = (block_number / T::RecalculationInterval::get() + 2u32.into())347				* T::RecalculationInterval::get();348349			<Staked<T>>::insert((&staker_id, block_number), {350				let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));351				balance_and_recalc_block.0 = balance_and_recalc_block352					.0353					.checked_add(&amount)354					.ok_or(ArithmeticError::Overflow)?;355				balance_and_recalc_block.1 = recalc_block;356				balance_and_recalc_block357			});358359			<TotalStaked<T>>::set(360				<TotalStaked<T>>::get()361					.checked_add(&amount)362					.ok_or(ArithmeticError::Overflow)?,363			);364365			StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);366			Ok(())367		}368369		#[pallet::weight(T::WeightInfo::unstake())]370		pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {371			let staker_id = ensure_signed(staker)?;372373			let mut total_stakes = 0u64;374375			let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))376				.map(|(_, (amount, _))| {377					total_stakes += 1;378					amount379				})380				.sum();381382			if total_staked.is_zero() {383				return Ok(None.into());384			}385			let block =386				T::RelayBlockNumberProvider::current_block_number() + T::PendingInterval::get();387			<PendingUnstake<T>>::insert(388				(&staker_id, block),389				<PendingUnstake<T>>::get((&staker_id, block))390					.checked_add(&total_staked)391					.ok_or(ArithmeticError::Overflow)?,392			);393394			TotalStaked::<T>::set(395				TotalStaked::<T>::get()396					.checked_sub(&total_staked)397					.ok_or(ArithmeticError::Underflow)?,398			); // when error we should recover initial stake state for the staker399400			StakesPerAccount::<T>::remove(&staker_id);401402			Ok(None.into())403		}404405		#[pallet::weight(T::WeightInfo::sponsor_collection())]406		pub fn sponsor_collection(407			admin: OriginFor<T>,408			collection_id: CollectionId,409		) -> DispatchResult {410			let admin_id = ensure_signed(admin)?;411			ensure!(412				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,413				Error::<T>::NoPermission414			);415416			T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)417		}418		#[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]419		pub fn stop_sponsoring_collection(420			admin: OriginFor<T>,421			collection_id: CollectionId,422		) -> DispatchResult {423			let admin_id = ensure_signed(admin)?;424425			ensure!(426				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,427				Error::<T>::NoPermission428			);429430			ensure!(431				T::CollectionHandler::get_sponsor(collection_id)?432					.ok_or(<Error<T>>::InvalidArgument)?433					== Self::account_id(),434				<Error<T>>::NoPermission435			);436			T::CollectionHandler::remove_collection_sponsor(collection_id)437		}438439		#[pallet::weight(T::WeightInfo::sponsor_contract())]440		pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {441			let admin_id = ensure_signed(admin)?;442443			ensure!(444				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,445				Error::<T>::NoPermission446			);447448			T::ContractHandler::set_sponsor(449				T::CrossAccountId::from_sub(Self::account_id()),450				contract_id,451			)452		}453454		#[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]455		pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {456			let admin_id = ensure_signed(admin)?;457458			ensure!(459				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,460				Error::<T>::NoPermission461			);462463			ensure!(464				T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?465					== T::CrossAccountId::from_sub(Self::account_id()),466				<Error<T>>::NoPermission467			);468			T::ContractHandler::remove_contract_sponsor(contract_id)469		}470471		#[pallet::weight(0)]472		pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {473			let admin_id = ensure_signed(admin)?;474475			ensure!(476				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,477				Error::<T>::NoPermission478			);479480			let current_recalc_block =481				Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());482			let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();483484			let mut storage_iterator = Self::get_next_calculated_key()485				.map_or(Staked::<T>::iter().skip(0), |key| {486					Staked::<T>::iter_from(key).skip(1)487				});488489			NextCalculatedRecord::<T>::set(None);490491			{492				let mut stakers_number = stakers_number.unwrap_or(20);493				let mut current_id = admin_id;494				let mut income_acc = BalanceOf::<T>::default();495496				while let Some(((id, staked_block), (amount, next_recalc_block_for_stake))) =497					storage_iterator.next()498				{499					if current_id != id {500						if income_acc != BalanceOf::<T>::default() {501							<T::Currency as Currency<T::AccountId>>::transfer(502								&T::TreasuryAccountId::get(),503								&current_id,504								income_acc,505								ExistenceRequirement::KeepAlive,506							)507							.and_then(|_| Self::add_lock_balance(&current_id, income_acc))?;508509							Self::deposit_event(Event::StakingRecalculation(510								current_id, amount, income_acc,511							));512						}513514						if stakers_number == 0 {515							NextCalculatedRecord::<T>::set(Some((id, staked_block)));516							break;517						}518						stakers_number -= 1;519						income_acc = BalanceOf::<T>::default();520						current_id = id;521					};522					if current_recalc_block >= next_recalc_block_for_stake {523						Self::recalculate_and_insert_stake(524							&current_id,525							staked_block,526							next_recalc_block,527							amount,528							((current_recalc_block - next_recalc_block_for_stake)529								/ T::RecalculationInterval::get())530							.into() + 1,531							&mut income_acc,532						);533					}534				}535			}536537			Ok(())538		}539	}540}541542impl<T: Config> Pallet<T> {543	pub fn account_id() -> T::AccountId {544		T::PalletId::get().into_account_truncating()545	}546547	fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {548		let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();549		locked_balance -= amount;550		Self::set_lock_unchecked(staker, locked_balance);551	}552553	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {554		Self::get_locked_balance(staker)555			.map_or(<BalanceOf<T>>::default(), |l| l.amount)556			.checked_add(&amount)557			.map(|new_lock| Self::set_lock_unchecked(staker, new_lock))558			.ok_or(ArithmeticError::Overflow.into())559	}560561	fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {562		if amount.is_zero() {563			<T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);564		} else {565			<T::Currency as LockableCurrency<T::AccountId>>::set_lock(566				LOCK_IDENTIFIER,567				staker,568				amount,569				WithdrawReasons::all(),570			)571		}572	}573574	pub fn get_locked_balance(575		staker: impl EncodeLike<T::AccountId>,576	) -> Option<BalanceLock<BalanceOf<T>>> {577		<T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)578			.into_iter()579			.find(|l| l.id == LOCK_IDENTIFIER)580	}581582	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {583		let staked = Staked::<T>::iter_prefix((staker,))584			.into_iter()585			.fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {586				acc + amount587			});588		if staked != <BalanceOf<T>>::default() {589			Some(staked)590		} else {591			None592		}593	}594595	pub fn total_staked_by_id_per_block(596		staker: impl EncodeLike<T::AccountId>,597	) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {598		let mut staked = Staked::<T>::iter_prefix((staker,))599			.into_iter()600			.map(|(block, (amount, _))| (block, amount))601			.collect::<Vec<_>>();602		staked.sort_by_key(|(block, _)| *block);603		if !staked.is_empty() {604			Some(staked)605		} else {606			None607		}608	}609610	pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {611		staker.map_or(Some(<TotalStaked<T>>::get()), |s| {612			Self::total_staked_by_id(s.as_sub())613		})614		// Self::total_staked_by_id(staker.as_sub())615	}616617	pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {618		Self::get_locked_balance(staker.as_sub())619			.map(|l| l.amount)620			.unwrap_or_default()621	}622623	pub fn cross_id_total_staked_per_block(624		staker: T::CrossAccountId,625	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {626		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()627	}628629	fn recalculate_and_insert_stake(630		staker: &T::AccountId,631		staked_block: T::BlockNumber,632		next_recalc_block: T::BlockNumber,633		base: BalanceOf<T>,634		iters: u32,635		income_acc: &mut BalanceOf<T>,636	) {637		let income = Self::calculate_income(base, iters);638639		base.checked_add(&income).map(|res| {640			<Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));641			*income_acc += income;642		});643	}644645	fn calculate_income<I>(base: I, iters: u32) -> I646	where647		I: EncodeLike<BalanceOf<T>> + Balance,648	{649		let mut income = base;650651		(0..iters).for_each(|_| income += T::IntervalIncome::get() * income);652653		income - base654	}655656	fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {657		(current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()658	}659660	// fn get_next_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {661	// 	Self::get_current_recalc_block(current_relay_block) + T::RecalculationInterval::get()662	// }663664	fn get_next_calculated_key() -> Option<Vec<u8>> {665		Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))666	}667}668669impl<T: Config> Pallet<T>670where671	<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,672{673	pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {674		staker.map_or(PendingUnstake::<T>::iter_values().sum(), |s| {675			PendingUnstake::<T>::iter_prefix_values((s.as_sub(),)).sum()676		})677	}678679	pub fn cross_id_pending_unstake_per_block(680		staker: T::CrossAccountId,681	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {682		let mut unsorted_res = PendingUnstake::<T>::iter_prefix((staker.as_sub(),))683			.into_iter()684			.collect::<Vec<_>>();685		unsorted_res.sort_by_key(|(block, _)| *block);686		unsorted_res687	}688}
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::{40	vec::{Vec},41	vec,42	iter::Sum,43	borrow::ToOwned,44};45use sp_core::H160;46use codec::EncodeLike;47use pallet_balances::BalanceLock;48pub use types::*;4950// use up_common::constants::{DAYS, UNIQUE};51use up_data_structs::CollectionId;5253use frame_support::{54	dispatch::{DispatchResult},55	traits::{56		Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,57	},58	ensure,59};6061use weights::WeightInfo;6263pub use pallet::*;64use pallet_evm::account::CrossAccountId;65use sp_runtime::{66	Perbill,67	traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion, Zero},68	ArithmeticError,69};7071pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";72const PENDING_LIMIT_PER_BLOCK: u32 = 3;7374type BalanceOf<T> =75	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;7677// const SECONDS_TO_BLOCK: u32 = 6;78// const DAY: u32 = 60 * 60 * 24 / SECONDS_TO_BLOCK;79// const WEEK: u32 = 7 * DAY;80// const TWO_WEEK: u32 = 2 * WEEK;81// const YEAR: u32 = DAY * 365;8283#[frame_support::pallet]84pub mod pallet {85	use super::*;86	use frame_support::{87		Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId,88		traits::ReservableCurrency,89	};90	use frame_system::pallet_prelude::*;9192	#[pallet::config]93	pub trait Config: frame_system::Config + pallet_evm::account::Config {94		type Currency: ExtendedLockableCurrency<Self::AccountId>95			+ ReservableCurrency<Self::AccountId>;9697		type CollectionHandler: CollectionHandler<98			AccountId = Self::AccountId,99			CollectionId = CollectionId,100		>;101102		type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;103104		type TreasuryAccountId: Get<Self::AccountId>;105106		/// The app's pallet id, used for deriving its sovereign account ID.107		#[pallet::constant]108		type PalletId: Get<PalletId>;109110		/// In relay blocks.111		#[pallet::constant]112		type RecalculationInterval: Get<Self::BlockNumber>;113		/// In relay blocks.114		#[pallet::constant]115		type PendingInterval: Get<Self::BlockNumber>;116117		/// In chain blocks.118		#[pallet::constant]119		type Day: Get<Self::BlockNumber>; // useless120121		#[pallet::constant]122		type Nominal: Get<BalanceOf<Self>>;123124		#[pallet::constant]125		type IntervalIncome: Get<Perbill>;126127		/// Weight information for extrinsics in this pallet.128		type WeightInfo: WeightInfo;129130		// The relay block number provider131		type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;132133		/// Events compatible with [`frame_system::Config::Event`].134		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;135	}136137	#[pallet::pallet]138	#[pallet::generate_store(pub(super) trait Store)]139	pub struct Pallet<T>(_);140141	#[pallet::event]142	#[pallet::generate_deposit(fn deposit_event)]143	pub enum Event<T: Config> {144		StakingRecalculation(145			/// An recalculated staker146			T::AccountId,147			/// Base on which interest is calculated148			BalanceOf<T>,149			/// Amount of accrued interest150			BalanceOf<T>,151		),152	}153154	#[pallet::error]155	pub enum Error<T> {156		/// Error due to action requiring admin to be set157		AdminNotSet,158		/// No permission to perform an action159		NoPermission,160		/// Insufficient funds to perform an action161		NotSufficientFounds,162		PendingForBlockOverflow,163		/// An error related to the fact that an invalid argument was passed to perform an action164		InvalidArgument,165	}166167	#[pallet::storage]168	pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;169170	#[pallet::storage]171	pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;172173	/// Amount of tokens staked by account in the blocknumber.174	#[pallet::storage]175	pub type Staked<T: Config> = StorageNMap<176		Key = (177			Key<Blake2_128Concat, T::AccountId>,178			Key<Twox64Concat, T::BlockNumber>,179		),180		Value = (BalanceOf<T>, T::BlockNumber),181		QueryKind = ValueQuery,182	>;183	/// Amount of stakes for an Account184	#[pallet::storage]185	pub type StakesPerAccount<T: Config> =186		StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;187188	/// Amount of tokens pending unstake per user per block.189	// #[pallet::storage]190	// pub type PendingUnstake<T: Config> = StorageNMap<191	// 	Key = (192	// 		Key<Blake2_128Concat, T::AccountId>,193	// 		Key<Twox64Concat, T::BlockNumber>,194	// 	),195	// 	Value = BalanceOf<T>,196	// 	QueryKind = ValueQuery,197	// >;198	#[pallet::storage]199	pub type PendingUnstake<T: Config> = StorageMap<200		_,201		Twox64Concat,202		T::BlockNumber,203		BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,204		ValueQuery,205	>;206207	/// A block when app-promotion has started .I think this is redundant, because we only need `NextInterestBlock`.208	#[pallet::storage]209	pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;210211	// /// Next target block when interest is recalculated212	// #[pallet::storage]213	// #[pallet::getter(fn get_interest_block)]214	// pub type NextInterestBlock<T: Config> =215	// 	StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;216217	/// Stores hash a record for which the last revenue recalculation was performed.218	/// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.219	#[pallet::storage]220	#[pallet::getter(fn get_next_calculated_record)]221	pub type NextCalculatedRecord<T: Config> =222		StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;223224	#[pallet::hooks]225	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {226		fn on_initialize(current_block_number: T::BlockNumber) -> Weight227		where228			<T as frame_system::Config>::BlockNumber: From<u32>,229		{230			let mut consumed_weight = 0;231			let mut add_weight = |reads, writes, weight| {232				consumed_weight += T::DbWeight::get().reads_writes(reads, writes);233				consumed_weight += weight;234			};235236			let block_pending = PendingUnstake::<T>::take(current_block_number);237238			add_weight(0, 1, 0);239240			if !block_pending.is_empty() {241				block_pending.into_iter().for_each(|(staker, amount)| {242					<T::Currency as ReservableCurrency<T::AccountId>>::unreserve(&staker, amount);243				});244			}245246			consumed_weight247		}248	}249250	#[pallet::call]251	impl<T: Config> Pallet<T>252	where253		T::BlockNumber: From<u32> + Into<u32>,254		<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,255	{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::stop_app_promotion())]289		// pub fn stop_app_promotion(origin: OriginFor<T>) -> DispatchResult290		// where291		// 	<T as frame_system::Config>::BlockNumber: From<u32>,292		// {293		// 	ensure_root(origin)?;294295		// 	if <StartBlock<T>>::get() != 0u32.into() {296		// 		<StartBlock<T>>::set(T::BlockNumber::default());297		// 		<NextInterestBlock<T>>::set(T::BlockNumber::default());298		// 	}299300		// 	Ok(())301		// }302303		#[pallet::weight(T::WeightInfo::stake())]304		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {305			let staker_id = ensure_signed(staker)?;306307			ensure!(308				StakesPerAccount::<T>::get(&staker_id) < 10,309				Error::<T>::NoPermission310			);311312			ensure!(313				amount >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(),314				ArithmeticError::Underflow315			);316317			let balance =318				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);319320			ensure!(balance >= amount, ArithmeticError::Underflow);321322			<<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(323				&staker_id,324				amount,325				WithdrawReasons::all(),326				balance - amount,327			)?;328329			Self::add_lock_balance(&staker_id, amount)?;330331			let block_number = T::RelayBlockNumberProvider::current_block_number();332			let recalc_block = (block_number / T::RecalculationInterval::get() + 2u32.into())333				* T::RecalculationInterval::get();334335			<Staked<T>>::insert((&staker_id, block_number), {336				let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));337				balance_and_recalc_block.0 = balance_and_recalc_block338					.0339					.checked_add(&amount)340					.ok_or(ArithmeticError::Overflow)?;341				balance_and_recalc_block.1 = recalc_block;342				balance_and_recalc_block343			});344345			<TotalStaked<T>>::set(346				<TotalStaked<T>>::get()347					.checked_add(&amount)348					.ok_or(ArithmeticError::Overflow)?,349			);350351			StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);352			Ok(())353		}354355		#[pallet::weight(T::WeightInfo::unstake())]356		pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {357			let staker_id = ensure_signed(staker)?;358			let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();359			let mut pendings = <PendingUnstake<T>>::get(block);360361			ensure!(pendings.is_full(), Error::<T>::PendingForBlockOverflow);362363			let mut total_stakes = 0u64;364365			let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))366				.map(|(_, (amount, _))| {367					total_stakes += 1;368					amount369				})370				.sum();371372			if total_staked.is_zero() {373				return Ok(None.into());374			}375376			pendings377				.try_push((staker_id.clone(), total_staked))378				.map_err(|_| Error::<T>::PendingForBlockOverflow)?;379380			<PendingUnstake<T>>::insert(block, pendings);381382			Self::unlock_balance_unchecked(&staker_id, total_staked);383384			<T::Currency as ReservableCurrency<T::AccountId>>::reserve(&staker_id, total_staked)?;385386			TotalStaked::<T>::set(387				TotalStaked::<T>::get()388					.checked_sub(&total_staked)389					.ok_or(ArithmeticError::Underflow)?,390			); // when error we should recover initial stake state for the staker391392			StakesPerAccount::<T>::remove(&staker_id);393394			Ok(None.into())395		}396397		#[pallet::weight(T::WeightInfo::sponsor_collection())]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(T::WeightInfo::stop_sponsoring_collection())]411		pub fn stop_sponsoring_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(T::WeightInfo::sponsor_contract())]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(T::WeightInfo::stop_sponsoring_contract())]447		pub fn stop_sponsoring_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		}462463		#[pallet::weight(0)]464		pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {465			let admin_id = ensure_signed(admin)?;466467			ensure!(468				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,469				Error::<T>::NoPermission470			);471472			let current_recalc_block =473				Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());474			let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();475476			let mut storage_iterator = Self::get_next_calculated_key()477				.map_or(Staked::<T>::iter().skip(0), |key| {478					Staked::<T>::iter_from(key).skip(1)479				});480481			NextCalculatedRecord::<T>::set(None);482483			{484				let mut stakers_number = stakers_number.unwrap_or(20);485				let mut current_id = admin_id;486				let mut income_acc = BalanceOf::<T>::default();487488				while let Some(((id, staked_block), (amount, next_recalc_block_for_stake))) =489					storage_iterator.next()490				{491					if current_id != id {492						if income_acc != BalanceOf::<T>::default() {493							<T::Currency as Currency<T::AccountId>>::transfer(494								&T::TreasuryAccountId::get(),495								&current_id,496								income_acc,497								ExistenceRequirement::KeepAlive,498							)499							.and_then(|_| Self::add_lock_balance(&current_id, income_acc))?;500501							Self::deposit_event(Event::StakingRecalculation(502								current_id, amount, income_acc,503							));504						}505506						if stakers_number == 0 {507							NextCalculatedRecord::<T>::set(Some((id, staked_block)));508							break;509						}510						stakers_number -= 1;511						income_acc = BalanceOf::<T>::default();512						current_id = id;513					};514					if current_recalc_block >= next_recalc_block_for_stake {515						Self::recalculate_and_insert_stake(516							&current_id,517							staked_block,518							next_recalc_block,519							amount,520							((current_recalc_block - next_recalc_block_for_stake)521								/ T::RecalculationInterval::get())522							.into() + 1,523							&mut income_acc,524						);525					}526				}527			}528529			Ok(())530		}531	}532}533534impl<T: Config> Pallet<T> {535	pub fn account_id() -> T::AccountId {536		T::PalletId::get().into_account_truncating()537	}538539	fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {540		let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();541		locked_balance -= amount;542		Self::set_lock_unchecked(staker, locked_balance);543	}544545	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {546		Self::get_locked_balance(staker)547			.map_or(<BalanceOf<T>>::default(), |l| l.amount)548			.checked_add(&amount)549			.map(|new_lock| Self::set_lock_unchecked(staker, new_lock))550			.ok_or(ArithmeticError::Overflow.into())551	}552553	fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {554		if amount.is_zero() {555			<T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);556		} else {557			<T::Currency as LockableCurrency<T::AccountId>>::set_lock(558				LOCK_IDENTIFIER,559				staker,560				amount,561				WithdrawReasons::all(),562			)563		}564	}565566	pub fn get_locked_balance(567		staker: impl EncodeLike<T::AccountId>,568	) -> Option<BalanceLock<BalanceOf<T>>> {569		<T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)570			.into_iter()571			.find(|l| l.id == LOCK_IDENTIFIER)572	}573574	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {575		let staked = Staked::<T>::iter_prefix((staker,))576			.into_iter()577			.fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {578				acc + amount579			});580		if staked != <BalanceOf<T>>::default() {581			Some(staked)582		} else {583			None584		}585	}586587	pub fn total_staked_by_id_per_block(588		staker: impl EncodeLike<T::AccountId>,589	) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {590		let mut staked = Staked::<T>::iter_prefix((staker,))591			.into_iter()592			.map(|(block, (amount, _))| (block, amount))593			.collect::<Vec<_>>();594		staked.sort_by_key(|(block, _)| *block);595		if !staked.is_empty() {596			Some(staked)597		} else {598			None599		}600	}601602	pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {603		staker.map_or(Some(<TotalStaked<T>>::get()), |s| {604			Self::total_staked_by_id(s.as_sub())605		})606		// Self::total_staked_by_id(staker.as_sub())607	}608609	pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {610		Self::get_locked_balance(staker.as_sub())611			.map(|l| l.amount)612			.unwrap_or_default()613	}614615	pub fn cross_id_total_staked_per_block(616		staker: T::CrossAccountId,617	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {618		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()619	}620621	fn recalculate_and_insert_stake(622		staker: &T::AccountId,623		staked_block: T::BlockNumber,624		next_recalc_block: T::BlockNumber,625		base: BalanceOf<T>,626		iters: u32,627		income_acc: &mut BalanceOf<T>,628	) {629		let income = Self::calculate_income(base, iters);630631		base.checked_add(&income).map(|res| {632			<Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));633			*income_acc += income;634		});635	}636637	fn calculate_income<I>(base: I, iters: u32) -> I638	where639		I: EncodeLike<BalanceOf<T>> + Balance,640	{641		let mut income = base;642643		(0..iters).for_each(|_| income += T::IntervalIncome::get() * income);644645		income - base646	}647648	fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {649		(current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()650	}651652	// fn get_next_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {653	// 	Self::get_current_recalc_block(current_relay_block) + T::RecalculationInterval::get()654	// }655656	fn get_next_calculated_key() -> Option<Vec<u8>> {657		Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))658	}659}660661impl<T: Config> Pallet<T>662where663	<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,664{665	pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {666		staker.map_or(667			PendingUnstake::<T>::iter_values()668				.flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))669				.sum(),670			|s| {671				PendingUnstake::<T>::iter_values()672					.flatten()673					.filter_map(|(id, amount)| {674						if id == *s.as_sub() {675							Some(amount)676						} else {677							None678						}679					})680					.sum()681			},682		)683	}684685	pub fn cross_id_pending_unstake_per_block(686		staker: T::CrossAccountId,687	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {688		let mut unsorted_res = vec![];689		PendingUnstake::<T>::iter().for_each(|(block, pendings)| {690			pendings.into_iter().for_each(|(id, amount)| {691				if id == *staker.as_sub() {692					unsorted_res.push((block, amount));693				};694			})695		});696697		unsorted_res.sort_by_key(|(block, _)| *block);698		unsorted_res699	}700}
modifiedpallets/app-promotion/src/tests.rsdiffbeforeafterboth
--- a/pallets/app-promotion/src/tests.rs
+++ b/pallets/app-promotion/src/tests.rs
@@ -16,20 +16,20 @@
 
 #![cfg(test)]
 #![allow(clippy::from_over_into)]
-use crate as pallet_promotion;
+// use crate as pallet_promotion;
 
-use frame_benchmarking::{add_benchmark, BenchmarkBatch};
-use frame_support::{
-	assert_ok, parameter_types,
-	traits::{Currency, OnInitialize, Everything, ConstU32},
-};
-use frame_system::RawOrigin;
-use sp_core::H256;
-use sp_runtime::{
-	traits::{BlakeTwo256, BlockNumberProvider, IdentityLookup},
-	testing::Header,
-	Perbill, Perquintill,
-};
+// use frame_benchmarking::{add_benchmark, BenchmarkBatch};
+// use frame_support::{
+// 	assert_ok, parameter_types,
+// 	traits::{Currency, OnInitialize, Everything, ConstU32},
+// };
+// use frame_system::RawOrigin;
+// use sp_core::H256;
+// use sp_runtime::{
+// 	traits::{BlakeTwo256, BlockNumberProvider, IdentityLookup},
+// 	testing::Header,
+// 	Perbill, Perquintill,
+// };
 
 // type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
 // type Block = frame_system::mocking::MockBlock<Test>;
@@ -127,24 +127,24 @@
 // 	} )
 // }
 
-#[test]
-fn test_perbill() {
-	const ONE_UNIQE: u128 = 1_000_000_000_000_000_000;
-	const SECONDS_TO_BLOCK: u32 = 12;
-	const DAY: u32 = 60 * 60 * 24 / SECONDS_TO_BLOCK;
-	const RECALCULATION_INTERVAL: u32 = 10;
-	let day_rate = Perbill::from_rational(5u64, 10_000);
-	let interval_rate =
-		Perbill::from_rational::<u64>(RECALCULATION_INTERVAL.into(), DAY.into()) * day_rate;
-	println!("{:?}", interval_rate * ONE_UNIQE + ONE_UNIQE);
-	println!("{:?}", day_rate * ONE_UNIQE);
-	println!("{:?}", Perbill::one() * ONE_UNIQE);
-	println!("{:?}", ONE_UNIQE);
-	let mut next_iters = ONE_UNIQE + interval_rate * ONE_UNIQE;
-	next_iters += interval_rate * next_iters;
-	println!("{:?}", next_iters);
-	let day_income = day_rate * ONE_UNIQE;
-	let interval_income = interval_rate * ONE_UNIQE;
-	let ratio = day_income / interval_income;
-	println!("{:?} || {:?}", ratio, DAY / RECALCULATION_INTERVAL);
-}
+// #[test]
+// fn test_perbill() {
+// 	const ONE_UNIQE: u128 = 1_000_000_000_000_000_000;
+// 	const SECONDS_TO_BLOCK: u32 = 12;
+// 	const DAY: u32 = 60 * 60 * 24 / SECONDS_TO_BLOCK;
+// 	const RECALCULATION_INTERVAL: u32 = 10;
+// 	let day_rate = Perbill::from_rational(5u64, 10_000);
+// 	let interval_rate =
+// 		Perbill::from_rational::<u64>(RECALCULATION_INTERVAL.into(), DAY.into()) * day_rate;
+// 	println!("{:?}", interval_rate * ONE_UNIQE + ONE_UNIQE);
+// 	println!("{:?}", day_rate * ONE_UNIQE);
+// 	println!("{:?}", Perbill::one() * ONE_UNIQE);
+// 	println!("{:?}", ONE_UNIQE);
+// 	let mut next_iters = ONE_UNIQE + interval_rate * ONE_UNIQE;
+// 	next_iters += interval_rate * next_iters;
+// 	println!("{:?}", next_iters);
+// 	let day_income = day_rate * ONE_UNIQE;
+// 	let interval_income = interval_rate * ONE_UNIQE;
+// 	let ratio = day_income / interval_income;
+// 	println!("{:?} || {:?}", ratio, DAY / RECALCULATION_INTERVAL);
+// }
modifiedpallets/app-promotion/src/weights.rsdiffbeforeafterboth
--- a/pallets/app-promotion/src/weights.rs
+++ b/pallets/app-promotion/src/weights.rs
@@ -3,7 +3,7 @@
 //! Autogenerated weights for pallet_app_promotion
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2022-08-31, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2022-09-01, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
 
 // Executed Command:
@@ -34,8 +34,6 @@
 
 /// Weight functions needed for pallet_app_promotion.
 pub trait WeightInfo {
-	fn start_app_promotion() -> Weight;
-	fn stop_app_promotion() -> Weight;
 	fn set_admin_address() -> Weight;
 	fn payout_stakers() -> Weight;
 	fn stake() -> Weight;
@@ -49,24 +47,9 @@
 /// Weights for pallet_app_promotion using the Substrate node and recommended hardware.
 pub struct SubstrateWeight<T>(PhantomData<T>);
 impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
-	// Storage: Promotion StartBlock (r:1 w:1)
-	// Storage: ParachainSystem ValidationData (r:1 w:0)
-	// Storage: Promotion NextInterestBlock (r:0 w:1)
-	fn start_app_promotion() -> Weight {
-		(3_995_000 as Weight)
-			.saturating_add(T::DbWeight::get().reads(2 as Weight))
-			.saturating_add(T::DbWeight::get().writes(2 as Weight))
-	}
-	// Storage: Promotion StartBlock (r:1 w:1)
-	// Storage: Promotion NextInterestBlock (r:0 w:1)
-	fn stop_app_promotion() -> Weight {
-		(3_623_000 as Weight)
-			.saturating_add(T::DbWeight::get().reads(1 as Weight))
-			.saturating_add(T::DbWeight::get().writes(2 as Weight))
-	}
 	// Storage: Promotion Admin (r:0 w:1)
 	fn set_admin_address() -> Weight {
-		(1_203_000 as Weight)
+		(515_000 as Weight)
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Promotion Admin (r:1 w:0)
@@ -74,54 +57,56 @@
 	// Storage: Promotion NextCalculatedRecord (r:1 w:1)
 	// Storage: Promotion Staked (r:2 w:0)
 	fn payout_stakers() -> Weight {
-		(10_859_000 as Weight)
+		(8_475_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(5 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 	// Storage: System Account (r:1 w:1)
+	// Storage: Promotion StakesPerAccount (r:1 w:1)
 	// Storage: Balances Locks (r:1 w:1)
 	// Storage: ParachainSystem ValidationData (r:1 w:0)
 	// Storage: Promotion Staked (r:1 w:1)
 	// Storage: Promotion TotalStaked (r:1 w:1)
 	fn stake() -> Weight {
-		(14_789_000 as Weight)
-			.saturating_add(T::DbWeight::get().reads(5 as Weight))
-			.saturating_add(T::DbWeight::get().writes(4 as Weight))
+		(12_266_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(6 as Weight))
+			.saturating_add(T::DbWeight::get().writes(5 as Weight))
 	}
 	// Storage: Promotion Staked (r:2 w:1)
 	// Storage: ParachainSystem ValidationData (r:1 w:0)
 	// Storage: Promotion PendingUnstake (r:1 w:1)
 	// Storage: Promotion TotalStaked (r:1 w:1)
+	// Storage: Promotion StakesPerAccount (r:0 w:1)
 	fn unstake() -> Weight {
-		(16_889_000 as Weight)
+		(10_663_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(5 as Weight))
-			.saturating_add(T::DbWeight::get().writes(3 as Weight))
+			.saturating_add(T::DbWeight::get().writes(4 as Weight))
 	}
 	// Storage: Promotion Admin (r:1 w:0)
 	// Storage: Common CollectionById (r:1 w:1)
 	fn sponsor_collection() -> Weight {
-		(18_377_000 as Weight)
+		(10_879_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Promotion Admin (r:1 w:0)
 	// Storage: Common CollectionById (r:1 w:1)
 	fn stop_sponsoring_collection() -> Weight {
-		(13_989_000 as Weight)
+		(10_548_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Promotion Admin (r:1 w:0)
 	// Storage: EvmContractHelpers Sponsoring (r:0 w:1)
 	fn sponsor_contract() -> Weight {
-		(4_162_000 as Weight)
+		(2_130_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(1 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Promotion Admin (r:1 w:0)
 	// Storage: EvmContractHelpers Sponsoring (r:1 w:1)
 	fn stop_sponsoring_contract() -> Weight {
-		(5_457_000 as Weight)
+		(3_509_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
@@ -129,24 +114,9 @@
 
 // For backwards compatibility and tests
 impl WeightInfo for () {
-	// Storage: Promotion StartBlock (r:1 w:1)
-	// Storage: ParachainSystem ValidationData (r:1 w:0)
-	// Storage: Promotion NextInterestBlock (r:0 w:1)
-	fn start_app_promotion() -> Weight {
-		(3_995_000 as Weight)
-			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
-			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
-	}
-	// Storage: Promotion StartBlock (r:1 w:1)
-	// Storage: Promotion NextInterestBlock (r:0 w:1)
-	fn stop_app_promotion() -> Weight {
-		(3_623_000 as Weight)
-			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
-			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
-	}
 	// Storage: Promotion Admin (r:0 w:1)
 	fn set_admin_address() -> Weight {
-		(1_203_000 as Weight)
+		(515_000 as Weight)
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Promotion Admin (r:1 w:0)
@@ -154,54 +124,56 @@
 	// Storage: Promotion NextCalculatedRecord (r:1 w:1)
 	// Storage: Promotion Staked (r:2 w:0)
 	fn payout_stakers() -> Weight {
-		(10_859_000 as Weight)
+		(8_475_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
 	// Storage: System Account (r:1 w:1)
+	// Storage: Promotion StakesPerAccount (r:1 w:1)
 	// Storage: Balances Locks (r:1 w:1)
 	// Storage: ParachainSystem ValidationData (r:1 w:0)
 	// Storage: Promotion Staked (r:1 w:1)
 	// Storage: Promotion TotalStaked (r:1 w:1)
 	fn stake() -> Weight {
-		(14_789_000 as Weight)
-			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
-			.saturating_add(RocksDbWeight::get().writes(4 as Weight))
+		(12_266_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(6 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(5 as Weight))
 	}
 	// Storage: Promotion Staked (r:2 w:1)
 	// Storage: ParachainSystem ValidationData (r:1 w:0)
 	// Storage: Promotion PendingUnstake (r:1 w:1)
 	// Storage: Promotion TotalStaked (r:1 w:1)
+	// Storage: Promotion StakesPerAccount (r:0 w:1)
 	fn unstake() -> Weight {
-		(16_889_000 as Weight)
+		(10_663_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
-			.saturating_add(RocksDbWeight::get().writes(3 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(4 as Weight))
 	}
 	// Storage: Promotion Admin (r:1 w:0)
 	// Storage: Common CollectionById (r:1 w:1)
 	fn sponsor_collection() -> Weight {
-		(18_377_000 as Weight)
+		(10_879_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Promotion Admin (r:1 w:0)
 	// Storage: Common CollectionById (r:1 w:1)
 	fn stop_sponsoring_collection() -> Weight {
-		(13_989_000 as Weight)
+		(10_548_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Promotion Admin (r:1 w:0)
 	// Storage: EvmContractHelpers Sponsoring (r:0 w:1)
 	fn sponsor_contract() -> Weight {
-		(4_162_000 as Weight)
+		(2_130_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Promotion Admin (r:1 w:0)
 	// Storage: EvmContractHelpers Sponsoring (r:1 w:1)
 	fn stop_sponsoring_contract() -> Weight {
-		(5_457_000 as Weight)
+		(3_509_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
modifiedruntime/common/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -197,11 +197,11 @@
                     #[cfg(feature = "app-promotion")]
                     return Ok(<pallet_app_promotion::Pallet<Runtime>>::cross_id_total_staked(staker).unwrap_or_default());
                 }
-                
+
                 fn total_staked_per_block(staker: CrossAccountId) -> Result<Vec<(BlockNumber, u128)>, DispatchError> {
                     #[cfg(not(feature = "app-promotion"))]
                     return unsupported!();
-                    
+
                     #[cfg(feature = "app-promotion")]
                     return Ok(<pallet_app_promotion::Pallet<Runtime>>::cross_id_total_staked_per_block(staker));
                 }
@@ -209,7 +209,7 @@
                 fn total_staking_locked(staker: CrossAccountId) -> Result<u128, DispatchError> {
                     #[cfg(not(feature = "app-promotion"))]
                     return unsupported!();
-                    
+
                     #[cfg(feature = "app-promotion")]
                     return Ok(<pallet_app_promotion::Pallet<Runtime>>::cross_id_locked_balance(staker));
                 }
@@ -217,7 +217,7 @@
                 fn pending_unstake(staker: Option<CrossAccountId>) -> Result<u128, DispatchError> {
                     #[cfg(not(feature = "app-promotion"))]
                     return unsupported!();
-                    
+
                     #[cfg(feature = "app-promotion")]
                     return Ok(<pallet_app_promotion::Pallet<Runtime>>::cross_id_pending_unstake(staker));
                 }
@@ -225,7 +225,7 @@
                 fn pending_unstake_per_block(staker: CrossAccountId) -> Result<Vec<(BlockNumber, u128)>, DispatchError> {
                     #[cfg(not(feature = "app-promotion"))]
                     return unsupported!();
-                    
+
                     #[cfg(feature = "app-promotion")]
                     return Ok(<pallet_app_promotion::Pallet<Runtime>>::cross_id_pending_unstake_per_block(staker))
                 }