difftreelog
fix unstake
in: master
1 file changed
pallets/app-promotion/src/lib.rsdiffbeforeafterboth1// 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 ¤t_id,545 income_acc,546 ExistenceRequirement::KeepAlive,547 )548 .and_then(|_| Self::add_lock_balance(¤t_id, income_acc))?;549550 Self::deposit_event(Event::StakingRecalculation(551 current_id, amount, income_acc,552 ));553 }554555 if stakers_number == 0 {556 NextCalculatedRecord::<T>::set(Some((id, staked_block)));557 break;558 }559 stakers_number -= 1;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 ¤t_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}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 // };212 213 let current_relay_block = T::RelayBlockNumberProvider::current_block_number();214 PendingUnstake::<T>::iter()215 .filter_map(|((staker, block), amount)| {216 if block <= current_relay_block {217 Some((staker, block, amount))218 } else {219 None220 }221 })222 .for_each(|(staker, block, amount)| {223 Self::unlock_balance_unchecked(&staker, amount); 224 <PendingUnstake<T>>::remove((staker, block));225 });226227 // let next_interest_block = Self::get_interest_block();228 // let current_relay_block = T::RelayBlockNumberProvider::current_block_number();229 // if next_interest_block != 0.into() && current_relay_block >= next_interest_block {230 // let mut acc = <BalanceOf<T>>::default();231 // let mut base_acc = <BalanceOf<T>>::default();232233 // NextInterestBlock::<T>::set(234 // NextInterestBlock::<T>::get() + T::RecalculationInterval::get(),235 // );236 // add_weight(0, 1, 0);237238 // Staked::<T>::iter()239 // .filter(|((_, block), _)| {240 // *block + T::RecalculationInterval::get() <= current_relay_block241 // })242 // .for_each(|((staker, block), amount)| {243 // Self::recalculate_stake(&staker, block, amount, &mut acc);244 // add_weight(0, 0, T::WeightInfo::recalculate_stake());245 // base_acc += amount;246 // });247 // <TotalStaked<T>>::get()248 // .checked_add(&acc)249 // .map(|res| <TotalStaked<T>>::set(res));250251 // Self::deposit_event(Event::StakingRecalculation(base_acc, acc));252 // add_weight(0, 1, 0);253 // } else {254 // add_weight(1, 0, 0)255 // };256 consumed_weight257 }258 }259260 #[pallet::call]261 impl<T: Config> Pallet<T>262 where263 T::BlockNumber: From<u32> + Into<u32>,264 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,265 {266 #[pallet::weight(T::WeightInfo::set_admin_address())]267 pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {268 ensure_root(origin)?;269 <Admin<T>>::set(Some(admin.as_sub().to_owned()));270271 Ok(())272 }273274 #[pallet::weight(T::WeightInfo::start_app_promotion())]275 pub fn start_app_promotion(276 origin: OriginFor<T>,277 promotion_start_relay_block: Option<T::BlockNumber>,278 ) -> DispatchResult279 where280 <T as frame_system::Config>::BlockNumber: From<u32>,281 {282 ensure_root(origin)?;283284 // Start app-promotion mechanics if it has not been yet initialized285 if <StartBlock<T>>::get() == 0u32.into() {286 let start_block = promotion_start_relay_block287 .unwrap_or(T::RelayBlockNumberProvider::current_block_number());288289 // Set promotion global start block290 <StartBlock<T>>::set(start_block);291292 <NextInterestBlock<T>>::set(start_block + T::RecalculationInterval::get());293 }294295 Ok(())296 }297298 #[pallet::weight(T::WeightInfo::stop_app_promotion())]299 pub fn stop_app_promotion(origin: OriginFor<T>) -> DispatchResult300 where301 <T as frame_system::Config>::BlockNumber: From<u32>,302 {303 ensure_root(origin)?;304305 if <StartBlock<T>>::get() != 0u32.into() {306 <StartBlock<T>>::set(T::BlockNumber::default());307 <NextInterestBlock<T>>::set(T::BlockNumber::default());308 }309310 Ok(())311 }312313 #[pallet::weight(T::WeightInfo::stake())]314 pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {315 let staker_id = ensure_signed(staker)?;316317 ensure!(318 amount >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(),319 ArithmeticError::Underflow320 );321322 let balance =323 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);324325 ensure!(balance >= amount, ArithmeticError::Underflow);326327 <<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(328 &staker_id,329 amount,330 WithdrawReasons::all(),331 balance - amount,332 )?;333334 Self::add_lock_balance(&staker_id, amount)?;335336 let block_number = T::RelayBlockNumberProvider::current_block_number();337 let recalc_block = (block_number / T::RecalculationInterval::get() + 2u32.into())338 * T::RecalculationInterval::get();339340 <Staked<T>>::insert((&staker_id, block_number), {341 let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));342 balance_and_recalc_block.0 = balance_and_recalc_block343 .0344 .checked_add(&amount)345 .ok_or(ArithmeticError::Overflow)?;346 balance_and_recalc_block.1 = recalc_block;347 balance_and_recalc_block348 });349350 <TotalStaked<T>>::set(351 <TotalStaked<T>>::get()352 .checked_add(&amount)353 .ok_or(ArithmeticError::Overflow)?,354 );355356 Ok(())357 }358359 #[pallet::weight(T::WeightInfo::unstake())]360 pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {361 let staker_id = ensure_signed(staker)?;362363 let mut total_stakes = 0u64;364365 let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))366 .map(|(_, (amount, _))| {367 *&mut total_stakes += 1;368 amount369 })370 .sum();371372 let block =373 T::RelayBlockNumberProvider::current_block_number() + T::PendingInterval::get();374 <PendingUnstake<T>>::insert(375 (&staker_id, block),376 <PendingUnstake<T>>::get((&staker_id, block))377 .checked_add(&total_staked)378 .ok_or(ArithmeticError::Overflow)?,379 );380381 TotalStaked::<T>::set(382 TotalStaked::<T>::get()383 .checked_sub(&total_staked)384 .ok_or(ArithmeticError::Underflow)?,385 ); // when error we should recover initial stake state for the staker386387 Ok(None.into())388389 // let staker_id = ensure_signed(staker)?;390391 // let mut stakes = Staked::<T>::iter_prefix((&staker_id,)).collect::<Vec<_>>();392393 // let total_staked = stakes394 // .iter()395 // .fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);396397 // ensure!(total_staked >= amount, ArithmeticError::Underflow);398399 // <TotalStaked<T>>::set(400 // <TotalStaked<T>>::get()401 // .checked_sub(&amount)402 // .ok_or(ArithmeticError::Underflow)?,403 // );404405 // let block =406 // T::RelayBlockNumberProvider::current_block_number() + T::PendingInterval::get();407 // <PendingUnstake<T>>::insert(408 // (&staker_id, block),409 // <PendingUnstake<T>>::get((&staker_id, block))410 // .checked_add(&amount)411 // .ok_or(ArithmeticError::Overflow)?,412 // );413414 // stakes.sort_by_key(|(block, _)| *block);415416 // let mut acc_amount = amount;417 // let new_state = stakes418 // .into_iter()419 // .map_while(|(block, balance_per_block)| {420 // if acc_amount == <BalanceOf<T>>::default() {421 // return None;422 // }423 // if acc_amount <= balance_per_block {424 // let res = (block, balance_per_block - acc_amount, acc_amount);425 // acc_amount = <BalanceOf<T>>::default();426 // return Some(res);427 // } else {428 // acc_amount -= balance_per_block;429 // return Some((block, <BalanceOf<T>>::default(), acc_amount));430 // }431 // })432 // .collect::<Vec<_>>();433434 // new_state435 // .into_iter()436 // .for_each(|(block, to_staked, _to_pending)| {437 // if to_staked == <BalanceOf<T>>::default() {438 // <Staked<T>>::remove((&staker_id, block));439 // } else {440 // <Staked<T>>::insert((&staker_id, block), to_staked);441 // }442 // });443444 // Ok(())445 }446447 #[pallet::weight(T::WeightInfo::sponsor_collection())]448 pub fn sponsor_collection(449 admin: OriginFor<T>,450 collection_id: CollectionId,451 ) -> DispatchResult {452 let admin_id = ensure_signed(admin)?;453 ensure!(454 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,455 Error::<T>::NoPermission456 );457458 T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)459 }460 #[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]461 pub fn stop_sponsoring_collection(462 admin: OriginFor<T>,463 collection_id: CollectionId,464 ) -> 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 ensure!(473 T::CollectionHandler::get_sponsor(collection_id)?474 .ok_or(<Error<T>>::InvalidArgument)?475 == Self::account_id(),476 <Error<T>>::NoPermission477 );478 T::CollectionHandler::remove_collection_sponsor(collection_id)479 }480481 #[pallet::weight(T::WeightInfo::sponsor_contract())]482 pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {483 let admin_id = ensure_signed(admin)?;484485 ensure!(486 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,487 Error::<T>::NoPermission488 );489490 T::ContractHandler::set_sponsor(491 T::CrossAccountId::from_sub(Self::account_id()),492 contract_id,493 )494 }495496 #[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]497 pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {498 let admin_id = ensure_signed(admin)?;499500 ensure!(501 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,502 Error::<T>::NoPermission503 );504505 ensure!(506 T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?507 == T::CrossAccountId::from_sub(Self::account_id()),508 <Error<T>>::NoPermission509 );510 T::ContractHandler::remove_contract_sponsor(contract_id)511 }512513 #[pallet::weight(0)]514 pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {515 let admin_id = ensure_signed(admin)?;516517 ensure!(518 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,519 Error::<T>::NoPermission520 );521522 let current_recalc_block =523 Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());524 let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();525526 let mut storage_iterator = Self::get_next_calculated_key()527 .map_or(Staked::<T>::iter().skip(0), |key| {528 Staked::<T>::iter_from(key).skip(1)529 });530531 NextCalculatedRecord::<T>::set(None);532533 {534 let mut stakers_number = stakers_number.unwrap_or(20);535 let mut current_id = admin_id;536 let mut income_acc = BalanceOf::<T>::default();537538 while let Some(((id, staked_block), (amount, next_recalc_block_for_stake))) =539 storage_iterator.next()540 {541 if current_id != id {542 if income_acc != BalanceOf::<T>::default() {543 <T::Currency as Currency<T::AccountId>>::transfer(544 &T::TreasuryAccountId::get(),545 ¤t_id,546 income_acc,547 ExistenceRequirement::KeepAlive,548 )549 .and_then(|_| Self::add_lock_balance(¤t_id, income_acc))?;550551 Self::deposit_event(Event::StakingRecalculation(552 current_id, amount, income_acc,553 ));554 }555556 if stakers_number == 0 {557 NextCalculatedRecord::<T>::set(Some((id, staked_block)));558 break;559 }560 stakers_number -= 1;561 income_acc = BalanceOf::<T>::default();562 current_id = id;563 };564 if next_recalc_block_for_stake >= current_recalc_block {565 Self::recalculate_and_insert_stake(566 ¤t_id,567 staked_block,568 next_recalc_block,569 amount,570 ((next_recalc_block_for_stake - current_recalc_block)571 / T::RecalculationInterval::get())572 .into() + 1,573 &mut income_acc,574 );575 }576 }577 }578579 Ok(())580 }581 }582}583584impl<T: Config> Pallet<T> {585 pub fn account_id() -> T::AccountId {586 T::PalletId::get().into_account_truncating()587 }588589 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {590 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();591 locked_balance -= amount;592 Self::set_lock_unchecked(staker, locked_balance);593 }594595 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {596 Self::get_locked_balance(staker)597 .map_or(<BalanceOf<T>>::default(), |l| l.amount)598 .checked_add(&amount)599 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))600 .ok_or(ArithmeticError::Overflow.into())601 }602603 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {604 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(605 LOCK_IDENTIFIER,606 staker,607 amount,608 WithdrawReasons::all(),609 )610 }611612 pub fn get_locked_balance(613 staker: impl EncodeLike<T::AccountId>,614 ) -> Option<BalanceLock<BalanceOf<T>>> {615 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)616 .into_iter()617 .find(|l| l.id == LOCK_IDENTIFIER)618 }619620 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {621 let staked = Staked::<T>::iter_prefix((staker,))622 .into_iter()623 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {624 acc + amount625 });626 if staked != <BalanceOf<T>>::default() {627 Some(staked)628 } else {629 None630 }631 }632633 pub fn total_staked_by_id_per_block(634 staker: impl EncodeLike<T::AccountId>,635 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {636 let mut staked = Staked::<T>::iter_prefix((staker,))637 .into_iter()638 .map(|(block, (amount, _))| (block, amount))639 .collect::<Vec<_>>();640 staked.sort_by_key(|(block, _)| *block);641 if !staked.is_empty() {642 Some(staked)643 } else {644 None645 }646 }647648 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {649 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {650 Self::total_staked_by_id(s.as_sub())651 })652 // Self::total_staked_by_id(staker.as_sub())653 }654655 pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {656 Self::get_locked_balance(staker.as_sub())657 .map(|l| l.amount)658 .unwrap_or_default()659 }660661 pub fn cross_id_total_staked_per_block(662 staker: T::CrossAccountId,663 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {664 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()665 }666667 fn recalculate_and_insert_stake(668 staker: &T::AccountId,669 staked_block: T::BlockNumber,670 next_recalc_block: T::BlockNumber,671 base: BalanceOf<T>,672 iters: u32,673 income_acc: &mut BalanceOf<T>,674 ) {675 let income = Self::calculate_income(base, iters);676677 base.checked_add(&income).map(|res| {678 <Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));679 *income_acc += income;680 });681 }682683 fn calculate_income<I>(base: I, iters: u32) -> I684 where685 I: EncodeLike<BalanceOf<T>> + Balance,686 {687 let mut income = base;688689 (0..iters).for_each(|_| income += T::IntervalIncome::get() * income);690691 income - base692 }693694 fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {695 (current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()696 }697698 // fn get_next_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {699 // Self::get_current_recalc_block(current_relay_block) + T::RecalculationInterval::get()700 // }701702 fn get_next_calculated_key() -> Option<Vec<u8>> {703 Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))704 }705}706707impl<T: Config> Pallet<T>708where709 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,710{711 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {712 staker.map_or(PendingUnstake::<T>::iter_values().sum(), |s| {713 PendingUnstake::<T>::iter_prefix_values((s.as_sub(),)).sum()714 })715 }716717 pub fn cross_id_pending_unstake_per_block(718 staker: T::CrossAccountId,719 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {720 let mut unsorted_res = PendingUnstake::<T>::iter_prefix((staker.as_sub(),))721 .into_iter()722 .collect::<Vec<_>>();723 unsorted_res.sort_by_key(|(block, _)| *block);724 unsorted_res725 }726}