git.delta.rocks / unique-network / refs/commits / 50852e23a743

difftreelog

minimal deposit for staking increased to 100 , added impl for `payout_stakers`.

PraetorP2022-08-31parent: #e85b045.patch.diff
in: master

3 files changed

modifiedpallets/app-promotion/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/app-promotion/src/benchmarking.rs
+++ b/pallets/app-promotion/src/benchmarking.rs
@@ -36,7 +36,8 @@
 benchmarks! {
 	where_clause{
 		where T:  Config + pallet_unique::Config + pallet_evm_migration::Config ,
-		T::BlockNumber: From<u32>
+		T::BlockNumber: From<u32> + Into<u32>,
+		<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>
 	}
 	start_app_promotion {
 
@@ -73,16 +74,16 @@
 		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))?;
 
-	} : {PromototionPallet::<T>::unstake(RawOrigin::Signed(caller.clone()).into(), share * <T as Config>::Currency::total_balance(&caller))?}
+	} : {PromototionPallet::<T>::unstake(RawOrigin::Signed(caller.clone()).into())?}
 
-	recalculate_stake {
-		let caller = account::<T::AccountId>("caller", 0, SEED);
-		let share = Perbill::from_rational(1u32, 10);
-		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))?;
-		let block = <T::RelayBlockNumberProvider as BlockNumberProvider>::current_block_number();
-		let mut acc = <BalanceOf<T>>::default();
-	} : {PromototionPallet::<T>::recalculate_stake(&caller, block, share * <T as Config>::Currency::total_balance(&caller), &mut acc)}
+	// recalculate_and_insert_stake{
+	// 	let caller = account::<T::AccountId>("caller", 0, SEED);
+	// 	let share = Perbill::from_rational(1u32, 10);
+	// 	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))?;
+	// 	let block = <T::RelayBlockNumberProvider as BlockNumberProvider>::current_block_number();
+	// 	let mut acc = <BalanceOf<T>>::default();
+	// } : {PromototionPallet::<T>::recalculate_and_insert_stake(&caller, block, share * <T as Config>::Currency::total_balance(&caller), &mut acc)}
 
 	sponsor_collection {
 		let pallet_admin = account::<T::AccountId>("admin", 0, SEED);
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},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	>;172173	/// Amount of tokens pending unstake per user per block.174	#[pallet::storage]175	pub type PendingUnstake<T: Config> = StorageNMap<176		Key = (177			Key<Blake2_128Concat, T::AccountId>,178			Key<Twox64Concat, T::BlockNumber>,179		),180		Value = BalanceOf<T>,181		QueryKind = ValueQuery,182	>;183184	/// A block when app-promotion has started .I think this is redundant, because we only need `NextInterestBlock`.185	#[pallet::storage]186	pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;187188	/// Next target block when interest is recalculated189	#[pallet::storage]190	#[pallet::getter(fn get_interest_block)]191	pub type NextInterestBlock<T: Config> =192		StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;193194	/// Stores the address of the staker for which the last revenue recalculation was performed.195	/// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.196	#[pallet::storage]197	#[pallet::getter(fn get_last_calculated_staker)]198	pub type LastCalcucaltedStaker<T: Config> =199		StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;200201	#[pallet::hooks]202	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {203		fn on_initialize(current_block: T::BlockNumber) -> Weight204		where205			<T as frame_system::Config>::BlockNumber: From<u32>,206		{207			let mut consumed_weight = 0;208			// let mut add_weight = |reads, writes, weight| {209			// 	consumed_weight += T::DbWeight::get().reads_writes(reads, writes);210			// 	consumed_weight += weight;211			// };212213			PendingUnstake::<T>::iter()214				.filter_map(|((staker, block), amount)| {215					if block <= current_block {216						Some((staker, block, amount))217					} else {218						None219					}220				})221				.for_each(|(staker, block, amount)| {222					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 treasuries223					<PendingUnstake<T>>::remove((staker, block));224				});225226			// let next_interest_block = Self::get_interest_block();227			// let current_relay_block = T::RelayBlockNumberProvider::current_block_number();228			// if next_interest_block != 0.into() && current_relay_block >= next_interest_block {229			// 	let mut acc = <BalanceOf<T>>::default();230			// 	let mut base_acc = <BalanceOf<T>>::default();231232			// 	NextInterestBlock::<T>::set(233			// 		NextInterestBlock::<T>::get() + T::RecalculationInterval::get(),234			// 	);235			// 	add_weight(0, 1, 0);236237			// 	Staked::<T>::iter()238			// 		.filter(|((_, block), _)| {239			// 			*block + T::RecalculationInterval::get() <= current_relay_block240			// 		})241			// 		.for_each(|((staker, block), amount)| {242			// 			Self::recalculate_stake(&staker, block, amount, &mut acc);243			// 			add_weight(0, 0, T::WeightInfo::recalculate_stake());244			// 			base_acc += amount;245			// 		});246			// 	<TotalStaked<T>>::get()247			// 		.checked_add(&acc)248			// 		.map(|res| <TotalStaked<T>>::set(res));249250			// 	Self::deposit_event(Event::StakingRecalculation(base_acc, acc));251			// 	add_weight(0, 1, 0);252			// } else {253			// 	add_weight(1, 0, 0)254			// };255			consumed_weight256		}257	}258259	#[pallet::call]260	impl<T: Config> Pallet<T>261	where262		T::BlockNumber: From<u32>,263		<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>264	{265		#[pallet::weight(T::WeightInfo::set_admin_address())]266		pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {267			ensure_root(origin)?;268			<Admin<T>>::set(Some(admin.as_sub().to_owned()));269270			Ok(())271		}272273		#[pallet::weight(T::WeightInfo::start_app_promotion())]274		pub fn start_app_promotion(275			origin: OriginFor<T>,276			promotion_start_relay_block: Option<T::BlockNumber>,277		) -> DispatchResult278		where279			<T as frame_system::Config>::BlockNumber: From<u32>,280		{281			ensure_root(origin)?;282283			// Start app-promotion mechanics if it has not been yet initialized284			if <StartBlock<T>>::get() == 0u32.into() {285				let start_block = promotion_start_relay_block286					.unwrap_or(T::RelayBlockNumberProvider::current_block_number());287288				// Set promotion global start block289				<StartBlock<T>>::set(start_block);290291				<NextInterestBlock<T>>::set(start_block + T::RecalculationInterval::get());292			}293294			Ok(())295		}296297		#[pallet::weight(T::WeightInfo::stop_app_promotion())]298		pub fn stop_app_promotion(origin: OriginFor<T>) -> DispatchResult299		where300			<T as frame_system::Config>::BlockNumber: From<u32>,301		{302			ensure_root(origin)?;303304			if <StartBlock<T>>::get() != 0u32.into() {305				<StartBlock<T>>::set(T::BlockNumber::default());306				<NextInterestBlock<T>>::set(T::BlockNumber::default());307			}308309			Ok(())310		}311312		#[pallet::weight(T::WeightInfo::stake())]313		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {314			let staker_id = ensure_signed(staker)?;315			316317			ensure!(amount >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(), ArithmeticError::Underflow);318319			let balance =320				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);321322			ensure!(balance >= amount, ArithmeticError::Underflow);323324			<<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(325				&staker_id,326				amount,327				WithdrawReasons::all(),328				balance - amount,329			)?;330331			Self::add_lock_balance(&staker_id, amount)?;332333			let block_number = T::RelayBlockNumberProvider::current_block_number();334			let recalc_block = (block_number / T::RecalculationInterval::get() + 2u32.into())335				* T::RecalculationInterval::get();336337			<Staked<T>>::insert((&staker_id, block_number), {338				let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));339				balance_and_recalc_block.0 = balance_and_recalc_block340					.0341					.checked_add(&amount)342					.ok_or(ArithmeticError::Overflow)?;343				balance_and_recalc_block.1 = recalc_block;344				balance_and_recalc_block345			});346347			<TotalStaked<T>>::set(348				<TotalStaked<T>>::get()349					.checked_add(&amount)350					.ok_or(ArithmeticError::Overflow)?,351			);352353			Ok(())354		}355356		#[pallet::weight(T::WeightInfo::unstake())]357		pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {358			let staker_id = ensure_signed(staker)?;359360			let mut total_stakes = 0u64;361362			let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))363				.map(|(_, (amount, _))| {364					*&mut total_stakes += 1;365					amount366				})367				.sum();368				369			let block =370				T::RelayBlockNumberProvider::current_block_number() + T::PendingInterval::get();371			<PendingUnstake<T>>::insert(372				(&staker_id, block),373				<PendingUnstake<T>>::get((&staker_id, block))374					.checked_add(&total_staked)375					.ok_or(ArithmeticError::Overflow)?,376			);377			378			TotalStaked::<T>::set(TotalStaked::<T>::get().checked_sub(&total_staked).ok_or(ArithmeticError::Underflow)?); // when error we should recover stake state for the staker379380			Ok(None.into())381382			// let staker_id = ensure_signed(staker)?;383384			// let mut stakes = Staked::<T>::iter_prefix((&staker_id,)).collect::<Vec<_>>();385386			// let total_staked = stakes387			// 	.iter()388			// 	.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);389390			// ensure!(total_staked >= amount, ArithmeticError::Underflow);391392			// <TotalStaked<T>>::set(393			// 	<TotalStaked<T>>::get()394			// 		.checked_sub(&amount)395			// 		.ok_or(ArithmeticError::Underflow)?,396			// );397398			// let block =399			// 	T::RelayBlockNumberProvider::current_block_number() + T::PendingInterval::get();400			// <PendingUnstake<T>>::insert(401			// 	(&staker_id, block),402			// 	<PendingUnstake<T>>::get((&staker_id, block))403			// 		.checked_add(&amount)404			// 		.ok_or(ArithmeticError::Overflow)?,405			// );406407			// stakes.sort_by_key(|(block, _)| *block);408409			// let mut acc_amount = amount;410			// let new_state = stakes411			// 	.into_iter()412			// 	.map_while(|(block, balance_per_block)| {413			// 		if acc_amount == <BalanceOf<T>>::default() {414			// 			return None;415			// 		}416			// 		if acc_amount <= balance_per_block {417			// 			let res = (block, balance_per_block - acc_amount, acc_amount);418			// 			acc_amount = <BalanceOf<T>>::default();419			// 			return Some(res);420			// 		} else {421			// 			acc_amount -= balance_per_block;422			// 			return Some((block, <BalanceOf<T>>::default(), acc_amount));423			// 		}424			// 	})425			// 	.collect::<Vec<_>>();426427			// new_state428			// 	.into_iter()429			// 	.for_each(|(block, to_staked, _to_pending)| {430			// 		if to_staked == <BalanceOf<T>>::default() {431			// 			<Staked<T>>::remove((&staker_id, block));432			// 		} else {433			// 			<Staked<T>>::insert((&staker_id, block), to_staked);434			// 		}435			// 	});436437			// Ok(())438		}439440		#[pallet::weight(T::WeightInfo::sponsor_collection())]441		pub fn sponsor_collection(442			admin: OriginFor<T>,443			collection_id: CollectionId,444		) -> DispatchResult {445			let admin_id = ensure_signed(admin)?;446			ensure!(447				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,448				Error::<T>::NoPermission449			);450451			T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)452		}453		#[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]454		pub fn stop_sponsoring_collection(455			admin: OriginFor<T>,456			collection_id: CollectionId,457		) -> DispatchResult {458			let admin_id = ensure_signed(admin)?;459460			ensure!(461				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,462				Error::<T>::NoPermission463			);464465			ensure!(466				T::CollectionHandler::get_sponsor(collection_id)?467					.ok_or(<Error<T>>::InvalidArgument)?468					== Self::account_id(),469				<Error<T>>::NoPermission470			);471			T::CollectionHandler::remove_collection_sponsor(collection_id)472		}473474		#[pallet::weight(T::WeightInfo::sponsor_contract())]475		pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {476			let admin_id = ensure_signed(admin)?;477478			ensure!(479				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,480				Error::<T>::NoPermission481			);482483			T::ContractHandler::set_sponsor(484				T::CrossAccountId::from_sub(Self::account_id()),485				contract_id,486			)487		}488489		#[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]490		pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {491			let admin_id = ensure_signed(admin)?;492493			ensure!(494				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,495				Error::<T>::NoPermission496			);497498			ensure!(499				T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?500					== T::CrossAccountId::from_sub(Self::account_id()),501				<Error<T>>::NoPermission502			);503			T::ContractHandler::remove_contract_sponsor(contract_id)504		}505506		#[pallet::weight(0)]507		pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {508			let admin_id = ensure_signed(admin)?;509510			ensure!(511				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,512				Error::<T>::NoPermission513			);514			515			let raw_key = Staked::<T>::hashed_key_for((admin_id, T::BlockNumber::default()));516			517			let key_iterator = Staked::<T>::iter_keys_from(raw_key).skip(1).into_iter();518			519			match Self::get_last_calculated_staker() {520				Some(last_staker) => {},521				None  => {}522			};523524			Ok(())525		}526	}527}528529impl<T: Config> Pallet<T> {530	pub fn account_id() -> T::AccountId {531		T::PalletId::get().into_account_truncating()532	}533534	fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {535		let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();536		locked_balance -= amount;537		Self::set_lock_unchecked(staker, locked_balance);538	}539540	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {541		Self::get_locked_balance(staker)542			.map_or(<BalanceOf<T>>::default(), |l| l.amount)543			.checked_add(&amount)544			.map(|new_lock| Self::set_lock_unchecked(staker, new_lock))545			.ok_or(ArithmeticError::Overflow.into())546	}547548	fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {549		<T::Currency as LockableCurrency<T::AccountId>>::set_lock(550			LOCK_IDENTIFIER,551			staker,552			amount,553			WithdrawReasons::all(),554		)555	}556557	pub fn get_locked_balance(558		staker: impl EncodeLike<T::AccountId>,559	) -> Option<BalanceLock<BalanceOf<T>>> {560		<T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)561			.into_iter()562			.find(|l| l.id == LOCK_IDENTIFIER)563	}564565	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {566		let staked = Staked::<T>::iter_prefix((staker,))567			.into_iter()568			.fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {569				acc + amount570			});571		if staked != <BalanceOf<T>>::default() {572			Some(staked)573		} else {574			None575		}576	}577578	pub fn total_staked_by_id_per_block(579		staker: impl EncodeLike<T::AccountId>,580	) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {581		let mut staked = Staked::<T>::iter_prefix((staker,))582			.into_iter()583			.map(|(block, (amount, _))| (block, amount))584			.collect::<Vec<_>>();585		staked.sort_by_key(|(block, _)| *block);586		if !staked.is_empty() {587			Some(staked)588		} else {589			None590		}591	}592593	pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {594		staker.map_or(Some(<TotalStaked<T>>::get()), |s| {595			Self::total_staked_by_id(s.as_sub())596		})597		// Self::total_staked_by_id(staker.as_sub())598	}599600	pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {601		Self::get_locked_balance(staker.as_sub())602			.map(|l| l.amount)603			.unwrap_or_default()604	}605606	pub fn cross_id_total_staked_per_block(607		staker: T::CrossAccountId,608	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {609		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()610	}611612	fn recalculate_stake(613		staker: &T::AccountId,614		block: T::BlockNumber,615		base: BalanceOf<T>,616		income_acc: &mut BalanceOf<T>,617	) {618		let income = Self::calculate_income(base);619		// base.checked_add(&income).map(|res| {620		// 	<Staked<T>>::insert((staker, block), res);621		// 	*income_acc += income;622		// 	<T::Currency as Currency<T::AccountId>>::transfer(623		// 		&T::TreasuryAccountId::get(),624		// 		staker,625		// 		income,626		// 		ExistenceRequirement::KeepAlive,627		// 	)628		// 	.and_then(|_| Self::add_lock_balance(staker, income));629		// });630	}631632	fn calculate_income<I>(base: I) -> I633	where634		I: EncodeLike<BalanceOf<T>> + Balance,635	{636		T::IntervalIncome::get() * base637	}638}639640impl<T: Config> Pallet<T>641where642	<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,643{644	pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {645		staker.map_or(PendingUnstake::<T>::iter_values().sum(), |s| {646			PendingUnstake::<T>::iter_prefix_values((s.as_sub(),)).sum()647		})648	}649650	pub fn cross_id_pending_unstake_per_block(651		staker: T::CrossAccountId,652	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {653		let mut unsorted_res = PendingUnstake::<T>::iter_prefix((staker.as_sub(),))654			.into_iter()655			.collect::<Vec<_>>();656		unsorted_res.sort_by_key(|(block, _)| *block);657		unsorted_res658	}659}
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			/// 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	>;172173	/// Amount of tokens pending unstake per user per block.174	#[pallet::storage]175	pub type PendingUnstake<T: Config> = StorageNMap<176		Key = (177			Key<Blake2_128Concat, T::AccountId>,178			Key<Twox64Concat, T::BlockNumber>,179		),180		Value = BalanceOf<T>,181		QueryKind = ValueQuery,182	>;183184	/// A block when app-promotion has started .I think this is redundant, because we only need `NextInterestBlock`.185	#[pallet::storage]186	pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;187188	/// Next target block when interest is recalculated189	#[pallet::storage]190	#[pallet::getter(fn get_interest_block)]191	pub type NextInterestBlock<T: Config> =192		StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;193194	/// Stores hash a record for which the last revenue recalculation was performed.195	/// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.196	#[pallet::storage]197	#[pallet::getter(fn get_next_calculated_record)]198	pub type NextCalculatedRecord<T: Config> =199		StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;200201	#[pallet::hooks]202	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {203		fn on_initialize(current_block: T::BlockNumber) -> Weight204		where205			<T as frame_system::Config>::BlockNumber: From<u32>,206		{207			let mut consumed_weight = 0;208			// let mut add_weight = |reads, writes, weight| {209			// 	consumed_weight += T::DbWeight::get().reads_writes(reads, writes);210			// 	consumed_weight += weight;211			// };212213			PendingUnstake::<T>::iter()214				.filter_map(|((staker, block), amount)| {215					if block <= current_block {216						Some((staker, block, amount))217					} else {218						None219					}220				})221				.for_each(|(staker, block, amount)| {222					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 treasuries223					<PendingUnstake<T>>::remove((staker, block));224				});225226			// let next_interest_block = Self::get_interest_block();227			// let current_relay_block = T::RelayBlockNumberProvider::current_block_number();228			// if next_interest_block != 0.into() && current_relay_block >= next_interest_block {229			// 	let mut acc = <BalanceOf<T>>::default();230			// 	let mut base_acc = <BalanceOf<T>>::default();231232			// 	NextInterestBlock::<T>::set(233			// 		NextInterestBlock::<T>::get() + T::RecalculationInterval::get(),234			// 	);235			// 	add_weight(0, 1, 0);236237			// 	Staked::<T>::iter()238			// 		.filter(|((_, block), _)| {239			// 			*block + T::RecalculationInterval::get() <= current_relay_block240			// 		})241			// 		.for_each(|((staker, block), amount)| {242			// 			Self::recalculate_stake(&staker, block, amount, &mut acc);243			// 			add_weight(0, 0, T::WeightInfo::recalculate_stake());244			// 			base_acc += amount;245			// 		});246			// 	<TotalStaked<T>>::get()247			// 		.checked_add(&acc)248			// 		.map(|res| <TotalStaked<T>>::set(res));249250			// 	Self::deposit_event(Event::StakingRecalculation(base_acc, acc));251			// 	add_weight(0, 1, 0);252			// } else {253			// 	add_weight(1, 0, 0)254			// };255			consumed_weight256		}257	}258259	#[pallet::call]260	impl<T: Config> Pallet<T>261	where262		T::BlockNumber: From<u32> + Into<u32>,263		<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,264	{265		#[pallet::weight(T::WeightInfo::set_admin_address())]266		pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {267			ensure_root(origin)?;268			<Admin<T>>::set(Some(admin.as_sub().to_owned()));269270			Ok(())271		}272273		#[pallet::weight(T::WeightInfo::start_app_promotion())]274		pub fn start_app_promotion(275			origin: OriginFor<T>,276			promotion_start_relay_block: Option<T::BlockNumber>,277		) -> DispatchResult278		where279			<T as frame_system::Config>::BlockNumber: From<u32>,280		{281			ensure_root(origin)?;282283			// Start app-promotion mechanics if it has not been yet initialized284			if <StartBlock<T>>::get() == 0u32.into() {285				let start_block = promotion_start_relay_block286					.unwrap_or(T::RelayBlockNumberProvider::current_block_number());287288				// Set promotion global start block289				<StartBlock<T>>::set(start_block);290291				<NextInterestBlock<T>>::set(start_block + T::RecalculationInterval::get());292			}293294			Ok(())295		}296297		#[pallet::weight(T::WeightInfo::stop_app_promotion())]298		pub fn stop_app_promotion(origin: OriginFor<T>) -> DispatchResult299		where300			<T as frame_system::Config>::BlockNumber: From<u32>,301		{302			ensure_root(origin)?;303304			if <StartBlock<T>>::get() != 0u32.into() {305				<StartBlock<T>>::set(T::BlockNumber::default());306				<NextInterestBlock<T>>::set(T::BlockNumber::default());307			}308309			Ok(())310		}311312		#[pallet::weight(T::WeightInfo::stake())]313		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {314			let staker_id = ensure_signed(staker)?;315316			ensure!(317				amount >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(),318				ArithmeticError::Underflow319			);320321			let balance =322				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);323324			ensure!(balance >= amount, ArithmeticError::Underflow);325326			<<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(327				&staker_id,328				amount,329				WithdrawReasons::all(),330				balance - amount,331			)?;332333			Self::add_lock_balance(&staker_id, amount)?;334335			let block_number = T::RelayBlockNumberProvider::current_block_number();336			let recalc_block = (block_number / T::RecalculationInterval::get() + 2u32.into())337				* T::RecalculationInterval::get();338339			<Staked<T>>::insert((&staker_id, block_number), {340				let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));341				balance_and_recalc_block.0 = balance_and_recalc_block342					.0343					.checked_add(&amount)344					.ok_or(ArithmeticError::Overflow)?;345				balance_and_recalc_block.1 = recalc_block;346				balance_and_recalc_block347			});348349			<TotalStaked<T>>::set(350				<TotalStaked<T>>::get()351					.checked_add(&amount)352					.ok_or(ArithmeticError::Overflow)?,353			);354355			Ok(())356		}357358		#[pallet::weight(T::WeightInfo::unstake())]359		pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {360			let staker_id = ensure_signed(staker)?;361362			let mut total_stakes = 0u64;363364			let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))365				.map(|(_, (amount, _))| {366					*&mut total_stakes += 1;367					amount368				})369				.sum();370371			let block =372				T::RelayBlockNumberProvider::current_block_number() + T::PendingInterval::get();373			<PendingUnstake<T>>::insert(374				(&staker_id, block),375				<PendingUnstake<T>>::get((&staker_id, block))376					.checked_add(&total_staked)377					.ok_or(ArithmeticError::Overflow)?,378			);379380			TotalStaked::<T>::set(381				TotalStaked::<T>::get()382					.checked_sub(&total_staked)383					.ok_or(ArithmeticError::Underflow)?,384			); // when error we should recover initial stake state for the staker385386			Ok(None.into())387388			// let staker_id = ensure_signed(staker)?;389390			// let mut stakes = Staked::<T>::iter_prefix((&staker_id,)).collect::<Vec<_>>();391392			// let total_staked = stakes393			// 	.iter()394			// 	.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);395396			// ensure!(total_staked >= amount, ArithmeticError::Underflow);397398			// <TotalStaked<T>>::set(399			// 	<TotalStaked<T>>::get()400			// 		.checked_sub(&amount)401			// 		.ok_or(ArithmeticError::Underflow)?,402			// );403404			// let block =405			// 	T::RelayBlockNumberProvider::current_block_number() + T::PendingInterval::get();406			// <PendingUnstake<T>>::insert(407			// 	(&staker_id, block),408			// 	<PendingUnstake<T>>::get((&staker_id, block))409			// 		.checked_add(&amount)410			// 		.ok_or(ArithmeticError::Overflow)?,411			// );412413			// stakes.sort_by_key(|(block, _)| *block);414415			// let mut acc_amount = amount;416			// let new_state = stakes417			// 	.into_iter()418			// 	.map_while(|(block, balance_per_block)| {419			// 		if acc_amount == <BalanceOf<T>>::default() {420			// 			return None;421			// 		}422			// 		if acc_amount <= balance_per_block {423			// 			let res = (block, balance_per_block - acc_amount, acc_amount);424			// 			acc_amount = <BalanceOf<T>>::default();425			// 			return Some(res);426			// 		} else {427			// 			acc_amount -= balance_per_block;428			// 			return Some((block, <BalanceOf<T>>::default(), acc_amount));429			// 		}430			// 	})431			// 	.collect::<Vec<_>>();432433			// new_state434			// 	.into_iter()435			// 	.for_each(|(block, to_staked, _to_pending)| {436			// 		if to_staked == <BalanceOf<T>>::default() {437			// 			<Staked<T>>::remove((&staker_id, block));438			// 		} else {439			// 			<Staked<T>>::insert((&staker_id, block), to_staked);440			// 		}441			// 	});442443			// Ok(())444		}445446		#[pallet::weight(T::WeightInfo::sponsor_collection())]447		pub fn sponsor_collection(448			admin: OriginFor<T>,449			collection_id: CollectionId,450		) -> DispatchResult {451			let admin_id = ensure_signed(admin)?;452			ensure!(453				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,454				Error::<T>::NoPermission455			);456457			T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)458		}459		#[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]460		pub fn stop_sponsoring_collection(461			admin: OriginFor<T>,462			collection_id: CollectionId,463		) -> DispatchResult {464			let admin_id = ensure_signed(admin)?;465466			ensure!(467				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,468				Error::<T>::NoPermission469			);470471			ensure!(472				T::CollectionHandler::get_sponsor(collection_id)?473					.ok_or(<Error<T>>::InvalidArgument)?474					== Self::account_id(),475				<Error<T>>::NoPermission476			);477			T::CollectionHandler::remove_collection_sponsor(collection_id)478		}479480		#[pallet::weight(T::WeightInfo::sponsor_contract())]481		pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {482			let admin_id = ensure_signed(admin)?;483484			ensure!(485				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,486				Error::<T>::NoPermission487			);488489			T::ContractHandler::set_sponsor(490				T::CrossAccountId::from_sub(Self::account_id()),491				contract_id,492			)493		}494495		#[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]496		pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {497			let admin_id = ensure_signed(admin)?;498499			ensure!(500				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,501				Error::<T>::NoPermission502			);503504			ensure!(505				T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?506					== T::CrossAccountId::from_sub(Self::account_id()),507				<Error<T>>::NoPermission508			);509			T::ContractHandler::remove_contract_sponsor(contract_id)510		}511512		#[pallet::weight(0)]513		pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {514			let admin_id = ensure_signed(admin)?;515516			ensure!(517				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,518				Error::<T>::NoPermission519			);520521			let current_recalc_block =522				Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());523			let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();524525			let mut storage_iterator = Self::get_next_calculated_key()526				.map_or(Staked::<T>::iter().skip(0), |key| {527					Staked::<T>::iter_from(key).skip(1)528				});529530			NextCalculatedRecord::<T>::set(None);531532			{533				let mut stakers_number = stakers_number.unwrap_or(20);534				let mut current_id = admin_id;535				let mut income_acc = BalanceOf::<T>::default();536537				while let Some(((id, staked_block), (amount, next_recalc_block_for_stake))) =538					storage_iterator.next()539				{540					if current_id != id {541						if income_acc != BalanceOf::<T>::default() {542							<T::Currency as Currency<T::AccountId>>::transfer(543								&T::TreasuryAccountId::get(),544								&current_id,545								income_acc,546								ExistenceRequirement::KeepAlive,547							)548							.and_then(|_| Self::add_lock_balance(&current_id, income_acc))?;549550							Self::deposit_event(Event::StakingRecalculation(551								current_id, amount, income_acc,552							));553						}554555						stakers_number -= 1;556						if stakers_number == 0 {557							NextCalculatedRecord::<T>::set(Some((id, staked_block)));558							break;559						}560						income_acc = BalanceOf::<T>::default();561						current_id = id;562					};563					if next_recalc_block_for_stake >= current_recalc_block {564						Self::recalculate_and_insert_stake(565							&current_id,566							staked_block,567							next_recalc_block,568							amount,569							((next_recalc_block_for_stake - current_recalc_block)570								/ T::RecalculationInterval::get())571							.into() + 1,572							&mut income_acc,573						);574					}575				}576			}577578			Ok(())579		}580	}581}582583impl<T: Config> Pallet<T> {584	pub fn account_id() -> T::AccountId {585		T::PalletId::get().into_account_truncating()586	}587588	fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {589		let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();590		locked_balance -= amount;591		Self::set_lock_unchecked(staker, locked_balance);592	}593594	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {595		Self::get_locked_balance(staker)596			.map_or(<BalanceOf<T>>::default(), |l| l.amount)597			.checked_add(&amount)598			.map(|new_lock| Self::set_lock_unchecked(staker, new_lock))599			.ok_or(ArithmeticError::Overflow.into())600	}601602	fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {603		<T::Currency as LockableCurrency<T::AccountId>>::set_lock(604			LOCK_IDENTIFIER,605			staker,606			amount,607			WithdrawReasons::all(),608		)609	}610611	pub fn get_locked_balance(612		staker: impl EncodeLike<T::AccountId>,613	) -> Option<BalanceLock<BalanceOf<T>>> {614		<T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)615			.into_iter()616			.find(|l| l.id == LOCK_IDENTIFIER)617	}618619	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {620		let staked = Staked::<T>::iter_prefix((staker,))621			.into_iter()622			.fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {623				acc + amount624			});625		if staked != <BalanceOf<T>>::default() {626			Some(staked)627		} else {628			None629		}630	}631632	pub fn total_staked_by_id_per_block(633		staker: impl EncodeLike<T::AccountId>,634	) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {635		let mut staked = Staked::<T>::iter_prefix((staker,))636			.into_iter()637			.map(|(block, (amount, _))| (block, amount))638			.collect::<Vec<_>>();639		staked.sort_by_key(|(block, _)| *block);640		if !staked.is_empty() {641			Some(staked)642		} else {643			None644		}645	}646647	pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {648		staker.map_or(Some(<TotalStaked<T>>::get()), |s| {649			Self::total_staked_by_id(s.as_sub())650		})651		// Self::total_staked_by_id(staker.as_sub())652	}653654	pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {655		Self::get_locked_balance(staker.as_sub())656			.map(|l| l.amount)657			.unwrap_or_default()658	}659660	pub fn cross_id_total_staked_per_block(661		staker: T::CrossAccountId,662	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {663		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()664	}665666	fn recalculate_and_insert_stake(667		staker: &T::AccountId,668		staked_block: T::BlockNumber,669		next_recalc_block: T::BlockNumber,670		base: BalanceOf<T>,671		iters: u32,672		income_acc: &mut BalanceOf<T>,673	) {674		let income = Self::calculate_income(base, iters);675676		base.checked_add(&income).map(|res| {677			<Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));678			*income_acc += income;679		});680	}681682	fn calculate_income<I>(base: I, iters: u32) -> I683	where684		I: EncodeLike<BalanceOf<T>> + Balance,685	{686		let mut income = base;687688		(0..iters).for_each(|_| income += T::IntervalIncome::get() * income);689690		income - base691	}692693	fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {694		(current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()695	}696697	// fn get_next_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {698	// 	Self::get_current_recalc_block(current_relay_block) + T::RecalculationInterval::get()699	// }700701	fn get_next_calculated_key() -> Option<Vec<u8>> {702		Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))703	}704}705706impl<T: Config> Pallet<T>707where708	<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,709{710	pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {711		staker.map_or(PendingUnstake::<T>::iter_values().sum(), |s| {712			PendingUnstake::<T>::iter_prefix_values((s.as_sub(),)).sum()713		})714	}715716	pub fn cross_id_pending_unstake_per_block(717		staker: T::CrossAccountId,718	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {719		let mut unsorted_res = PendingUnstake::<T>::iter_prefix((staker.as_sub(),))720			.into_iter()721			.collect::<Vec<_>>();722		unsorted_res.sort_by_key(|(block, _)| *block);723		unsorted_res724	}725}
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-30, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2022-08-31, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
 
 // Executed Command:
@@ -26,6 +26,7 @@
 #![cfg_attr(rustfmt, rustfmt_skip)]
 #![allow(unused_parens)]
 #![allow(unused_imports)]
+#![allow(missing_docs)]
 #![allow(clippy::unnecessary_cast)]
 
 use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
@@ -39,7 +40,6 @@
 	fn payout_stakers() -> Weight;
 	fn stake() -> Weight;
 	fn unstake() -> Weight;
-	fn recalculate_stake() -> Weight;
 	fn sponsor_collection() -> Weight;
 	fn stop_sponsoring_collection() -> Weight;
 	fn sponsor_contract() -> Weight;
@@ -53,71 +53,75 @@
 	// Storage: ParachainSystem ValidationData (r:1 w:0)
 	// Storage: Promotion NextInterestBlock (r:0 w:1)
 	fn start_app_promotion() -> Weight {
-		(2_299_000 as 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 {
-		(1_733_000 as 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 {
-		(553_000 as Weight)
+		(1_203_000 as Weight)
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Promotion Admin (r:1 w:0)
+	// Storage: ParachainSystem ValidationData (r:1 w:0)
+	// Storage: Promotion NextCalculatedRecord (r:1 w:1)
+	// Storage: Promotion Staked (r:2 w:0)
 	fn payout_stakers() -> Weight {
-		(1_398_000 as Weight)
-			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+		(10_859_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: 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 {
-		(9_506_000 as Weight)
-			.saturating_add(T::DbWeight::get().reads(4 as Weight))
-			.saturating_add(T::DbWeight::get().writes(3 as Weight))
+		(14_789_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(5 as Weight))
+			.saturating_add(T::DbWeight::get().writes(4 as Weight))
 	}
-	// Storage: System Account (r:1 w:0)
+	// 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)
 	fn unstake() -> Weight {
-		(2_529_000 as Weight)
-			.saturating_add(T::DbWeight::get().reads(1 as Weight))
-	}
-	// Storage: System Account (r:1 w:0)
-	fn recalculate_stake() -> Weight {
-		(2_203_000 as Weight)
-			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+		(16_889_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(5 as Weight))
+			.saturating_add(T::DbWeight::get().writes(3 as Weight))
 	}
 	// Storage: Promotion Admin (r:1 w:0)
 	// Storage: Common CollectionById (r:1 w:1)
 	fn sponsor_collection() -> Weight {
-		(10_882_000 as Weight)
+		(18_377_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 {
-		(10_544_000 as Weight)
+		(13_989_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 {
-		(2_163_000 as Weight)
+		(4_162_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 {
-		(3_511_000 as Weight)
+		(5_457_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
@@ -129,71 +133,75 @@
 	// Storage: ParachainSystem ValidationData (r:1 w:0)
 	// Storage: Promotion NextInterestBlock (r:0 w:1)
 	fn start_app_promotion() -> Weight {
-		(2_299_000 as 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 {
-		(1_733_000 as 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 {
-		(553_000 as Weight)
+		(1_203_000 as Weight)
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Promotion Admin (r:1 w:0)
+	// Storage: ParachainSystem ValidationData (r:1 w:0)
+	// Storage: Promotion NextCalculatedRecord (r:1 w:1)
+	// Storage: Promotion Staked (r:2 w:0)
 	fn payout_stakers() -> Weight {
-		(1_398_000 as Weight)
-			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+		(10_859_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: 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 {
-		(9_506_000 as Weight)
-			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
-			.saturating_add(RocksDbWeight::get().writes(3 as Weight))
+		(14_789_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(4 as Weight))
 	}
-	// Storage: System Account (r:1 w:0)
+	// 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)
 	fn unstake() -> Weight {
-		(2_529_000 as Weight)
-			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
-	}
-	// Storage: System Account (r:1 w:0)
-	fn recalculate_stake() -> Weight {
-		(2_203_000 as Weight)
-			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+		(16_889_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(3 as Weight))
 	}
 	// Storage: Promotion Admin (r:1 w:0)
 	// Storage: Common CollectionById (r:1 w:1)
 	fn sponsor_collection() -> Weight {
-		(10_882_000 as Weight)
+		(18_377_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 {
-		(10_544_000 as Weight)
+		(13_989_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 {
-		(2_163_000 as Weight)
+		(4_162_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 {
-		(3_511_000 as Weight)
+		(5_457_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}