difftreelog
fix lock and unstake logic
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 // };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}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # App promotion18//!19//! The app promotion pallet is designed to ... .20//!21//! ## Interface22//!23//! ### Dispatchable Functions24//!25//! * `start_inflation` - This method sets the inflation start date. Can be only called once.26//! Inflation start block can be backdated and will catch up. The method will create Treasury27//! account if it does not exist and perform the first inflation deposit.2829// #![recursion_limit = "1024"]30#![cfg_attr(not(feature = "std"), no_std)]3132#[cfg(feature = "runtime-benchmarks")]33mod benchmarking;34#[cfg(test)]35mod tests;36pub mod types;37pub mod weights;3839use sp_std::{vec::Vec, iter::Sum, borrow::ToOwned};40use sp_core::H160;41use codec::EncodeLike;42use pallet_balances::BalanceLock;43pub use types::*;4445// use up_common::constants::{DAYS, UNIQUE};46use up_data_structs::CollectionId;4748use frame_support::{49 dispatch::{DispatchResult},50 traits::{51 Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,52 },53 ensure,54};5556use weights::WeightInfo;5758pub use pallet::*;59use pallet_evm::account::CrossAccountId;60use sp_runtime::{61 Perbill,62 traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion, Zero},63 ArithmeticError,64};6566type BalanceOf<T> =67 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;6869// const SECONDS_TO_BLOCK: u32 = 6;70// const DAY: u32 = 60 * 60 * 24 / SECONDS_TO_BLOCK;71// const WEEK: u32 = 7 * DAY;72// const TWO_WEEK: u32 = 2 * WEEK;73// const YEAR: u32 = DAY * 365;7475pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";7677#[frame_support::pallet]78pub mod pallet {79 use super::*;80 use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId};81 use frame_system::pallet_prelude::*;8283 #[pallet::config]84 pub trait Config: frame_system::Config + pallet_evm::account::Config {85 type Currency: ExtendedLockableCurrency<Self::AccountId>;8687 type CollectionHandler: CollectionHandler<88 AccountId = Self::AccountId,89 CollectionId = CollectionId,90 >;9192 type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;9394 type TreasuryAccountId: Get<Self::AccountId>;9596 /// The app's pallet id, used for deriving its sovereign account ID.97 #[pallet::constant]98 type PalletId: Get<PalletId>;99100 /// In relay blocks.101 #[pallet::constant]102 type RecalculationInterval: Get<Self::BlockNumber>;103 /// In relay blocks.104 #[pallet::constant]105 type PendingInterval: Get<Self::BlockNumber>;106107 /// In chain blocks.108 #[pallet::constant]109 type Day: Get<Self::BlockNumber>; // useless110111 #[pallet::constant]112 type Nominal: Get<BalanceOf<Self>>;113114 #[pallet::constant]115 type IntervalIncome: Get<Perbill>;116117 /// Weight information for extrinsics in this pallet.118 type WeightInfo: WeightInfo;119120 // The relay block number provider121 type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;122123 /// Events compatible with [`frame_system::Config::Event`].124 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;125 }126127 #[pallet::pallet]128 #[pallet::generate_store(pub(super) trait Store)]129 pub struct Pallet<T>(_);130131 #[pallet::event]132 #[pallet::generate_deposit(fn deposit_event)]133 pub enum Event<T: Config> {134 StakingRecalculation(135 /// An recalculated staker136 T::AccountId,137 /// Base on which interest is calculated138 BalanceOf<T>,139 /// Amount of accrued interest140 BalanceOf<T>,141 ),142 }143144 #[pallet::error]145 pub enum Error<T> {146 /// Error due to action requiring admin to be set147 AdminNotSet,148 /// No permission to perform an action149 NoPermission,150 /// Insufficient funds to perform an action151 NotSufficientFounds,152 /// An error related to the fact that an invalid argument was passed to perform an action153 InvalidArgument,154 }155156 #[pallet::storage]157 pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;158159 #[pallet::storage]160 pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;161162 /// Amount of tokens staked by account in the blocknumber.163 #[pallet::storage]164 pub type Staked<T: Config> = StorageNMap<165 Key = (166 Key<Blake2_128Concat, T::AccountId>,167 Key<Twox64Concat, T::BlockNumber>,168 ),169 Value = (BalanceOf<T>, T::BlockNumber),170 QueryKind = ValueQuery,171 >;172 /// Amount of stakes for an Account173 #[pallet::storage]174 pub type StakesPerAccount<T: Config> =175 StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;176177 /// Amount of tokens pending unstake per user per block.178 #[pallet::storage]179 pub type PendingUnstake<T: Config> = StorageNMap<180 Key = (181 Key<Blake2_128Concat, T::AccountId>,182 Key<Twox64Concat, T::BlockNumber>,183 ),184 Value = BalanceOf<T>,185 QueryKind = ValueQuery,186 >;187188 /// A block when app-promotion has started .I think this is redundant, because we only need `NextInterestBlock`.189 #[pallet::storage]190 pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;191192 /// Next target block when interest is recalculated193 #[pallet::storage]194 #[pallet::getter(fn get_interest_block)]195 pub type NextInterestBlock<T: Config> =196 StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;197198 /// Stores hash a record for which the last revenue recalculation was performed.199 /// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.200 #[pallet::storage]201 #[pallet::getter(fn get_next_calculated_record)]202 pub type NextCalculatedRecord<T: Config> =203 StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;204205 #[pallet::hooks]206 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {207 fn on_initialize(current_block: T::BlockNumber) -> Weight208 where209 <T as frame_system::Config>::BlockNumber: From<u32>,210 {211 let mut consumed_weight = 0;212 // let mut add_weight = |reads, writes, weight| {213 // consumed_weight += T::DbWeight::get().reads_writes(reads, writes);214 // consumed_weight += weight;215 // };216217 let current_relay_block = T::RelayBlockNumberProvider::current_block_number();218 PendingUnstake::<T>::iter()219 .filter_map(|((staker, block), amount)| {220 if block <= current_relay_block {221 Some((staker, block, amount))222 } else {223 None224 }225 })226 .for_each(|(staker, block, amount)| {227 Self::unlock_balance_unchecked(&staker, amount);228 <PendingUnstake<T>>::remove((staker, block));229 });230231 // let next_interest_block = Self::get_interest_block();232 // let current_relay_block = T::RelayBlockNumberProvider::current_block_number();233 // if next_interest_block != 0.into() && current_relay_block >= next_interest_block {234 // let mut acc = <BalanceOf<T>>::default();235 // let mut base_acc = <BalanceOf<T>>::default();236237 // NextInterestBlock::<T>::set(238 // NextInterestBlock::<T>::get() + T::RecalculationInterval::get(),239 // );240 // add_weight(0, 1, 0);241242 // Staked::<T>::iter()243 // .filter(|((_, block), _)| {244 // *block + T::RecalculationInterval::get() <= current_relay_block245 // })246 // .for_each(|((staker, block), amount)| {247 // Self::recalculate_stake(&staker, block, amount, &mut acc);248 // add_weight(0, 0, T::WeightInfo::recalculate_stake());249 // base_acc += amount;250 // });251 // <TotalStaked<T>>::get()252 // .checked_add(&acc)253 // .map(|res| <TotalStaked<T>>::set(res));254255 // Self::deposit_event(Event::StakingRecalculation(base_acc, acc));256 // add_weight(0, 1, 0);257 // } else {258 // add_weight(1, 0, 0)259 // };260 consumed_weight261 }262 }263264 #[pallet::call]265 impl<T: Config> Pallet<T>266 where267 T::BlockNumber: From<u32> + Into<u32>,268 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,269 {270 #[pallet::weight(T::WeightInfo::set_admin_address())]271 pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {272 ensure_root(origin)?;273 <Admin<T>>::set(Some(admin.as_sub().to_owned()));274275 Ok(())276 }277278 #[pallet::weight(T::WeightInfo::start_app_promotion())]279 pub fn start_app_promotion(280 origin: OriginFor<T>,281 promotion_start_relay_block: Option<T::BlockNumber>,282 ) -> DispatchResult283 where284 <T as frame_system::Config>::BlockNumber: From<u32>,285 {286 ensure_root(origin)?;287288 // Start app-promotion mechanics if it has not been yet initialized289 if <StartBlock<T>>::get() == 0u32.into() {290 let start_block = promotion_start_relay_block291 .unwrap_or(T::RelayBlockNumberProvider::current_block_number());292293 // Set promotion global start block294 <StartBlock<T>>::set(start_block);295296 <NextInterestBlock<T>>::set(start_block + T::RecalculationInterval::get());297 }298299 Ok(())300 }301302 #[pallet::weight(T::WeightInfo::stop_app_promotion())]303 pub fn stop_app_promotion(origin: OriginFor<T>) -> DispatchResult304 where305 <T as frame_system::Config>::BlockNumber: From<u32>,306 {307 ensure_root(origin)?;308309 if <StartBlock<T>>::get() != 0u32.into() {310 <StartBlock<T>>::set(T::BlockNumber::default());311 <NextInterestBlock<T>>::set(T::BlockNumber::default());312 }313314 Ok(())315 }316317 #[pallet::weight(T::WeightInfo::stake())]318 pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {319 let staker_id = ensure_signed(staker)?;320321 ensure!(322 StakesPerAccount::<T>::get(&staker_id) < 10,323 Error::<T>::NoPermission324 );325326 ensure!(327 amount >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(),328 ArithmeticError::Underflow329 );330331 let count = Staked::<T>::iter_prefix((staker_id.clone(),)).count();332333 let balance =334 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);335336 ensure!(balance >= amount, ArithmeticError::Underflow);337338 <<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(339 &staker_id,340 amount,341 WithdrawReasons::all(),342 balance - amount,343 )?;344345 Self::add_lock_balance(&staker_id, amount)?;346347 let block_number = T::RelayBlockNumberProvider::current_block_number();348 let recalc_block = (block_number / T::RecalculationInterval::get() + 2u32.into())349 * T::RecalculationInterval::get();350351 <Staked<T>>::insert((&staker_id, block_number), {352 let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));353 balance_and_recalc_block.0 = balance_and_recalc_block354 .0355 .checked_add(&amount)356 .ok_or(ArithmeticError::Overflow)?;357 balance_and_recalc_block.1 = recalc_block;358 balance_and_recalc_block359 });360361 <TotalStaked<T>>::set(362 <TotalStaked<T>>::get()363 .checked_add(&amount)364 .ok_or(ArithmeticError::Overflow)?,365 );366367 StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);368 Ok(())369 }370371 #[pallet::weight(T::WeightInfo::unstake())]372 pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {373 let staker_id = ensure_signed(staker)?;374375 let mut total_stakes = 0u64;376377 let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))378 .map(|(_, (amount, _))| {379 total_stakes += 1;380 amount381 })382 .sum();383 384 if total_staked.is_zero() {385 return Ok(None.into());386 }387 let block =388 T::RelayBlockNumberProvider::current_block_number() + T::PendingInterval::get();389 <PendingUnstake<T>>::insert(390 (&staker_id, block),391 <PendingUnstake<T>>::get((&staker_id, block))392 .checked_add(&total_staked)393 .ok_or(ArithmeticError::Overflow)?,394 );395396 TotalStaked::<T>::set(397 TotalStaked::<T>::get()398 .checked_sub(&total_staked)399 .ok_or(ArithmeticError::Underflow)?,400 ); // when error we should recover initial stake state for the staker401402 StakesPerAccount::<T>::remove(&staker_id);403404 Ok(None.into())405 }406407 #[pallet::weight(T::WeightInfo::sponsor_collection())]408 pub fn sponsor_collection(409 admin: OriginFor<T>,410 collection_id: CollectionId,411 ) -> DispatchResult {412 let admin_id = ensure_signed(admin)?;413 ensure!(414 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,415 Error::<T>::NoPermission416 );417418 T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)419 }420 #[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]421 pub fn stop_sponsoring_collection(422 admin: OriginFor<T>,423 collection_id: CollectionId,424 ) -> DispatchResult {425 let admin_id = ensure_signed(admin)?;426427 ensure!(428 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,429 Error::<T>::NoPermission430 );431432 ensure!(433 T::CollectionHandler::get_sponsor(collection_id)?434 .ok_or(<Error<T>>::InvalidArgument)?435 == Self::account_id(),436 <Error<T>>::NoPermission437 );438 T::CollectionHandler::remove_collection_sponsor(collection_id)439 }440441 #[pallet::weight(T::WeightInfo::sponsor_contract())]442 pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {443 let admin_id = ensure_signed(admin)?;444445 ensure!(446 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,447 Error::<T>::NoPermission448 );449450 T::ContractHandler::set_sponsor(451 T::CrossAccountId::from_sub(Self::account_id()),452 contract_id,453 )454 }455456 #[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]457 pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {458 let admin_id = ensure_signed(admin)?;459460 ensure!(461 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,462 Error::<T>::NoPermission463 );464465 ensure!(466 T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?467 == T::CrossAccountId::from_sub(Self::account_id()),468 <Error<T>>::NoPermission469 );470 T::ContractHandler::remove_contract_sponsor(contract_id)471 }472473 #[pallet::weight(0)]474 pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {475 let admin_id = ensure_signed(admin)?;476477 ensure!(478 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,479 Error::<T>::NoPermission480 );481482 let current_recalc_block =483 Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());484 let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();485486 let mut storage_iterator = Self::get_next_calculated_key()487 .map_or(Staked::<T>::iter().skip(0), |key| {488 Staked::<T>::iter_from(key).skip(1)489 });490491 NextCalculatedRecord::<T>::set(None);492493 {494 let mut stakers_number = stakers_number.unwrap_or(20);495 let mut current_id = admin_id;496 let mut income_acc = BalanceOf::<T>::default();497498 while let Some(((id, staked_block), (amount, next_recalc_block_for_stake))) =499 storage_iterator.next()500 {501 if current_id != id {502 if income_acc != BalanceOf::<T>::default() {503 <T::Currency as Currency<T::AccountId>>::transfer(504 &T::TreasuryAccountId::get(),505 ¤t_id,506 income_acc,507 ExistenceRequirement::KeepAlive,508 )509 .and_then(|_| Self::add_lock_balance(¤t_id, income_acc))?;510511 Self::deposit_event(Event::StakingRecalculation(512 current_id, amount, income_acc,513 ));514 }515516 if stakers_number == 0 {517 NextCalculatedRecord::<T>::set(Some((id, staked_block)));518 break;519 }520 stakers_number -= 1;521 income_acc = BalanceOf::<T>::default();522 current_id = id;523 };524 if next_recalc_block_for_stake >= current_recalc_block {525 Self::recalculate_and_insert_stake(526 ¤t_id,527 staked_block,528 next_recalc_block,529 amount,530 ((next_recalc_block_for_stake - current_recalc_block)531 / T::RecalculationInterval::get())532 .into() + 1,533 &mut income_acc,534 );535 }536 }537 }538539 Ok(())540 }541 }542}543544impl<T: Config> Pallet<T> {545 pub fn account_id() -> T::AccountId {546 T::PalletId::get().into_account_truncating()547 }548549 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {550 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();551 locked_balance -= amount;552 Self::set_lock_unchecked(staker, locked_balance);553 }554555 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {556 Self::get_locked_balance(staker)557 .map_or(<BalanceOf<T>>::default(), |l| l.amount)558 .checked_add(&amount)559 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))560 .ok_or(ArithmeticError::Overflow.into())561 }562563 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {564 if amount.is_zero() {565 <T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);566 } else {567 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(568 LOCK_IDENTIFIER,569 staker,570 amount,571 WithdrawReasons::all(),572 )573 }574 }575576 pub fn get_locked_balance(577 staker: impl EncodeLike<T::AccountId>,578 ) -> Option<BalanceLock<BalanceOf<T>>> {579 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)580 .into_iter()581 .find(|l| l.id == LOCK_IDENTIFIER)582 }583584 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {585 let staked = Staked::<T>::iter_prefix((staker,))586 .into_iter()587 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {588 acc + amount589 });590 if staked != <BalanceOf<T>>::default() {591 Some(staked)592 } else {593 None594 }595 }596597 pub fn total_staked_by_id_per_block(598 staker: impl EncodeLike<T::AccountId>,599 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {600 let mut staked = Staked::<T>::iter_prefix((staker,))601 .into_iter()602 .map(|(block, (amount, _))| (block, amount))603 .collect::<Vec<_>>();604 staked.sort_by_key(|(block, _)| *block);605 if !staked.is_empty() {606 Some(staked)607 } else {608 None609 }610 }611612 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {613 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {614 Self::total_staked_by_id(s.as_sub())615 })616 // Self::total_staked_by_id(staker.as_sub())617 }618619 pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {620 Self::get_locked_balance(staker.as_sub())621 .map(|l| l.amount)622 .unwrap_or_default()623 }624625 pub fn cross_id_total_staked_per_block(626 staker: T::CrossAccountId,627 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {628 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()629 }630631 fn recalculate_and_insert_stake(632 staker: &T::AccountId,633 staked_block: T::BlockNumber,634 next_recalc_block: T::BlockNumber,635 base: BalanceOf<T>,636 iters: u32,637 income_acc: &mut BalanceOf<T>,638 ) {639 let income = Self::calculate_income(base, iters);640641 base.checked_add(&income).map(|res| {642 <Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));643 *income_acc += income;644 });645 }646647 fn calculate_income<I>(base: I, iters: u32) -> I648 where649 I: EncodeLike<BalanceOf<T>> + Balance,650 {651 let mut income = base;652653 (0..iters).for_each(|_| income += T::IntervalIncome::get() * income);654655 income - base656 }657658 fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {659 (current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()660 }661662 // fn get_next_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {663 // Self::get_current_recalc_block(current_relay_block) + T::RecalculationInterval::get()664 // }665666 fn get_next_calculated_key() -> Option<Vec<u8>> {667 Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))668 }669}670671impl<T: Config> Pallet<T>672where673 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,674{675 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {676 staker.map_or(PendingUnstake::<T>::iter_values().sum(), |s| {677 PendingUnstake::<T>::iter_prefix_values((s.as_sub(),)).sum()678 })679 }680681 pub fn cross_id_pending_unstake_per_block(682 staker: T::CrossAccountId,683 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {684 let mut unsorted_res = PendingUnstake::<T>::iter_prefix((staker.as_sub(),))685 .into_iter()686 .collect::<Vec<_>>();687 unsorted_res.sort_by_key(|(block, _)| *block);688 unsorted_res689 }690}