git.delta.rocks / unique-network / refs/commits / 97044329ec08

difftreelog

fix logic payout

PraetorP2022-09-02parent: #61a1841.patch.diff
in: master

3 files changed

modifiedclient/rpc/src/lib.rsdiffbeforeafterboth
--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -424,7 +424,7 @@
 			client: Arc<C>,
 			_marker: std::marker::PhantomData<P>,
 		}
-		
+
 		impl<C, P> $name<C, P> {
 			pub fn new(client: Arc<C>) -> Self {
 				Self {
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::{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()); // TO-DO374			}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			);391392			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(), |key| {478					Staked::<T>::iter_from(key)479				});480481			NextCalculatedRecord::<T>::set(None);482483			{484				let mut stakers_number = stakers_number.unwrap_or(20);485				let mut last_id = admin_id;486				let mut income_acc = BalanceOf::<T>::default();487488				while let Some(((current_id, staked_block), (amount, next_recalc_block_for_stake))) =489					storage_iterator.next()490				{491					if last_id != current_id {492						if income_acc != BalanceOf::<T>::default() {493							<T::Currency as Currency<T::AccountId>>::transfer(494								&T::TreasuryAccountId::get(),495								&last_id,496								income_acc,497								ExistenceRequirement::KeepAlive,498							)499							.and_then(|_| Self::add_lock_balance(&last_id, income_acc))?;500501							Self::deposit_event(Event::StakingRecalculation(502								last_id, amount, income_acc,503							));504						}505506						if stakers_number == 0 {507							NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));508							break;509						}510						stakers_number -= 1;511						income_acc = BalanceOf::<T>::default();512						last_id = current_id;513					};514					if current_recalc_block >= next_recalc_block_for_stake {515						Self::recalculate_and_insert_stake(516							&last_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}
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	cell::RefCell,45};46use sp_core::H160;47use codec::EncodeLike;48use pallet_balances::BalanceLock;49pub use types::*;5051// use up_common::constants::{DAYS, UNIQUE};52use up_data_structs::CollectionId;5354use frame_support::{55	dispatch::{DispatchResult},56	traits::{57		Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,58	},59	ensure,60};6162use weights::WeightInfo;6364pub use pallet::*;65use pallet_evm::account::CrossAccountId;66use sp_runtime::{67	Perbill,68	traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion, Zero},69	ArithmeticError,70};7172pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";73const PENDING_LIMIT_PER_BLOCK: u32 = 3;7475type BalanceOf<T> =76	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;7778// const SECONDS_TO_BLOCK: u32 = 6;79// const DAY: u32 = 60 * 60 * 24 / SECONDS_TO_BLOCK;80// const WEEK: u32 = 7 * DAY;81// const TWO_WEEK: u32 = 2 * WEEK;82// const YEAR: u32 = DAY * 365;8384#[frame_support::pallet]85pub mod pallet {86	use super::*;87	use frame_support::{88		Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId,89		traits::ReservableCurrency,90	};91	use frame_system::pallet_prelude::*;9293	#[pallet::config]94	pub trait Config: frame_system::Config + pallet_evm::account::Config {95		type Currency: ExtendedLockableCurrency<Self::AccountId>96			+ ReservableCurrency<Self::AccountId>;9798		type CollectionHandler: CollectionHandler<99			AccountId = Self::AccountId,100			CollectionId = CollectionId,101		>;102103		type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;104105		type TreasuryAccountId: Get<Self::AccountId>;106107		/// The app's pallet id, used for deriving its sovereign account ID.108		#[pallet::constant]109		type PalletId: Get<PalletId>;110111		/// In relay blocks.112		#[pallet::constant]113		type RecalculationInterval: Get<Self::BlockNumber>;114		/// In relay blocks.115		#[pallet::constant]116		type PendingInterval: Get<Self::BlockNumber>;117118		/// In chain blocks.119		#[pallet::constant]120		type Day: Get<Self::BlockNumber>; // useless121122		#[pallet::constant]123		type Nominal: Get<BalanceOf<Self>>;124125		#[pallet::constant]126		type IntervalIncome: Get<Perbill>;127128		/// Weight information for extrinsics in this pallet.129		type WeightInfo: WeightInfo;130131		// The relay block number provider132		type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;133134		/// Events compatible with [`frame_system::Config::Event`].135		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;136	}137138	#[pallet::pallet]139	#[pallet::generate_store(pub(super) trait Store)]140	pub struct Pallet<T>(_);141142	#[pallet::event]143	#[pallet::generate_deposit(fn deposit_event)]144	pub enum Event<T: Config> {145		StakingRecalculation(146			/// An recalculated staker147			T::AccountId,148			/// Base on which interest is calculated149			BalanceOf<T>,150			/// Amount of accrued interest151			BalanceOf<T>,152		),153	}154155	#[pallet::error]156	pub enum Error<T> {157		/// Error due to action requiring admin to be set158		AdminNotSet,159		/// No permission to perform an action160		NoPermission,161		/// Insufficient funds to perform an action162		NotSufficientFounds,163		PendingForBlockOverflow,164		/// An error related to the fact that an invalid argument was passed to perform an action165		InvalidArgument,166	}167168	#[pallet::storage]169	pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;170171	#[pallet::storage]172	pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;173174	/// Amount of tokens staked by account in the blocknumber.175	#[pallet::storage]176	pub type Staked<T: Config> = StorageNMap<177		Key = (178			Key<Blake2_128Concat, T::AccountId>,179			Key<Twox64Concat, T::BlockNumber>,180		),181		Value = (BalanceOf<T>, T::BlockNumber),182		QueryKind = ValueQuery,183	>;184	/// Amount of stakes for an Account185	#[pallet::storage]186	pub type StakesPerAccount<T: Config> =187		StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;188189	/// Amount of tokens pending unstake per user per block.190	// #[pallet::storage]191	// pub type PendingUnstake<T: Config> = StorageNMap<192	// 	Key = (193	// 		Key<Blake2_128Concat, T::AccountId>,194	// 		Key<Twox64Concat, T::BlockNumber>,195	// 	),196	// 	Value = BalanceOf<T>,197	// 	QueryKind = ValueQuery,198	// >;199	#[pallet::storage]200	pub type PendingUnstake<T: Config> = StorageMap<201		_,202		Twox64Concat,203		T::BlockNumber,204		BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,205		ValueQuery,206	>;207208	/// A block when app-promotion has started .I think this is redundant, because we only need `NextInterestBlock`.209	#[pallet::storage]210	pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;211212	// /// Next target block when interest is recalculated213	// #[pallet::storage]214	// #[pallet::getter(fn get_interest_block)]215	// pub type NextInterestBlock<T: Config> =216	// 	StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;217218	/// Stores hash a record for which the last revenue recalculation was performed.219	/// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.220	#[pallet::storage]221	#[pallet::getter(fn get_next_calculated_record)]222	pub type NextCalculatedRecord<T: Config> =223		StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;224225	#[pallet::hooks]226	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {227		fn on_initialize(current_block_number: T::BlockNumber) -> Weight228		where229			<T as frame_system::Config>::BlockNumber: From<u32>,230		{231			let mut consumed_weight = 0;232			let mut add_weight = |reads, writes, weight| {233				consumed_weight += T::DbWeight::get().reads_writes(reads, writes);234				consumed_weight += weight;235			};236237			let block_pending = PendingUnstake::<T>::take(current_block_number);238239			add_weight(0, 1, 0);240241			if !block_pending.is_empty() {242				block_pending.into_iter().for_each(|(staker, amount)| {243					<T::Currency as ReservableCurrency<T::AccountId>>::unreserve(&staker, amount);244				});245			}246247			consumed_weight248		}249	}250251	#[pallet::call]252	impl<T: Config> Pallet<T>253	where254		T::BlockNumber: From<u32> + Into<u32>,255		<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,256	{257		#[pallet::weight(T::WeightInfo::set_admin_address())]258		pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {259			ensure_root(origin)?;260			<Admin<T>>::set(Some(admin.as_sub().to_owned()));261262			Ok(())263		}264265		// #[pallet::weight(T::WeightInfo::start_app_promotion())]266		// pub fn start_app_promotion(267		// 	origin: OriginFor<T>,268		// 	promotion_start_relay_block: Option<T::BlockNumber>,269		// ) -> DispatchResult270		// where271		// 	<T as frame_system::Config>::BlockNumber: From<u32>,272		// {273		// 	ensure_root(origin)?;274275		// 	// Start app-promotion mechanics if it has not been yet initialized276		// 	if <StartBlock<T>>::get() == 0u32.into() {277		// 		let start_block = promotion_start_relay_block278		// 			.unwrap_or(T::RelayBlockNumberProvider::current_block_number());279280		// 		// Set promotion global start block281		// 		<StartBlock<T>>::set(start_block);282283		// 		<NextInterestBlock<T>>::set(start_block + T::RecalculationInterval::get());284		// 	}285286		// 	Ok(())287		// }288289		// #[pallet::weight(T::WeightInfo::stop_app_promotion())]290		// pub fn stop_app_promotion(origin: OriginFor<T>) -> DispatchResult291		// where292		// 	<T as frame_system::Config>::BlockNumber: From<u32>,293		// {294		// 	ensure_root(origin)?;295296		// 	if <StartBlock<T>>::get() != 0u32.into() {297		// 		<StartBlock<T>>::set(T::BlockNumber::default());298		// 		<NextInterestBlock<T>>::set(T::BlockNumber::default());299		// 	}300301		// 	Ok(())302		// }303304		#[pallet::weight(T::WeightInfo::stake())]305		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {306			let staker_id = ensure_signed(staker)?;307308			ensure!(309				StakesPerAccount::<T>::get(&staker_id) < 10,310				Error::<T>::NoPermission311			);312313			ensure!(314				amount >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(),315				ArithmeticError::Underflow316			);317318			let balance =319				<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);320321			ensure!(balance >= amount, ArithmeticError::Underflow);322323			<<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(324				&staker_id,325				amount,326				WithdrawReasons::all(),327				balance - amount,328			)?;329330			Self::add_lock_balance(&staker_id, amount)?;331332			let block_number = T::RelayBlockNumberProvider::current_block_number();333			let recalc_block = (block_number / T::RecalculationInterval::get() + 2u32.into())334				* T::RecalculationInterval::get();335336			<Staked<T>>::insert((&staker_id, block_number), {337				let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));338				balance_and_recalc_block.0 = balance_and_recalc_block339					.0340					.checked_add(&amount)341					.ok_or(ArithmeticError::Overflow)?;342				balance_and_recalc_block.1 = recalc_block;343				balance_and_recalc_block344			});345346			<TotalStaked<T>>::set(347				<TotalStaked<T>>::get()348					.checked_add(&amount)349					.ok_or(ArithmeticError::Overflow)?,350			);351352			StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);353			Ok(())354		}355356		#[pallet::weight(T::WeightInfo::unstake())]357		pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {358			let staker_id = ensure_signed(staker)?;359			let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();360			let mut pendings = <PendingUnstake<T>>::get(block);361362			ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);363364			let mut total_stakes = 0u64;365366			let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))367				.map(|(_, (amount, _))| {368					total_stakes += 1;369					amount370				})371				.sum();372373			if total_staked.is_zero() {374				return Ok(None.into()); // TO-DO375			}376377			pendings378				.try_push((staker_id.clone(), total_staked))379				.map_err(|_| Error::<T>::PendingForBlockOverflow)?;380381			<PendingUnstake<T>>::insert(block, pendings);382383			Self::unlock_balance_unchecked(&staker_id, total_staked);384385			<T::Currency as ReservableCurrency<T::AccountId>>::reserve(&staker_id, total_staked)?;386387			TotalStaked::<T>::set(388				TotalStaked::<T>::get()389					.checked_sub(&total_staked)390					.ok_or(ArithmeticError::Underflow)?,391			);392393			StakesPerAccount::<T>::remove(&staker_id);394395			Ok(None.into())396		}397398		#[pallet::weight(T::WeightInfo::sponsor_collection())]399		pub fn sponsor_collection(400			admin: OriginFor<T>,401			collection_id: CollectionId,402		) -> DispatchResult {403			let admin_id = ensure_signed(admin)?;404			ensure!(405				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,406				Error::<T>::NoPermission407			);408409			T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)410		}411		#[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]412		pub fn stop_sponsoring_collection(413			admin: OriginFor<T>,414			collection_id: CollectionId,415		) -> DispatchResult {416			let admin_id = ensure_signed(admin)?;417418			ensure!(419				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,420				Error::<T>::NoPermission421			);422423			ensure!(424				T::CollectionHandler::get_sponsor(collection_id)?425					.ok_or(<Error<T>>::InvalidArgument)?426					== Self::account_id(),427				<Error<T>>::NoPermission428			);429			T::CollectionHandler::remove_collection_sponsor(collection_id)430		}431432		#[pallet::weight(T::WeightInfo::sponsor_contract())]433		pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {434			let admin_id = ensure_signed(admin)?;435436			ensure!(437				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,438				Error::<T>::NoPermission439			);440441			T::ContractHandler::set_sponsor(442				T::CrossAccountId::from_sub(Self::account_id()),443				contract_id,444			)445		}446447		#[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]448		pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {449			let admin_id = ensure_signed(admin)?;450451			ensure!(452				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,453				Error::<T>::NoPermission454			);455456			ensure!(457				T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?458					== T::CrossAccountId::from_sub(Self::account_id()),459				<Error<T>>::NoPermission460			);461			T::ContractHandler::remove_contract_sponsor(contract_id)462		}463464		#[pallet::weight(0)]465		pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {466			let admin_id = ensure_signed(admin)?;467468			ensure!(469				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,470				Error::<T>::NoPermission471			);472473			let current_recalc_block =474				Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());475			let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();476477			let mut storage_iterator = Self::get_next_calculated_key()478				.map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));479480			NextCalculatedRecord::<T>::set(None);481482			// {483			// 	let mut stakers_number = stakers_number.unwrap_or(20);484			// 	let mut last_id = admin_id;485			// 	let mut income_acc = BalanceOf::<T>::default();486			// 	let mut amount_acc = BalanceOf::<T>::default();487488			// 	while let Some((489			// 		(current_id, staked_block),490			// 		(amount, next_recalc_block_for_stake),491			// 	)) = storage_iterator.next()492			// 	{493			// 		if last_id != current_id {494			// 			if income_acc != BalanceOf::<T>::default() {495			// 				<T::Currency as Currency<T::AccountId>>::transfer(496			// 					&T::TreasuryAccountId::get(),497			// 					&last_id,498			// 					income_acc,499			// 					ExistenceRequirement::KeepAlive,500			// 				)501			// 				.and_then(|_| Self::add_lock_balance(&last_id, income_acc))?;502503			// 				Self::deposit_event(Event::StakingRecalculation(504			// 					last_id, amount, income_acc,505			// 				));506			// 			}507508			// 			if stakers_number == 0 {509			// 				NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));510			// 				break;511			// 			}512			// 			stakers_number -= 1;513			// 			income_acc = BalanceOf::<T>::default();514			// 			last_id = current_id;515			// 		};516			// 		if current_recalc_block >= next_recalc_block_for_stake {517			// 			Self::recalculate_and_insert_stake(518			// 				&last_id,519			// 				staked_block,520			// 				next_recalc_block,521			// 				amount,522			// 				((current_recalc_block - next_recalc_block_for_stake)523			// 					/ T::RecalculationInterval::get())524			// 				.into() + 1,525			// 				&mut income_acc,526			// 			);527			// 		}528			// 	}529			// }530			531			{532				let mut stakers_number = stakers_number.unwrap_or(20);533				let  last_id = RefCell::new(None);534				let income_acc = RefCell::new(BalanceOf::<T>::default());535				let amount_acc = RefCell::new(BalanceOf::<T>::default());536				537				let flush_stake = || -> DispatchResult {538					if let Some(last_id) = &*last_id.borrow() {539						if !income_acc.borrow().is_zero() {540							<T::Currency as Currency<T::AccountId>>::transfer(541								&T::TreasuryAccountId::get(),542								last_id,543								*income_acc.borrow(),544								ExistenceRequirement::KeepAlive,545							)546							.and_then(|_| Self::add_lock_balance(last_id, *income_acc.borrow()))?;547548							Self::deposit_event(Event::StakingRecalculation(549								last_id.clone(),550								*amount_acc.borrow(),551								*income_acc.borrow(),552							));553						}554555						*income_acc.borrow_mut() = BalanceOf::<T>::default();556						*amount_acc.borrow_mut() = BalanceOf::<T>::default();557					}558					Ok(())559				};560561				while let Some((562					(current_id, staked_block),563					(amount, next_recalc_block_for_stake),564				)) = storage_iterator.next()565				{566					if stakers_number == 0 {567						NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));568						break;569					}570					stakers_number -= 1;571					if last_id.borrow().as_ref() != Some(&current_id) {572						flush_stake()?;573					};574					*last_id.borrow_mut() = Some(current_id.clone());575					if current_recalc_block >= next_recalc_block_for_stake {576						*amount_acc.borrow_mut() += amount;  577						Self::recalculate_and_insert_stake(578							&current_id,579							staked_block,580							next_recalc_block,581							amount,582							((current_recalc_block - next_recalc_block_for_stake)583								/ T::RecalculationInterval::get())584							.into() + 1,585							&mut *income_acc.borrow_mut(),586						);587					}588				}589				flush_stake()?;590			}591592593			Ok(())594		}595	}596}597598impl<T: Config> Pallet<T> {599	pub fn account_id() -> T::AccountId {600		T::PalletId::get().into_account_truncating()601	}602603	fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {604		let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();605		locked_balance -= amount;606		Self::set_lock_unchecked(staker, locked_balance);607	}608609	fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {610		Self::get_locked_balance(staker)611			.map_or(<BalanceOf<T>>::default(), |l| l.amount)612			.checked_add(&amount)613			.map(|new_lock| Self::set_lock_unchecked(staker, new_lock))614			.ok_or(ArithmeticError::Overflow.into())615	}616617	fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {618		if amount.is_zero() {619			<T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);620		} else {621			<T::Currency as LockableCurrency<T::AccountId>>::set_lock(622				LOCK_IDENTIFIER,623				staker,624				amount,625				WithdrawReasons::all(),626			)627		}628	}629630	pub fn get_locked_balance(631		staker: impl EncodeLike<T::AccountId>,632	) -> Option<BalanceLock<BalanceOf<T>>> {633		<T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)634			.into_iter()635			.find(|l| l.id == LOCK_IDENTIFIER)636	}637638	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {639		let staked = Staked::<T>::iter_prefix((staker,))640			.into_iter()641			.fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {642				acc + amount643			});644		if staked != <BalanceOf<T>>::default() {645			Some(staked)646		} else {647			None648		}649	}650651	pub fn total_staked_by_id_per_block(652		staker: impl EncodeLike<T::AccountId>,653	) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {654		let mut staked = Staked::<T>::iter_prefix((staker,))655			.into_iter()656			.map(|(block, (amount, _))| (block, amount))657			.collect::<Vec<_>>();658		staked.sort_by_key(|(block, _)| *block);659		if !staked.is_empty() {660			Some(staked)661		} else {662			None663		}664	}665666	pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {667		staker.map_or(Some(<TotalStaked<T>>::get()), |s| {668			Self::total_staked_by_id(s.as_sub())669		})670		// Self::total_staked_by_id(staker.as_sub())671	}672673	pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {674		Self::get_locked_balance(staker.as_sub())675			.map(|l| l.amount)676			.unwrap_or_default()677	}678679	pub fn cross_id_total_staked_per_block(680		staker: T::CrossAccountId,681	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {682		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()683	}684685	fn recalculate_and_insert_stake(686		staker: &T::AccountId,687		staked_block: T::BlockNumber,688		next_recalc_block: T::BlockNumber,689		base: BalanceOf<T>,690		iters: u32,691		income_acc: &mut BalanceOf<T>,692	) {693		let income = Self::calculate_income(base, iters);694695		base.checked_add(&income).map(|res| {696			<Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));697			*income_acc += income;698		});699	}700701	fn calculate_income<I>(base: I, iters: u32) -> I702	where703		I: EncodeLike<BalanceOf<T>> + Balance,704	{705		let mut income = base;706707		(0..iters).for_each(|_| income += T::IntervalIncome::get() * income);708709		income - base710	}711712	fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {713		(current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()714	}715716	// fn get_next_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {717	// 	Self::get_current_recalc_block(current_relay_block) + T::RecalculationInterval::get()718	// }719720	fn get_next_calculated_key() -> Option<Vec<u8>> {721		Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))722	}723}724725impl<T: Config> Pallet<T>726where727	<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,728{729	pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {730		staker.map_or(731			PendingUnstake::<T>::iter_values()732				.flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))733				.sum(),734			|s| {735				PendingUnstake::<T>::iter_values()736					.flatten()737					.filter_map(|(id, amount)| {738						if id == *s.as_sub() {739							Some(amount)740						} else {741							None742						}743					})744					.sum()745			},746		)747	}748749	pub fn cross_id_pending_unstake_per_block(750		staker: T::CrossAccountId,751	) -> Vec<(T::BlockNumber, BalanceOf<T>)> {752		let mut unsorted_res = vec![];753		PendingUnstake::<T>::iter().for_each(|(block, pendings)| {754			pendings.into_iter().for_each(|(id, amount)| {755				if id == *staker.as_sub() {756					unsorted_res.push((block, amount));757				};758			})759		});760761		unsorted_res.sort_by_key(|(block, _)| *block);762		unsorted_res763	}764}
modifiedruntime/common/config/pallets/app_promotion.rsdiffbeforeafterboth
--- a/runtime/common/config/pallets/app_promotion.rs
+++ b/runtime/common/config/pallets/app_promotion.rs
@@ -30,7 +30,7 @@
 parameter_types! {
 	pub const AppPromotionId: PalletId = PalletId(*b"appstake");
 	pub const RecalculationInterval: BlockNumber = 20;
-	pub const PendingInterval: BlockNumber = 20;
+	pub const PendingInterval: BlockNumber = 10;
 	pub const Nominal: Balance = UNIQUE;
 	pub const Day: BlockNumber = DAYS;
 	pub IntervalIncome: Perbill = Perbill::from_rational(RecalculationInterval::get(), RELAY_DAYS) * Perbill::from_rational(5u32, 10_000);