git.delta.rocks / unique-network / refs/commits / bfab6035a123

difftreelog

source

pallets/app-promotion/src/lib.rs33.4 KiBsourcehistory
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 Promotion pallet18//!19//! The pallet implements the mechanics of staking and sponsoring collections/contracts.20//!21//! - [`Config`]22//! - [`Pallet`]23//! - [`Error`]24//! - [`Event`]25//!26//! ## Overview27//! The App Promotion pallet allows fund holders to stake at a certain daily rate of return.28//! The mechanics implemented in the pallet allow it to act as a sponsor for collections / contracts,29//! the list of which is set by the pallet administrator.30//!31//!32//! ## Interface33//! The pallet provides interfaces for funds, collection/contract operations (see [types] module).3435//!36//! ### Dispatchable Functions37//! - [`set_admin_address`][`Pallet::set_admin_address`] - sets an address as the the admin.38//! - [`stake`][`Pallet::stake`] - stakes the amount of native tokens.39//! - [`unstake`][`Pallet::unstake`] - unstakes all stakes.40//! - [`sponsor_collection`][`Pallet::sponsor_collection`] - sets the pallet to be the sponsor for the collection.41//! - [`stop_sponsoring_collection`][`Pallet::stop_sponsoring_collection`] - removes the pallet as the sponsor for the collection.42//! - [`sponsor_contract`][`Pallet::sponsor_contract`] - sets the pallet to be the sponsor for the contract.43//! - [`stop_sponsoring_contract`][`Pallet::stop_sponsoring_contract`] - removes the pallet as the sponsor for the contract.44//! - [`payout_stakers`][`Pallet::payout_stakers`] - recalculates interest for the specified number of stakers.45//!4647// #![recursion_limit = "1024"]48#![cfg_attr(not(feature = "std"), no_std)]4950#[cfg(feature = "runtime-benchmarks")]51mod benchmarking;5253pub mod types;54pub mod weights;5556use frame_support::{57	dispatch::DispatchResult,58	ensure,59	pallet_prelude::*,60	storage::Key,61	traits::{62		fungible::{Inspect, InspectFreeze, Mutate, MutateFreeze},63		tokens::Balance,64		Get,65	},66	weights::Weight,67	Blake2_128Concat, BoundedVec, PalletId, Twox64Concat,68};69use frame_system::pallet_prelude::*;70pub use pallet::*;71use pallet_evm::account::CrossAccountId;72use parity_scale_codec::EncodeLike;73use sp_core::H160;74use sp_runtime::{75	traits::{AccountIdConversion, BlockNumberProvider, CheckedAdd, CheckedSub, Zero},76	ArithmeticError, DispatchError, Perbill,77};78use sp_std::{borrow::ToOwned, cell::RefCell, iter::Sum, vec, vec::Vec};79pub use types::*;80use up_data_structs::CollectionId;81use weights::WeightInfo;8283const PENDING_LIMIT_PER_BLOCK: u32 = 3;8485type BalanceOf<T> =86	<<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;8788#[frame_support::pallet]89pub mod pallet {9091	use super::*;9293	#[pallet::config]94	pub trait Config:95		frame_system::Config + pallet_evm::Config + pallet_configuration::Config96	{97		/// Type to interact with the native token98		type Currency: MutateFreeze<Self::AccountId> + Mutate<Self::AccountId>;99100		/// Type for interacting with collections101		type CollectionHandler: CollectionHandler<102			AccountId = Self::AccountId,103			CollectionId = CollectionId,104		>;105106		/// Type for interacting with conrtacts107		type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;108109		/// `AccountId` for treasury110		type TreasuryAccountId: Get<Self::AccountId>;111112		/// The app's pallet id, used for deriving its sovereign account address.113		#[pallet::constant]114		type PalletId: Get<PalletId>;115116		/// Freeze identifier used by the pallet117		#[pallet::constant]118		type FreezeIdentifier: Get<119			<<Self as Config>::Currency as InspectFreeze<Self::AccountId>>::Id,120		>;121122		/// In relay blocks.123		#[pallet::constant]124		type RecalculationInterval: Get<BlockNumberFor<Self>>;125126		/// In parachain blocks.127		#[pallet::constant]128		type PendingInterval: Get<BlockNumberFor<Self>>;129130		/// Rate of return for interval in blocks defined in `RecalculationInterval`.131		#[pallet::constant]132		type IntervalIncome: Get<Perbill>;133134		/// Decimals for the `Currency`.135		#[pallet::constant]136		type Nominal: Get<BalanceOf<Self>>;137138		/// Maintenance mode status.139		type IsMaintenanceModeEnabled: Get<bool>;140141		/// Weight information for extrinsics in this pallet.142		type WeightInfo: WeightInfo;143144		// The relay block number provider145		type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = BlockNumberFor<Self>>;146147		/// Events compatible with [`frame_system::Config::Event`].148		type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;149	}150151	#[pallet::pallet]152	pub struct Pallet<T>(_);153154	#[pallet::event]155	#[pallet::generate_deposit(pub(super) fn deposit_event)]156	pub enum Event<T: Config> {157		/// Staking recalculation was performed158		///159		/// # Arguments160		/// * AccountId: account of the staker.161		/// * Balance : recalculation base162		/// * Balance : total income163		StakingRecalculation(164			/// An recalculated staker165			T::AccountId,166			/// Base on which interest is calculated167			BalanceOf<T>,168			/// Amount of accrued interest169			BalanceOf<T>,170		),171172		/// Staking was performed173		///174		/// # Arguments175		/// * AccountId: account of the staker176		/// * Balance : staking amount177		Stake(T::AccountId, BalanceOf<T>),178179		/// Unstaking was performed180		///181		/// # Arguments182		/// * AccountId: account of the staker183		/// * Balance : unstaking amount184		Unstake(T::AccountId, BalanceOf<T>),185186		/// The admin was set187		///188		/// # Arguments189		/// * AccountId: account address of the admin190		SetAdmin(T::AccountId),191	}192193	#[pallet::error]194	pub enum Error<T> {195		/// Error due to action requiring admin to be set.196		AdminNotSet,197		/// No permission to perform an action.198		NoPermission,199		/// Insufficient funds to perform an action.200		NotSufficientFunds,201		/// Occurs when a pending unstake cannot be added in this block. PENDING_LIMIT_PER_BLOCK` limits exceeded.202		PendingForBlockOverflow,203		/// The error is due to the fact that the collection/contract must already be sponsored in order to perform the action.204		SponsorNotSet,205		/// Errors caused by insufficient staked balance.206		InsufficientStakedBalance,207		/// Errors caused by incorrect state of a staker in context of the pallet.208		InconsistencyState,209	}210211	/// Stores the total staked amount.212	#[pallet::storage]213	pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;214215	/// Stores the `admin` account. Some extrinsics can only be executed if they were signed by `admin`.216	#[pallet::storage]217	pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;218219	/// Stores the amount of tokens staked by account in the blocknumber.220	///221	/// * **Key1** - Staker account.222	/// * **Key2** - Relay block number when the stake was made.223	/// * **(Balance, BlockNumber)** - Balance of the stake.224	/// The number of the relay block in which we must perform the interest recalculation225	#[pallet::storage]226	pub type Staked<T: Config> = StorageNMap<227		Key = (228			Key<Blake2_128Concat, T::AccountId>,229			Key<Twox64Concat, BlockNumberFor<T>>,230		),231		Value = (BalanceOf<T>, BlockNumberFor<T>),232		QueryKind = ValueQuery,233	>;234235	/// Stores number of stake records for an `Account`.236	///237	/// * **Key** - Staker account.238	/// * **Value** - Amount of stakes.239	#[pallet::storage]240	pub type StakesPerAccount<T: Config> =241		StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;242243	/// Pending unstake records for an `Account`.244	///245	/// * **Key** - Staker account.246	/// * **Value** - Amount of stakes.247	#[pallet::storage]248	pub type PendingUnstake<T: Config> = StorageMap<249		_,250		Twox64Concat,251		BlockNumberFor<T>,252		BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,253		ValueQuery,254	>;255256	/// Stores a key for record for which the revenue recalculation was performed.257	/// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.258	#[pallet::storage]259	#[pallet::getter(fn get_next_calculated_record)]260	pub type PreviousCalculatedRecord<T: Config> =261		StorageValue<Value = (T::AccountId, BlockNumberFor<T>), QueryKind = OptionQuery>;262263	#[pallet::hooks]264	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {265		/// Block overflow is impossible due to the fact that the unstake algorithm in on_initialize266		/// implies the execution of a strictly limited number of relatively lightweight operations.267		/// A separate benchmark has been implemented to scale the weight depending on the number of pendings.268		fn on_initialize(current_block_number: BlockNumberFor<T>) -> Weight269		where270			BlockNumberFor<T>: From<u32>,271		{272			if T::IsMaintenanceModeEnabled::get() {273				return T::DbWeight::get().reads_writes(1, 0);274			}275276			let block_pending = PendingUnstake::<T>::take(current_block_number);277			let counter = block_pending.len() as u32;278279			if !block_pending.is_empty() {280				block_pending.into_iter().for_each(|(staker, amount)| {281					if let Some(b) = Self::get_frozen_balance(&staker) {282						let new_state = b.checked_sub(&amount).unwrap_or_default();283284						// In this case, setting a new state for the frozen funds cannot fail285						// because the state change goes in the direction of decreasing the frozen funds286						// and the validity of this transition is ensured by the fact287						// that we cannot (in the current implementation) unfreeze more funds288						// than were originally frozen by the pallet. Either way, `on_initialize()` cannot fail.289						Self::set_freeze_unchecked(&staker, new_state);290					};291				});292			}293294			<T as Config>::WeightInfo::on_initialize(counter)295		}296	}297298	#[pallet::call]299	impl<T: Config> Pallet<T>300	where301		BlockNumberFor<T>: From<u32> + Into<u32>,302		<<T as Config>::Currency as Inspect<T::AccountId>>::Balance: Sum + From<u128>,303	{304		/// Sets an address as the the admin.305		///306		/// # Permissions307		///308		/// * Sudo309		///310		/// # Arguments311		///312		/// * `admin`: account of the new admin.313		#[pallet::call_index(0)]314		#[pallet::weight(<T as Config>::WeightInfo::set_admin_address())]315		pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {316			ensure_root(origin)?;317318			<Admin<T>>::set(Some(admin.as_sub().to_owned()));319320			Self::deposit_event(Event::SetAdmin(admin.as_sub().to_owned()));321322			Ok(())323		}324325		/// Stakes the amount of native tokens.326		/// Sets `amount` to the locked state.327		/// The maximum number of stakes for a staker is 10.328		///329		/// # Arguments330		///331		/// * `amount`: in native tokens.332		#[pallet::call_index(1)]333		#[pallet::weight(<T as Config>::WeightInfo::stake())]334		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {335			let staker_id = ensure_signed(staker)?;336337			ensure!(338				StakesPerAccount::<T>::get(&staker_id) < 10,339				Error::<T>::NoPermission340			);341342			ensure!(343				amount >= <BalanceOf<T>>::from(100u128) * T::Nominal::get(),344				ArithmeticError::Underflow345			);346			let config = <PalletConfiguration<T>>::get();347348			let balance = <<T as Config>::Currency as Inspect<T::AccountId>>::balance(&staker_id);349350			// checks that we can freeze `amount` on the `staker` account.351			ensure!(352				amount353					<= match Self::get_frozen_balance(&staker_id) {354						Some(frozen_by_pallet) => balance355							.checked_sub(&frozen_by_pallet)356							.ok_or(ArithmeticError::Underflow)?,357						None => balance,358					},359				ArithmeticError::Underflow360			);361362			Self::add_freeze_balance(&staker_id, amount)?;363364			let block_number = T::RelayBlockNumberProvider::current_block_number();365366			// Calculation of the number of recalculation periods,367			// after how much the first interest calculation should be performed for the stake368			let recalculate_after_interval: BlockNumberFor<T> =369				if block_number % config.recalculation_interval == 0u32.into() {370					1u32.into()371				} else {372					2u32.into()373				};374375			// Сalculation of the number of the relay block376			// in which it is necessary to accrue remuneration for the stake.377			let recalc_block = (block_number / config.recalculation_interval378				+ recalculate_after_interval)379				* config.recalculation_interval;380381			<Staked<T>>::insert((&staker_id, block_number), {382				let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));383				balance_and_recalc_block.0 = balance_and_recalc_block384					.0385					.checked_add(&amount)386					.ok_or(ArithmeticError::Overflow)?;387				balance_and_recalc_block.1 = recalc_block;388				balance_and_recalc_block389			});390391			<TotalStaked<T>>::set(392				<TotalStaked<T>>::get()393					.checked_add(&amount)394					.ok_or(ArithmeticError::Overflow)?,395			);396397			StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);398399			Self::deposit_event(Event::Stake(staker_id, amount));400401			Ok(())402		}403404		/// Unstakes all stakes.405		/// After the end of `PendingInterval` this sum becomes completely406		/// free for further use.407		#[pallet::call_index(2)]408		#[pallet::weight(<T as Config>::WeightInfo::unstake_all())]409		pub fn unstake_all(staker: OriginFor<T>) -> DispatchResult {410			let staker_id = ensure_signed(staker)?;411412			Self::unstake_all_internal(staker_id)413		}414415		/// Unstakes the amount of balance for the staker.416		/// After the end of `PendingInterval` this sum becomes completely417		/// free for further use.418		///419		///  # Arguments420		///421		/// * `staker`: staker account.422		/// * `amount`: amount of unstaked funds.423		#[pallet::call_index(8)]424		#[pallet::weight(<T as Config>::WeightInfo::unstake_partial())]425		pub fn unstake_partial(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {426			let staker_id = ensure_signed(staker)?;427428			Self::unstake_partial_internal(staker_id, amount)429		}430431		/// Sets the pallet to be the sponsor for the collection.432		///433		/// # Permissions434		///435		/// * Pallet admin436		///437		/// # Arguments438		///439		/// * `collection_id`: ID of the collection that will be sponsored by `pallet_id`440		#[pallet::call_index(3)]441		#[pallet::weight(<T as Config>::WeightInfo::sponsor_collection())]442		pub fn sponsor_collection(443			admin: OriginFor<T>,444			collection_id: CollectionId,445		) -> DispatchResult {446			let admin_id = ensure_signed(admin)?;447			ensure!(448				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,449				Error::<T>::NoPermission450			);451452			T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)453		}454455		/// Removes the pallet as the sponsor for the collection.456		/// Returns [`NoPermission`][`Error::NoPermission`]457		/// if the pallet wasn't the sponsor.458		///459		/// # Permissions460		///461		/// * Pallet admin462		///463		/// # Arguments464		///465		/// * `collection_id`: ID of the collection that is sponsored by `pallet_id`466		#[pallet::call_index(4)]467		#[pallet::weight(<T as Config>::WeightInfo::stop_sponsoring_collection())]468		pub fn stop_sponsoring_collection(469			admin: OriginFor<T>,470			collection_id: CollectionId,471		) -> DispatchResult {472			let admin_id = ensure_signed(admin)?;473474			ensure!(475				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,476				Error::<T>::NoPermission477			);478479			ensure!(480				T::CollectionHandler::sponsor(collection_id)?.ok_or(<Error<T>>::SponsorNotSet)?481					== Self::account_id(),482				<Error<T>>::NoPermission483			);484			T::CollectionHandler::remove_collection_sponsor(collection_id)485		}486487		/// Sets the pallet to be the sponsor for the contract.488		///489		/// # Permissions490		///491		/// * Pallet admin492		///493		/// # Arguments494		///495		/// * `contract_id`: the contract address that will be sponsored by `pallet_id`496		#[pallet::call_index(5)]497		#[pallet::weight(<T as Config>::WeightInfo::sponsor_contract())]498		pub fn sponsor_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {499			let admin_id = ensure_signed(admin)?;500501			ensure!(502				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,503				Error::<T>::NoPermission504			);505506			T::ContractHandler::set_sponsor(507				T::CrossAccountId::from_sub(Self::account_id()),508				contract_id,509			)510		}511512		/// Removes the pallet as the sponsor for the contract.513		/// Returns [`NoPermission`][`Error::NoPermission`]514		/// if the pallet wasn't the sponsor.515		///516		/// # Permissions517		///518		/// * Pallet admin519		///520		/// # Arguments521		///522		/// * `contract_id`: the contract address that is sponsored by `pallet_id`523		#[pallet::call_index(6)]524		#[pallet::weight(<T as Config>::WeightInfo::stop_sponsoring_contract())]525		pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {526			let admin_id = ensure_signed(admin)?;527528			ensure!(529				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,530				Error::<T>::NoPermission531			);532533			ensure!(534				T::ContractHandler::sponsor(contract_id)?535					.ok_or(<Error<T>>::SponsorNotSet)?536					.as_sub() == &Self::account_id(),537				<Error<T>>::NoPermission538			);539			T::ContractHandler::remove_contract_sponsor(contract_id)540		}541542		/// Recalculates interest for the specified number of stakers.543		/// If all stakers are not recalculated, the next call of the extrinsic544		/// will continue the recalculation, from those stakers for whom this545		/// was not perform in last call.546		///547		/// # Permissions548		///549		/// * Pallet admin550		///551		/// # Arguments552		///553		/// * `stakers_number`: the number of stakers for which recalculation will be performed554		#[pallet::call_index(7)]555		#[pallet::weight(<T as Config>::WeightInfo::payout_stakers(stakers_number.unwrap_or(DEFAULT_NUMBER_PAYOUTS) as u32))]556		pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {557			let admin_id = ensure_signed(admin)?;558559			ensure!(560				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,561				Error::<T>::NoPermission562			);563			let config = <PalletConfiguration<T>>::get();564565			let mut stakers_number = stakers_number.unwrap_or(DEFAULT_NUMBER_PAYOUTS);566567			ensure!(568				stakers_number <= config.max_stakers_per_calculation && stakers_number != 0,569				Error::<T>::NoPermission570			);571572			// calculate the number of the current recalculation block,573			// this is necessary in order to understand which stakers we should calculate interest574			let current_recalc_block = Self::get_current_recalc_block(575				T::RelayBlockNumberProvider::current_block_number(),576				&config,577			);578579			// calculate the number of the next recalculation block,580			// this value is set for the stakers to whom the recalculation will be performed581			let next_recalc_block = current_recalc_block + config.recalculation_interval;582583			let storage_iterator =584				Self::get_next_calculated_key().map_or(Staked::<T>::iter(), Staked::<T>::iter_from);585586			PreviousCalculatedRecord::<T>::set(None);587588			{589				// Address handled in the last payout loop iteration (below)590				let last_id = RefCell::new(None);591				// Block number (as a part of the key) for which calculation was performed in the last payout loop iteration592				let mut last_staked_calculated_block = Default::default();593				// Reward balance for the address in the iteration594				let income_acc = RefCell::new(BalanceOf::<T>::default());595				// Staked balance for the address in the iteration (before stake is recalculated)596				let amount_acc = RefCell::new(BalanceOf::<T>::default());597598				// This closure is used to finalize handling single staker address in each of the two conditions: (1) when we break out of the payout599				// loop because we reached the number of stakes for rewarding, (2) When all stakes by the single address are handled and the payout600				// loop switches to handling the next staker address:601				//   1. Transfer full reward amount to the payee602				//   2. Lock the reward in staking lock603				//   3. Update TotalStaked amount604				//   4. Issue StakingRecalculation event605				let flush_stake = || -> DispatchResult {606					if let Some(last_id) = &*last_id.borrow() {607						if !income_acc.borrow().is_zero() {608							// TO-DO: When moving to ED>0, reconsider the value of preservation609							<<T as Config>::Currency as Mutate<T::AccountId>>::transfer(610								&T::TreasuryAccountId::get(),611								last_id,612								*income_acc.borrow(),613								frame_support::traits::tokens::Preservation::Protect,614							)?;615616							Self::add_freeze_balance(last_id, *income_acc.borrow())?;617							<TotalStaked<T>>::try_mutate(|staked| -> DispatchResult {618								*staked = staked619									.checked_add(&*income_acc.borrow())620									.ok_or(ArithmeticError::Overflow)?;621								Ok(())622							})?;623624							Self::deposit_event(Event::StakingRecalculation(625								last_id.clone(),626								*amount_acc.borrow(),627								*income_acc.borrow(),628							));629						}630631						*income_acc.borrow_mut() = BalanceOf::<T>::default();632						*amount_acc.borrow_mut() = BalanceOf::<T>::default();633					}634					Ok(())635				};636637				// Reward payment loop. Should loop for no more than config.max_stakers_per_calculation638				// iterations in one extrinsic call639				//640				// stakers_number - keeps the remaining number of iterations (staker addresses to handle)641				// next_recalc_block_for_stake - is taken from the state and stores the starting relay block from which reward should be paid out642				// income_acc - stores the reward amount to pay to the staker address (accumulates over all address stake records)643				for ((current_id, staked_block), (amount, next_recalc_block_for_stake)) in644					storage_iterator645				{646					// last_id is not equal current_id when we switch to handling a new staker address647					// or just start handling the very first address. In the latter case last_id will be None and648					// flush_stake will do nothing649					if last_id.borrow().as_ref() != Some(&current_id) {650						if stakers_number > 0 {651							flush_stake()?;652							*last_id.borrow_mut() = Some(current_id.clone());653							stakers_number -= 1;654						}655						// Break out if we reached the address limit656						else {657							if let Some(staker) = &*last_id.borrow() {658								// Save the last calculated record to pick up in the next extrinsic call659								PreviousCalculatedRecord::<T>::set(Some((660									staker.clone(),661									last_staked_calculated_block,662								)));663							}664							break;665						};666					};667668					// Increase accumulated reward for current address and update current staking record, i.e. (address, staked_block) -> amount669					if current_recalc_block >= next_recalc_block_for_stake {670						*amount_acc.borrow_mut() += amount;671						Self::recalculate_and_insert_stake(672							&current_id,673							staked_block,674							next_recalc_block,675							amount,676							((current_recalc_block - next_recalc_block_for_stake)677								/ config.recalculation_interval)678								.into() + 1,679							&mut *income_acc.borrow_mut(),680						);681					}682					last_staked_calculated_block = staked_block;683				}684				flush_stake()?;685			}686687			Ok(())688		}689690		/// Called for blocks that, for some reason, have not been unstacked691		///692		/// # Permissions693		///694		/// * Sudo695		///696		///   # Arguments697		///698		/// * `origin`: Must be `Root`.699		/// * `pending_blocks`: Block numbers that will be processed.700		#[pallet::call_index(9)]701		#[pallet::weight(<T as Config>::WeightInfo::on_initialize(PENDING_LIMIT_PER_BLOCK*pending_blocks.len() as u32))]702		pub fn force_unstake(703			origin: OriginFor<T>,704			pending_blocks: Vec<BlockNumberFor<T>>,705		) -> DispatchResult {706			ensure_root(origin)?;707708			ensure!(709				pending_blocks710					.iter()711					.all(|b| *b < <frame_system::Pallet<T>>::block_number()),712				<Error<T>>::NoPermission713			);714715			let mut pendings =716				Vec::with_capacity(PENDING_LIMIT_PER_BLOCK as usize * pending_blocks.len());717			pending_blocks718				.into_iter()719				.for_each(|b| pendings.append(&mut PendingUnstake::<T>::take(b).into_inner()));720721			pendings722				.into_iter()723				.try_for_each(|(staker, amount)| -> Result<(), DispatchError> {724					if let Some(b) = Self::get_frozen_balance(&staker) {725						let new_state = b.checked_sub(&amount).unwrap_or_default();726						Self::set_freeze_with_result(&staker, new_state)?;727					}728729					Ok(())730				})?;731732			Ok(())733		}734	}735}736737impl<T: Config> Pallet<T> {738	/// The account address of the app promotion pot.739	///740	/// This actually does computation. If you need to keep using it, then make sure you cache the741	/// value and only call this once.742	pub fn account_id() -> T::AccountId {743		T::PalletId::get().into_account_truncating()744	}745746	/// Unstakes the balance for the staker.747	///748	/// - `staker`: staker account.749	/// - `amount`: amount of unstaked funds.750	fn unstake_partial_internal(751		staker_id: T::AccountId,752		unstaked_balance: BalanceOf<T>,753	) -> DispatchResult {754		if unstaked_balance == Default::default() {755			return Ok(());756		}757758		let config = <PalletConfiguration<T>>::get();759760		// calculate block number where the sum would be free761		let unpending_block = <frame_system::Pallet<T>>::block_number() + config.pending_interval;762763		let mut pendings = <PendingUnstake<T>>::get(unpending_block);764765		// checks that we can do unstake in the block766		ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);767768		let mut stakes = Staked::<T>::iter_prefix((&staker_id,)).collect::<Vec<_>>();769770		let total_staked = stakes771			.iter()772			.fold(<BalanceOf<T>>::default(), |acc, (_, (balance, _))| {773				acc + *balance774			});775776		ensure!(777			unstaked_balance <= total_staked,778			<Error<T>>::InsufficientStakedBalance779		);780781		<TotalStaked<T>>::set(782			<TotalStaked<T>>::get()783				.checked_sub(&unstaked_balance)784				.ok_or(ArithmeticError::Underflow)?,785		);786787		stakes.sort_by_key(|(block, _)| *block);788789		let mut acc_amount = unstaked_balance;790		let mut will_deleted_stakes_count = 0u8;791792		let changed_stakes = stakes793			.into_iter()794			.map_while(|(block, (balance_per_block, _))| {795				if acc_amount == <BalanceOf<T>>::default() {796					return None;797				}798				if acc_amount < balance_per_block {799					let res = (block, balance_per_block - acc_amount);800					acc_amount = <BalanceOf<T>>::default();801					Some(res)802				} else {803					acc_amount -= balance_per_block;804					will_deleted_stakes_count += 1;805					Some((block, <BalanceOf<T>>::default()))806				}807			})808			.collect::<Vec<_>>();809810		pendings811			.try_push((staker_id.clone(), unstaked_balance))812			.map_err(|_| Error::<T>::PendingForBlockOverflow)?;813814		StakesPerAccount::<T>::try_mutate(&staker_id, |stakes| -> DispatchResult {815			*stakes = stakes816				.checked_sub(will_deleted_stakes_count)817				.ok_or(ArithmeticError::Underflow)?;818			Ok(())819		})?;820821		changed_stakes822			.into_iter()823			.for_each(|(staked_block, current_stake_state)| {824				if current_stake_state == Default::default() {825					<Staked<T>>::remove((&staker_id, staked_block));826				} else {827					<Staked<T>>::mutate((&staker_id, staked_block), |(old_stake_state, _)| {828						*old_stake_state = current_stake_state829					});830				}831			});832833		<PendingUnstake<T>>::insert(unpending_block, pendings);834835		Self::deposit_event(Event::Unstake(staker_id, unstaked_balance));836837		Ok(())838	}839840	/// Adds the balance to frozen by the pallet.841	///842	/// - `staker`: staker account.843	/// - `amount`: amount of added frozen funds.844	fn add_freeze_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {845		Self::get_frozen_balance(staker)846			.unwrap_or_default()847			.checked_add(&amount)848			.map(|freeze| Self::set_freeze_with_result(staker, freeze))849			.ok_or::<DispatchError>(ArithmeticError::Overflow.into())?850	}851852	/// Sets the new state of a balance frozen by the pallet.853	///854	/// - `staker`: staker account.855	/// - `amount`: amount of frozen funds.856	fn set_freeze_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {857		let _ = Self::set_freeze_with_result(staker, amount);858	}859860	/// Sets the new state of a balance frozen by the pallet.861	///862	/// - `staker`: staker account.863	/// - `amount`: amount of frozen funds.864	fn set_freeze_with_result(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {865		if amount.is_zero() {866			<<T as Config>::Currency as MutateFreeze<T::AccountId>>::thaw(867				&T::FreezeIdentifier::get(),868				staker,869			)870		} else {871			<<T as Config>::Currency as MutateFreeze<T::AccountId>>::set_freeze(872				&T::FreezeIdentifier::get(),873				staker,874				amount,875			)876		}877	}878879	/// Returns the balance frozen by the pallet for the staker.880	///881	/// - `staker`: staker account.882	pub fn get_frozen_balance(staker: &T::AccountId) -> Option<BalanceOf<T>> {883		let res = <<T as Config>::Currency as InspectFreeze<T::AccountId>>::balance_frozen(884			&T::FreezeIdentifier::get(),885			staker,886		);887888		if res == Zero::zero() {889			None890		} else {891			Some(res)892		}893	}894895	/// Returns the total staked balance for the staker.896	///897	/// - `staker`: staker account.898	pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {899		let staked = Staked::<T>::iter_prefix((staker,))900			.fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {901				acc + amount902			});903		if staked != <BalanceOf<T>>::default() {904			Some(staked)905		} else {906			None907		}908	}909910	/// Returns all relay block numbers when stake was made,911	/// the amount of the stake.912	///913	/// - `staker`: staker account.914	pub fn total_staked_by_id_per_block(915		staker: impl EncodeLike<T::AccountId>,916	) -> Option<Vec<(BlockNumberFor<T>, BalanceOf<T>)>> {917		let mut staked = Staked::<T>::iter_prefix((staker,))918			.map(|(block, (amount, _))| (block, amount))919			.collect::<Vec<_>>();920		staked.sort_by_key(|(block, _)| *block);921		if !staked.is_empty() {922			Some(staked)923		} else {924			None925		}926	}927928	/// Returns the total staked balance for the staker.929	/// If `staker` is `None`, returns the total amount staked.930	/// - `staker`: staker account.931	pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {932		staker.map_or(Some(<TotalStaked<T>>::get()), |s| {933			Self::total_staked_by_id(s.as_sub())934		})935	}936937	/// Returns all relay block numbers when stake was made,938	/// the amount of the stake.939	///940	/// - `staker`: staker account.941	pub fn cross_id_total_staked_per_block(942		staker: T::CrossAccountId,943	) -> Vec<(BlockNumberFor<T>, BalanceOf<T>)> {944		Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()945	}946947	fn recalculate_and_insert_stake(948		staker: &T::AccountId,949		staked_block: BlockNumberFor<T>,950		next_recalc_block: BlockNumberFor<T>,951		base: BalanceOf<T>,952		iters: u32,953		income_acc: &mut BalanceOf<T>,954	) {955		let income = Self::calculate_income(base, iters);956957		if let Some(res) = base.checked_add(&income) {958			<Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));959			*income_acc += income;960		};961	}962963	fn calculate_income<I>(base: I, iters: u32) -> I964	where965		I: EncodeLike<BalanceOf<T>> + Balance,966	{967		let config = <PalletConfiguration<T>>::get();968		let mut income = base;969970		(0..iters).for_each(|_| income += config.interval_income * income);971972		income - base973	}974975	/// Get relay block number rounded down to multiples of config.recalculation_interval.976	/// We need it to reward stakers in integer parts of recalculation_interval977	fn get_current_recalc_block(978		current_relay_block: BlockNumberFor<T>,979		config: &PalletConfiguration<T>,980	) -> BlockNumberFor<T> {981		(current_relay_block / config.recalculation_interval) * config.recalculation_interval982	}983984	fn get_next_calculated_key() -> Option<Vec<u8>> {985		Self::get_next_calculated_record().map(Staked::<T>::hashed_key_for)986	}987}988989impl<T: Config> Pallet<T>990where991	<<T as Config>::Currency as Inspect<T::AccountId>>::Balance: Sum,992{993	/// Returns the amount reserved by the pending.994	/// If `staker` is `None`, returns the total pending.995	///996	/// -`staker`: staker account.997	///998	/// Since user funds are not transferred anywhere by staking, overflow protection is provided999	/// at the level of the associated type `Balance` of `Currency` trait. In order to overflow,1000	/// the staker must have more funds on his account than the maximum set for `Balance` type.1001	pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {1002		staker.map_or(1003			PendingUnstake::<T>::iter_values()1004				.flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))1005				.sum(),1006			|s| {1007				PendingUnstake::<T>::iter_values()1008					.flatten()1009					.filter_map(|(id, amount)| {1010						if id == *s.as_sub() {1011							Some(amount)1012						} else {1013							None1014						}1015					})1016					.sum()1017			},1018		)1019	}10201021	/// Returns all parachain block numbers when unreserve is expected,1022	/// the amount of the unreserved funds.1023	///1024	/// - `staker`: staker account.1025	pub fn cross_id_pending_unstake_per_block(1026		staker: T::CrossAccountId,1027	) -> Vec<(BlockNumberFor<T>, BalanceOf<T>)> {1028		let mut unsorted_res = vec![];1029		PendingUnstake::<T>::iter().for_each(|(block, pendings)| {1030			pendings.into_iter().for_each(|(id, amount)| {1031				if id == *staker.as_sub() {1032					unsorted_res.push((block, amount));1033				};1034			})1035		});10361037		unsorted_res.sort_by_key(|(block, _)| *block);1038		unsorted_res1039	}10401041	fn unstake_all_internal(staker_id: T::AccountId) -> DispatchResult {1042		let config = <PalletConfiguration<T>>::get();10431044		// calculate block number where the sum would be free1045		let block = <frame_system::Pallet<T>>::block_number() + config.pending_interval;10461047		let mut pendings = <PendingUnstake<T>>::get(block);10481049		// checks that we can do unstake in the block1050		ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);10511052		let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))1053			.map(|(_, (amount, _))| amount)1054			.sum();10551056		if total_staked.is_zero() {1057			return Ok(());1058		}10591060		pendings1061			.try_push((staker_id.clone(), total_staked))1062			.map_err(|_| Error::<T>::PendingForBlockOverflow)?;10631064		<PendingUnstake<T>>::insert(block, pendings);10651066		TotalStaked::<T>::set(1067			TotalStaked::<T>::get()1068				.checked_sub(&total_staked)1069				.ok_or(ArithmeticError::Underflow)?,1070		);10711072		StakesPerAccount::<T>::remove(&staker_id);10731074		Self::deposit_event(Event::Unstake(staker_id, total_staked));10751076		Ok(())1077	}1078}