difftreelog
fix stake 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::{40 vec::{Vec},41 vec,42 iter::Sum,43 borrow::ToOwned,44 cell::RefCell,45};46use sp_core::H160;47use codec::EncodeLike;48use pallet_balances::BalanceLock;49pub use types::*;5051// use up_common::constants::{DAYS, UNIQUE};52use up_data_structs::CollectionId;5354use frame_support::{55 dispatch::{DispatchResult},56 traits::{57 Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,58 },59 ensure,60};6162use weights::WeightInfo;6364pub use pallet::*;65use pallet_evm::account::CrossAccountId;66use sp_runtime::{67 Perbill,68 traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion, Zero},69 ArithmeticError,70};7172pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";7374const PENDING_LIMIT_PER_BLOCK: u32 = 3;7576type BalanceOf<T> =77 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;7879#[frame_support::pallet]80pub mod pallet {81 use super::*;82 use frame_support::{83 Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId,84 traits::ReservableCurrency,85 };86 use frame_system::pallet_prelude::*;8788 #[pallet::config]89 pub trait Config: frame_system::Config + pallet_evm::account::Config {90 type Currency: ExtendedLockableCurrency<Self::AccountId>91 + ReservableCurrency<Self::AccountId>;9293 type CollectionHandler: CollectionHandler<94 AccountId = Self::AccountId,95 CollectionId = CollectionId,96 >;9798 type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;99100 type TreasuryAccountId: Get<Self::AccountId>;101102 /// The app's pallet id, used for deriving its sovereign account ID.103 #[pallet::constant]104 type PalletId: Get<PalletId>;105106 /// In relay blocks.107 #[pallet::constant]108 type RecalculationInterval: Get<Self::BlockNumber>;109 /// In relay blocks.110 #[pallet::constant]111 type PendingInterval: Get<Self::BlockNumber>;112113 // /// In chain blocks.114 // #[pallet::constant]115 // type Day: Get<Self::BlockNumber>; // useless116117 #[pallet::constant]118 type Nominal: Get<BalanceOf<Self>>;119120 #[pallet::constant]121 type IntervalIncome: Get<Perbill>;122123 /// Weight information for extrinsics in this pallet.124 type WeightInfo: WeightInfo;125126 // The relay block number provider127 type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;128129 /// Events compatible with [`frame_system::Config::Event`].130 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;131 }132133 #[pallet::pallet]134 #[pallet::generate_store(pub(super) trait Store)]135 pub struct Pallet<T>(_);136137 #[pallet::event]138 #[pallet::generate_deposit(fn deposit_event)]139 pub enum Event<T: Config> {140 StakingRecalculation(141 /// An recalculated staker142 T::AccountId,143 /// Base on which interest is calculated144 BalanceOf<T>,145 /// Amount of accrued interest146 BalanceOf<T>,147 ),148 Stake(T::AccountId, BalanceOf<T>),149 Unstake(T::AccountId, BalanceOf<T>),150 SetAdmin(T::AccountId),151 }152153 #[pallet::error]154 pub enum Error<T> {155 /// Error due to action requiring admin to be set156 AdminNotSet,157 /// No permission to perform an action158 NoPermission,159 /// Insufficient funds to perform an action160 NotSufficientFunds,161 PendingForBlockOverflow,162 /// An error related to the fact that an invalid argument was passed to perform an action163 InvalidArgument,164 }165166 #[pallet::storage]167 pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;168169 #[pallet::storage]170 pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;171172 /// Amount of tokens staked by account in the blocknumber.173 #[pallet::storage]174 pub type Staked<T: Config> = StorageNMap<175 Key = (176 Key<Blake2_128Concat, T::AccountId>,177 Key<Twox64Concat, T::BlockNumber>,178 ),179 Value = (BalanceOf<T>, T::BlockNumber),180 QueryKind = ValueQuery,181 >;182 /// Amount of stakes for an Account183 #[pallet::storage]184 pub type StakesPerAccount<T: Config> =185 StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;186187 /// Amount of tokens pending unstake per user per block.188 // #[pallet::storage]189 // pub type PendingUnstake<T: Config> = StorageNMap<190 // Key = (191 // Key<Blake2_128Concat, T::AccountId>,192 // Key<Twox64Concat, T::BlockNumber>,193 // ),194 // Value = BalanceOf<T>,195 // QueryKind = ValueQuery,196 // >;197 #[pallet::storage]198 pub type PendingUnstake<T: Config> = StorageMap<199 _,200 Twox64Concat,201 T::BlockNumber,202 BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,203 ValueQuery,204 >;205206 // /// A block when app-promotion has started .I think this is redundant, because we only need `NextInterestBlock`.207 // #[pallet::storage]208 // pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;209210 // /// Next target block when interest is recalculated211 // #[pallet::storage]212 // #[pallet::getter(fn get_interest_block)]213 // pub type NextInterestBlock<T: Config> =214 // StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;215216 /// Stores hash a record for which the last revenue recalculation was performed.217 /// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.218 #[pallet::storage]219 #[pallet::getter(fn get_next_calculated_record)]220 pub type NextCalculatedRecord<T: Config> =221 StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;222223 #[pallet::hooks]224 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {225 fn on_initialize(current_block_number: T::BlockNumber) -> Weight226 where227 <T as frame_system::Config>::BlockNumber: From<u32>,228 {229 let block_pending = PendingUnstake::<T>::take(current_block_number);230 let counter = block_pending.len() as u32;231 // add_weight(0, 1, 0);232233 if !block_pending.is_empty() {234 block_pending.into_iter().for_each(|(staker, amount)| {235 <T::Currency as ReservableCurrency<T::AccountId>>::unreserve(&staker, amount);236 });237 }238239 T::WeightInfo::on_initialize(counter)240 }241 }242243 #[pallet::call]244 impl<T: Config> Pallet<T>245 where246 T::BlockNumber: From<u32> + Into<u32>,247 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,248 {249 #[pallet::weight(T::WeightInfo::set_admin_address())]250 pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {251 ensure_root(origin)?;252253 <Admin<T>>::set(Some(admin.as_sub().to_owned()));254255 Self::deposit_event(Event::SetAdmin(admin.as_sub().to_owned()));256257 Ok(())258 }259260 #[pallet::weight(T::WeightInfo::stake())]261 pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {262 let staker_id = ensure_signed(staker)?;263264 ensure!(265 StakesPerAccount::<T>::get(&staker_id) < 10,266 Error::<T>::NoPermission267 );268269 ensure!(270 amount >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(),271 ArithmeticError::Underflow272 );273274 let balance =275 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);276277 // ensure!(balance >= amount, ArithmeticError::Underflow);278279 <<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(280 &staker_id,281 amount,282 WithdrawReasons::all(),283 balance - amount,284 )?;285286 Self::add_lock_balance(&staker_id, amount)?;287288 let block_number = T::RelayBlockNumberProvider::current_block_number();289290 let recalculate_after_interval: T::BlockNumber =291 if block_number % T::RecalculationInterval::get() == 0u32.into() {292 1u32.into()293 } else {294 2u32.into()295 };296297 let recalc_block = (block_number / T::RecalculationInterval::get()298 + recalculate_after_interval)299 * T::RecalculationInterval::get();300301 <Staked<T>>::insert((&staker_id, block_number), {302 let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));303 balance_and_recalc_block.0 = balance_and_recalc_block304 .0305 .checked_add(&amount)306 .ok_or(ArithmeticError::Overflow)?;307 balance_and_recalc_block.1 = recalc_block;308 balance_and_recalc_block309 });310311 <TotalStaked<T>>::set(312 <TotalStaked<T>>::get()313 .checked_add(&amount)314 .ok_or(ArithmeticError::Overflow)?,315 );316317 StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);318319 Self::deposit_event(Event::Stake(staker_id, amount));320321 Ok(())322 }323324 #[pallet::weight(T::WeightInfo::unstake())]325 pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {326 let staker_id = ensure_signed(staker)?;327 let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();328 let mut pendings = <PendingUnstake<T>>::get(block);329330 ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);331332 let mut total_stakes = 0u64;333334 let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))335 .map(|(_, (amount, _))| {336 total_stakes += 1;337 amount338 })339 .sum();340341 if total_staked.is_zero() {342 return Ok(None.into()); // TO-DO343 }344345 pendings346 .try_push((staker_id.clone(), total_staked))347 .map_err(|_| Error::<T>::PendingForBlockOverflow)?;348349 <PendingUnstake<T>>::insert(block, pendings);350351 Self::unlock_balance_unchecked(&staker_id, total_staked);352353 <T::Currency as ReservableCurrency<T::AccountId>>::reserve(&staker_id, total_staked)?;354355 TotalStaked::<T>::set(356 TotalStaked::<T>::get()357 .checked_sub(&total_staked)358 .ok_or(ArithmeticError::Underflow)?,359 );360361 StakesPerAccount::<T>::remove(&staker_id);362363 Self::deposit_event(Event::Unstake(staker_id, total_staked));364365 Ok(None.into())366 }367368 #[pallet::weight(T::WeightInfo::sponsor_collection())]369 pub fn sponsor_collection(370 admin: OriginFor<T>,371 collection_id: CollectionId,372 ) -> DispatchResult {373 let admin_id = ensure_signed(admin)?;374 ensure!(375 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,376 Error::<T>::NoPermission377 );378379 T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)380 }381 #[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]382 pub fn stop_sponsoring_collection(383 admin: OriginFor<T>,384 collection_id: CollectionId,385 ) -> DispatchResult {386 let admin_id = ensure_signed(admin)?;387388 ensure!(389 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,390 Error::<T>::NoPermission391 );392393 ensure!(394 T::CollectionHandler::sponsor(collection_id)?.ok_or(<Error<T>>::InvalidArgument)?395 == Self::account_id(),396 <Error<T>>::NoPermission397 );398 T::CollectionHandler::remove_collection_sponsor(collection_id)399 }400401 #[pallet::weight(T::WeightInfo::sponsor_contract())]402 pub fn sponsor_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {403 let admin_id = ensure_signed(admin)?;404405 ensure!(406 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,407 Error::<T>::NoPermission408 );409410 T::ContractHandler::set_sponsor(411 T::CrossAccountId::from_sub(Self::account_id()),412 contract_id,413 )414 }415416 #[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]417 pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {418 let admin_id = ensure_signed(admin)?;419420 ensure!(421 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,422 Error::<T>::NoPermission423 );424425 ensure!(426 T::ContractHandler::sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?427 == T::CrossAccountId::from_sub(Self::account_id()),428 <Error<T>>::NoPermission429 );430 T::ContractHandler::remove_contract_sponsor(contract_id)431 }432433 #[pallet::weight(T::WeightInfo::payout_stakers(stakers_number.unwrap_or(20) as u32))]434 pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {435 let admin_id = ensure_signed(admin)?;436437 ensure!(438 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,439 Error::<T>::NoPermission440 );441442 let current_recalc_block =443 Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());444 let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();445446 let mut storage_iterator = Self::get_next_calculated_key()447 .map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));448449 NextCalculatedRecord::<T>::set(None);450451 // {452 // let mut stakers_number = stakers_number.unwrap_or(20);453 // let mut last_id = admin_id;454 // let mut income_acc = BalanceOf::<T>::default();455 // let mut amount_acc = BalanceOf::<T>::default();456457 // while let Some((458 // (current_id, staked_block),459 // (amount, next_recalc_block_for_stake),460 // )) = storage_iterator.next()461 // {462 // if last_id != current_id {463 // if income_acc != BalanceOf::<T>::default() {464 // <T::Currency as Currency<T::AccountId>>::transfer(465 // &T::TreasuryAccountId::get(),466 // &last_id,467 // income_acc,468 // ExistenceRequirement::KeepAlive,469 // )470 // .and_then(|_| Self::add_lock_balance(&last_id, income_acc))?;471472 // Self::deposit_event(Event::StakingRecalculation(473 // last_id, amount, income_acc,474 // ));475 // }476477 // if stakers_number == 0 {478 // NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));479 // break;480 // }481 // stakers_number -= 1;482 // income_acc = BalanceOf::<T>::default();483 // last_id = current_id;484 // };485 // if current_recalc_block >= next_recalc_block_for_stake {486 // Self::recalculate_and_insert_stake(487 // &last_id,488 // staked_block,489 // next_recalc_block,490 // amount,491 // ((current_recalc_block - next_recalc_block_for_stake)492 // / T::RecalculationInterval::get())493 // .into() + 1,494 // &mut income_acc,495 // );496 // }497 // }498 // }499500 // {501 // let mut stakers_number = stakers_number.unwrap_or(20);502 // let last_id = RefCell::new(None);503 // let income_acc = RefCell::new(BalanceOf::<T>::default());504 // let amount_acc = RefCell::new(BalanceOf::<T>::default());505506 // let flush_stake = || -> DispatchResult {507 // if let Some(last_id) = &*last_id.borrow() {508 // if !income_acc.borrow().is_zero() {509 // <T::Currency as Currency<T::AccountId>>::transfer(510 // &T::TreasuryAccountId::get(),511 // last_id,512 // *income_acc.borrow(),513 // ExistenceRequirement::KeepAlive,514 // )515 // .and_then(|_| {516 // Self::add_lock_balance(last_id, *income_acc.borrow());517 // <TotalStaked<T>>::try_mutate(|staked| {518 // staked519 // .checked_add(&*income_acc.borrow())520 // .ok_or(ArithmeticError::Overflow.into())521 // })522 // })?;523524 // Self::deposit_event(Event::StakingRecalculation(525 // last_id.clone(),526 // *amount_acc.borrow(),527 // *income_acc.borrow(),528 // ));529 // }530531 // *income_acc.borrow_mut() = BalanceOf::<T>::default();532 // *amount_acc.borrow_mut() = BalanceOf::<T>::default();533 // }534 // Ok(())535 // };536537 // while let Some((538 // (current_id, staked_block),539 // (amount, next_recalc_block_for_stake),540 // )) = storage_iterator.next()541 // {542 // if stakers_number == 0 {543 // NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));544 // break;545 // }546 // stakers_number -= 1;547 // if last_id.borrow().as_ref() != Some(¤t_id) {548 // flush_stake()?;549 // };550 // *last_id.borrow_mut() = Some(current_id.clone());551 // if current_recalc_block >= next_recalc_block_for_stake {552 // *amount_acc.borrow_mut() += amount;553 // Self::recalculate_and_insert_stake(554 // ¤t_id,555 // staked_block,556 // next_recalc_block,557 // amount,558 // ((current_recalc_block - next_recalc_block_for_stake)559 // / T::RecalculationInterval::get())560 // .into() + 1,561 // &mut *income_acc.borrow_mut(),562 // );563 // }564 // }565 // flush_stake()?;566 // }567568 {569 let mut stakers_number = stakers_number.unwrap_or(20);570 let last_id = RefCell::new(None);571 let income_acc = RefCell::new(BalanceOf::<T>::default());572 let amount_acc = RefCell::new(BalanceOf::<T>::default());573574 let flush_stake = || -> DispatchResult {575 if let Some(last_id) = &*last_id.borrow() {576 if !income_acc.borrow().is_zero() {577 <T::Currency as Currency<T::AccountId>>::transfer(578 &T::TreasuryAccountId::get(),579 last_id,580 *income_acc.borrow(),581 ExistenceRequirement::KeepAlive,582 )583 .and_then(|_| {584 Self::add_lock_balance(last_id, *income_acc.borrow())?;585 <TotalStaked<T>>::try_mutate(|staked| {586 staked587 .checked_add(&*income_acc.borrow())588 .ok_or(ArithmeticError::Overflow.into())589 })590 })?;591592 Self::deposit_event(Event::StakingRecalculation(593 last_id.clone(),594 *amount_acc.borrow(),595 *income_acc.borrow(),596 ));597 }598599 *income_acc.borrow_mut() = BalanceOf::<T>::default();600 *amount_acc.borrow_mut() = BalanceOf::<T>::default();601 }602 Ok(())603 };604605 while let Some((606 (current_id, staked_block),607 (amount, next_recalc_block_for_stake),608 )) = storage_iterator.next()609 {610 if stakers_number == 0 {611 NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));612 break;613 }614 if last_id.borrow().as_ref() != Some(¤t_id) {615 flush_stake()?;616 *last_id.borrow_mut() = Some(current_id.clone());617 stakers_number -= 1;618 };619 if current_recalc_block >= next_recalc_block_for_stake {620 *amount_acc.borrow_mut() += amount;621 Self::recalculate_and_insert_stake(622 ¤t_id,623 staked_block,624 next_recalc_block,625 amount,626 ((current_recalc_block - next_recalc_block_for_stake)627 / T::RecalculationInterval::get())628 .into() + 1,629 &mut *income_acc.borrow_mut(),630 );631 }632 }633 flush_stake()?;634 }635636 Ok(())637 }638 }639}640641impl<T: Config> Pallet<T> {642 pub fn account_id() -> T::AccountId {643 T::PalletId::get().into_account_truncating()644 }645646 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {647 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();648 locked_balance -= amount;649 Self::set_lock_unchecked(staker, locked_balance);650 }651652 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {653 Self::get_locked_balance(staker)654 .map_or(<BalanceOf<T>>::default(), |l| l.amount)655 .checked_add(&amount)656 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))657 .ok_or(ArithmeticError::Overflow.into())658 }659660 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {661 if amount.is_zero() {662 <T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);663 } else {664 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(665 LOCK_IDENTIFIER,666 staker,667 amount,668 WithdrawReasons::all(),669 )670 }671 }672673 pub fn get_locked_balance(674 staker: impl EncodeLike<T::AccountId>,675 ) -> Option<BalanceLock<BalanceOf<T>>> {676 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)677 .into_iter()678 .find(|l| l.id == LOCK_IDENTIFIER)679 }680681 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {682 let staked = Staked::<T>::iter_prefix((staker,))683 .into_iter()684 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {685 acc + amount686 });687 if staked != <BalanceOf<T>>::default() {688 Some(staked)689 } else {690 None691 }692 }693694 pub fn total_staked_by_id_per_block(695 staker: impl EncodeLike<T::AccountId>,696 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {697 let mut staked = Staked::<T>::iter_prefix((staker,))698 .into_iter()699 .map(|(block, (amount, _))| (block, amount))700 .collect::<Vec<_>>();701 staked.sort_by_key(|(block, _)| *block);702 if !staked.is_empty() {703 Some(staked)704 } else {705 None706 }707 }708709 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {710 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {711 Self::total_staked_by_id(s.as_sub())712 })713 // Self::total_staked_by_id(staker.as_sub())714 }715716 pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {717 Self::get_locked_balance(staker.as_sub())718 .map(|l| l.amount)719 .unwrap_or_default()720 }721722 pub fn cross_id_total_staked_per_block(723 staker: T::CrossAccountId,724 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {725 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()726 }727728 fn recalculate_and_insert_stake(729 staker: &T::AccountId,730 staked_block: T::BlockNumber,731 next_recalc_block: T::BlockNumber,732 base: BalanceOf<T>,733 iters: u32,734 income_acc: &mut BalanceOf<T>,735 ) {736 let income = Self::calculate_income(base, iters);737738 base.checked_add(&income).map(|res| {739 <Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));740 *income_acc += income;741 });742 }743744 fn calculate_income<I>(base: I, iters: u32) -> I745 where746 I: EncodeLike<BalanceOf<T>> + Balance,747 {748 let mut income = base;749750 (0..iters).for_each(|_| income += T::IntervalIncome::get() * income);751752 income - base753 }754755 fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {756 (current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()757 }758759 // fn get_next_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {760 // Self::get_current_recalc_block(current_relay_block) + T::RecalculationInterval::get()761 // }762763 fn get_next_calculated_key() -> Option<Vec<u8>> {764 Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))765 }766}767768impl<T: Config> Pallet<T>769where770 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,771{772 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {773 staker.map_or(774 PendingUnstake::<T>::iter_values()775 .flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))776 .sum(),777 |s| {778 PendingUnstake::<T>::iter_values()779 .flatten()780 .filter_map(|(id, amount)| {781 if id == *s.as_sub() {782 Some(amount)783 } else {784 None785 }786 })787 .sum()788 },789 )790 }791792 pub fn cross_id_pending_unstake_per_block(793 staker: T::CrossAccountId,794 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {795 let mut unsorted_res = vec![];796 PendingUnstake::<T>::iter().for_each(|(block, pendings)| {797 pendings.into_iter().for_each(|(id, amount)| {798 if id == *staker.as_sub() {799 unsorted_res.push((block, amount));800 };801 })802 });803804 unsorted_res.sort_by_key(|(block, _)| *block);805 unsorted_res806 }807}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # App promotion18//!19//! The app promotion pallet is designed to ... .20//!21//! ## Interface22//!23//! ### Dispatchable Functions24//!25//! * `start_inflation` - This method sets the inflation start date. Can be only called once.26//! Inflation start block can be backdated and will catch up. The method will create Treasury27//! account if it does not exist and perform the first inflation deposit.2829// #![recursion_limit = "1024"]30#![cfg_attr(not(feature = "std"), no_std)]3132#[cfg(feature = "runtime-benchmarks")]33mod benchmarking;34#[cfg(test)]35mod tests;36pub mod types;37pub mod weights;3839use sp_std::{40 vec::{Vec},41 vec,42 iter::Sum,43 borrow::ToOwned,44 cell::RefCell,45};46use sp_core::H160;47use codec::EncodeLike;48use pallet_balances::BalanceLock;49pub use types::*;5051// use up_common::constants::{DAYS, UNIQUE};52use up_data_structs::CollectionId;5354use frame_support::{55 dispatch::{DispatchResult},56 traits::{57 Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,58 },59 ensure,60};6162use weights::WeightInfo;6364pub use pallet::*;65use pallet_evm::account::CrossAccountId;66use sp_runtime::{67 Perbill,68 traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion, Zero},69 ArithmeticError,70};7172pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";7374const PENDING_LIMIT_PER_BLOCK: u32 = 3;7576type BalanceOf<T> =77 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;7879#[frame_support::pallet]80pub mod pallet {81 use super::*;82 use frame_support::{83 Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId,84 traits::ReservableCurrency,85 };86 use frame_system::pallet_prelude::*;8788 #[pallet::config]89 pub trait Config: frame_system::Config + pallet_evm::account::Config {90 type Currency: ExtendedLockableCurrency<Self::AccountId>91 + ReservableCurrency<Self::AccountId>;9293 type CollectionHandler: CollectionHandler<94 AccountId = Self::AccountId,95 CollectionId = CollectionId,96 >;9798 type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;99100 type TreasuryAccountId: Get<Self::AccountId>;101102 /// The app's pallet id, used for deriving its sovereign account ID.103 #[pallet::constant]104 type PalletId: Get<PalletId>;105106 /// In relay blocks.107 #[pallet::constant]108 type RecalculationInterval: Get<Self::BlockNumber>;109 /// In relay blocks.110 #[pallet::constant]111 type PendingInterval: Get<Self::BlockNumber>;112113 // /// In chain blocks.114 // #[pallet::constant]115 // type Day: Get<Self::BlockNumber>; // useless116117 #[pallet::constant]118 type Nominal: Get<BalanceOf<Self>>;119120 #[pallet::constant]121 type IntervalIncome: Get<Perbill>;122123 /// Weight information for extrinsics in this pallet.124 type WeightInfo: WeightInfo;125126 // The relay block number provider127 type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;128129 /// Events compatible with [`frame_system::Config::Event`].130 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;131 }132133 #[pallet::pallet]134 #[pallet::generate_store(pub(super) trait Store)]135 pub struct Pallet<T>(_);136137 #[pallet::event]138 #[pallet::generate_deposit(fn deposit_event)]139 pub enum Event<T: Config> {140 StakingRecalculation(141 /// An recalculated staker142 T::AccountId,143 /// Base on which interest is calculated144 BalanceOf<T>,145 /// Amount of accrued interest146 BalanceOf<T>,147 ),148 Stake(T::AccountId, BalanceOf<T>),149 Unstake(T::AccountId, BalanceOf<T>),150 SetAdmin(T::AccountId),151 }152153 #[pallet::error]154 pub enum Error<T> {155 /// Error due to action requiring admin to be set156 AdminNotSet,157 /// No permission to perform an action158 NoPermission,159 /// Insufficient funds to perform an action160 NotSufficientFunds,161 PendingForBlockOverflow,162 /// An error related to the fact that an invalid argument was passed to perform an action163 InvalidArgument,164 }165166 #[pallet::storage]167 pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;168169 #[pallet::storage]170 pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;171172 /// Amount of tokens staked by account in the blocknumber.173 #[pallet::storage]174 pub type Staked<T: Config> = StorageNMap<175 Key = (176 Key<Blake2_128Concat, T::AccountId>,177 Key<Twox64Concat, T::BlockNumber>,178 ),179 Value = (BalanceOf<T>, T::BlockNumber),180 QueryKind = ValueQuery,181 >;182 /// Amount of stakes for an Account183 #[pallet::storage]184 pub type StakesPerAccount<T: Config> =185 StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;186187 /// Amount of tokens pending unstake per user per block.188 // #[pallet::storage]189 // pub type PendingUnstake<T: Config> = StorageNMap<190 // Key = (191 // Key<Blake2_128Concat, T::AccountId>,192 // Key<Twox64Concat, T::BlockNumber>,193 // ),194 // Value = BalanceOf<T>,195 // QueryKind = ValueQuery,196 // >;197 #[pallet::storage]198 pub type PendingUnstake<T: Config> = StorageMap<199 _,200 Twox64Concat,201 T::BlockNumber,202 BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,203 ValueQuery,204 >;205206 // /// A block when app-promotion has started .I think this is redundant, because we only need `NextInterestBlock`.207 // #[pallet::storage]208 // pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;209210 // /// Next target block when interest is recalculated211 // #[pallet::storage]212 // #[pallet::getter(fn get_interest_block)]213 // pub type NextInterestBlock<T: Config> =214 // StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;215216 /// Stores hash a record for which the last revenue recalculation was performed.217 /// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.218 #[pallet::storage]219 #[pallet::getter(fn get_next_calculated_record)]220 pub type NextCalculatedRecord<T: Config> =221 StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;222223 #[pallet::hooks]224 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {225 fn on_initialize(current_block_number: T::BlockNumber) -> Weight226 where227 <T as frame_system::Config>::BlockNumber: From<u32>,228 {229 let block_pending = PendingUnstake::<T>::take(current_block_number);230 let counter = block_pending.len() as u32;231 // add_weight(0, 1, 0);232233 if !block_pending.is_empty() {234 block_pending.into_iter().for_each(|(staker, amount)| {235 <T::Currency as ReservableCurrency<T::AccountId>>::unreserve(&staker, amount);236 });237 }238239 T::WeightInfo::on_initialize(counter)240 }241 }242243 #[pallet::call]244 impl<T: Config> Pallet<T>245 where246 T::BlockNumber: From<u32> + Into<u32>,247 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,248 {249 #[pallet::weight(T::WeightInfo::set_admin_address())]250 pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {251 ensure_root(origin)?;252253 <Admin<T>>::set(Some(admin.as_sub().to_owned()));254255 Self::deposit_event(Event::SetAdmin(admin.as_sub().to_owned()));256257 Ok(())258 }259260 #[pallet::weight(T::WeightInfo::stake())]261 pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {262 let staker_id = ensure_signed(staker)?;263264 ensure!(265 StakesPerAccount::<T>::get(&staker_id) < 10,266 Error::<T>::NoPermission267 );268269 ensure!(270 amount >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(),271 ArithmeticError::Underflow272 );273274 let balance =275 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);276277 // ensure!(balance >= amount, ArithmeticError::Underflow);278279 <<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(280 &staker_id,281 amount,282 WithdrawReasons::all(),283 balance284 .checked_sub(&amount)285 .ok_or(ArithmeticError::Underflow)?,286 )?;287288 Self::add_lock_balance(&staker_id, amount)?;289290 let block_number = T::RelayBlockNumberProvider::current_block_number();291292 let recalculate_after_interval: T::BlockNumber =293 if block_number % T::RecalculationInterval::get() == 0u32.into() {294 1u32.into()295 } else {296 2u32.into()297 };298299 let recalc_block = (block_number / T::RecalculationInterval::get()300 + recalculate_after_interval)301 * T::RecalculationInterval::get();302303 <Staked<T>>::insert((&staker_id, block_number), {304 let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));305 balance_and_recalc_block.0 = balance_and_recalc_block306 .0307 .checked_add(&amount)308 .ok_or(ArithmeticError::Overflow)?;309 balance_and_recalc_block.1 = recalc_block;310 balance_and_recalc_block311 });312313 <TotalStaked<T>>::set(314 <TotalStaked<T>>::get()315 .checked_add(&amount)316 .ok_or(ArithmeticError::Overflow)?,317 );318319 StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);320321 Self::deposit_event(Event::Stake(staker_id, amount));322323 Ok(())324 }325326 #[pallet::weight(T::WeightInfo::unstake())]327 pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {328 let staker_id = ensure_signed(staker)?;329 let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();330 let mut pendings = <PendingUnstake<T>>::get(block);331332 ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);333334 let mut total_stakes = 0u64;335336 let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))337 .map(|(_, (amount, _))| {338 total_stakes += 1;339 amount340 })341 .sum();342343 if total_staked.is_zero() {344 return Ok(None.into()); // TO-DO345 }346347 pendings348 .try_push((staker_id.clone(), total_staked))349 .map_err(|_| Error::<T>::PendingForBlockOverflow)?;350351 <PendingUnstake<T>>::insert(block, pendings);352353 Self::unlock_balance_unchecked(&staker_id, total_staked);354355 <T::Currency as ReservableCurrency<T::AccountId>>::reserve(&staker_id, total_staked)?;356357 TotalStaked::<T>::set(358 TotalStaked::<T>::get()359 .checked_sub(&total_staked)360 .ok_or(ArithmeticError::Underflow)?,361 );362363 StakesPerAccount::<T>::remove(&staker_id);364365 Self::deposit_event(Event::Unstake(staker_id, total_staked));366367 Ok(None.into())368 }369370 #[pallet::weight(T::WeightInfo::sponsor_collection())]371 pub fn sponsor_collection(372 admin: OriginFor<T>,373 collection_id: CollectionId,374 ) -> DispatchResult {375 let admin_id = ensure_signed(admin)?;376 ensure!(377 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,378 Error::<T>::NoPermission379 );380381 T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)382 }383 #[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]384 pub fn stop_sponsoring_collection(385 admin: OriginFor<T>,386 collection_id: CollectionId,387 ) -> DispatchResult {388 let admin_id = ensure_signed(admin)?;389390 ensure!(391 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,392 Error::<T>::NoPermission393 );394395 ensure!(396 T::CollectionHandler::sponsor(collection_id)?.ok_or(<Error<T>>::InvalidArgument)?397 == Self::account_id(),398 <Error<T>>::NoPermission399 );400 T::CollectionHandler::remove_collection_sponsor(collection_id)401 }402403 #[pallet::weight(T::WeightInfo::sponsor_contract())]404 pub fn sponsor_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {405 let admin_id = ensure_signed(admin)?;406407 ensure!(408 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,409 Error::<T>::NoPermission410 );411412 T::ContractHandler::set_sponsor(413 T::CrossAccountId::from_sub(Self::account_id()),414 contract_id,415 )416 }417418 #[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]419 pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {420 let admin_id = ensure_signed(admin)?;421422 ensure!(423 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,424 Error::<T>::NoPermission425 );426427 ensure!(428 T::ContractHandler::sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?429 == T::CrossAccountId::from_sub(Self::account_id()),430 <Error<T>>::NoPermission431 );432 T::ContractHandler::remove_contract_sponsor(contract_id)433 }434435 #[pallet::weight(T::WeightInfo::payout_stakers(stakers_number.unwrap_or(20) as u32))]436 pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {437 let admin_id = ensure_signed(admin)?;438439 ensure!(440 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,441 Error::<T>::NoPermission442 );443444 let current_recalc_block =445 Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());446 let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();447448 let mut storage_iterator = Self::get_next_calculated_key()449 .map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));450451 NextCalculatedRecord::<T>::set(None);452453 // {454 // let mut stakers_number = stakers_number.unwrap_or(20);455 // let mut last_id = admin_id;456 // let mut income_acc = BalanceOf::<T>::default();457 // let mut amount_acc = BalanceOf::<T>::default();458459 // while let Some((460 // (current_id, staked_block),461 // (amount, next_recalc_block_for_stake),462 // )) = storage_iterator.next()463 // {464 // if last_id != current_id {465 // if income_acc != BalanceOf::<T>::default() {466 // <T::Currency as Currency<T::AccountId>>::transfer(467 // &T::TreasuryAccountId::get(),468 // &last_id,469 // income_acc,470 // ExistenceRequirement::KeepAlive,471 // )472 // .and_then(|_| Self::add_lock_balance(&last_id, income_acc))?;473474 // Self::deposit_event(Event::StakingRecalculation(475 // last_id, amount, income_acc,476 // ));477 // }478479 // if stakers_number == 0 {480 // NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));481 // break;482 // }483 // stakers_number -= 1;484 // income_acc = BalanceOf::<T>::default();485 // last_id = current_id;486 // };487 // if current_recalc_block >= next_recalc_block_for_stake {488 // Self::recalculate_and_insert_stake(489 // &last_id,490 // staked_block,491 // next_recalc_block,492 // amount,493 // ((current_recalc_block - next_recalc_block_for_stake)494 // / T::RecalculationInterval::get())495 // .into() + 1,496 // &mut income_acc,497 // );498 // }499 // }500 // }501502 // {503 // let mut stakers_number = stakers_number.unwrap_or(20);504 // let last_id = RefCell::new(None);505 // let income_acc = RefCell::new(BalanceOf::<T>::default());506 // let amount_acc = RefCell::new(BalanceOf::<T>::default());507508 // let flush_stake = || -> DispatchResult {509 // if let Some(last_id) = &*last_id.borrow() {510 // if !income_acc.borrow().is_zero() {511 // <T::Currency as Currency<T::AccountId>>::transfer(512 // &T::TreasuryAccountId::get(),513 // last_id,514 // *income_acc.borrow(),515 // ExistenceRequirement::KeepAlive,516 // )517 // .and_then(|_| {518 // Self::add_lock_balance(last_id, *income_acc.borrow());519 // <TotalStaked<T>>::try_mutate(|staked| {520 // staked521 // .checked_add(&*income_acc.borrow())522 // .ok_or(ArithmeticError::Overflow.into())523 // })524 // })?;525526 // Self::deposit_event(Event::StakingRecalculation(527 // last_id.clone(),528 // *amount_acc.borrow(),529 // *income_acc.borrow(),530 // ));531 // }532533 // *income_acc.borrow_mut() = BalanceOf::<T>::default();534 // *amount_acc.borrow_mut() = BalanceOf::<T>::default();535 // }536 // Ok(())537 // };538539 // while let Some((540 // (current_id, staked_block),541 // (amount, next_recalc_block_for_stake),542 // )) = storage_iterator.next()543 // {544 // if stakers_number == 0 {545 // NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));546 // break;547 // }548 // stakers_number -= 1;549 // if last_id.borrow().as_ref() != Some(¤t_id) {550 // flush_stake()?;551 // };552 // *last_id.borrow_mut() = Some(current_id.clone());553 // if current_recalc_block >= next_recalc_block_for_stake {554 // *amount_acc.borrow_mut() += amount;555 // Self::recalculate_and_insert_stake(556 // ¤t_id,557 // staked_block,558 // next_recalc_block,559 // amount,560 // ((current_recalc_block - next_recalc_block_for_stake)561 // / T::RecalculationInterval::get())562 // .into() + 1,563 // &mut *income_acc.borrow_mut(),564 // );565 // }566 // }567 // flush_stake()?;568 // }569570 {571 let mut stakers_number = stakers_number.unwrap_or(20);572 let last_id = RefCell::new(None);573 let income_acc = RefCell::new(BalanceOf::<T>::default());574 let amount_acc = RefCell::new(BalanceOf::<T>::default());575576 let flush_stake = || -> DispatchResult {577 if let Some(last_id) = &*last_id.borrow() {578 if !income_acc.borrow().is_zero() {579 <T::Currency as Currency<T::AccountId>>::transfer(580 &T::TreasuryAccountId::get(),581 last_id,582 *income_acc.borrow(),583 ExistenceRequirement::KeepAlive,584 )585 .and_then(|_| {586 Self::add_lock_balance(last_id, *income_acc.borrow())?;587 <TotalStaked<T>>::try_mutate(|staked| {588 staked589 .checked_add(&*income_acc.borrow())590 .ok_or(ArithmeticError::Overflow.into())591 })592 })?;593594 Self::deposit_event(Event::StakingRecalculation(595 last_id.clone(),596 *amount_acc.borrow(),597 *income_acc.borrow(),598 ));599 }600601 *income_acc.borrow_mut() = BalanceOf::<T>::default();602 *amount_acc.borrow_mut() = BalanceOf::<T>::default();603 }604 Ok(())605 };606607 while let Some((608 (current_id, staked_block),609 (amount, next_recalc_block_for_stake),610 )) = storage_iterator.next()611 {612 if stakers_number == 0 {613 NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));614 break;615 }616 if last_id.borrow().as_ref() != Some(¤t_id) {617 flush_stake()?;618 *last_id.borrow_mut() = Some(current_id.clone());619 stakers_number -= 1;620 };621 if current_recalc_block >= next_recalc_block_for_stake {622 *amount_acc.borrow_mut() += amount;623 Self::recalculate_and_insert_stake(624 ¤t_id,625 staked_block,626 next_recalc_block,627 amount,628 ((current_recalc_block - next_recalc_block_for_stake)629 / T::RecalculationInterval::get())630 .into() + 1,631 &mut *income_acc.borrow_mut(),632 );633 }634 }635 flush_stake()?;636 }637638 Ok(())639 }640 }641}642643impl<T: Config> Pallet<T> {644 pub fn account_id() -> T::AccountId {645 T::PalletId::get().into_account_truncating()646 }647648 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {649 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();650 locked_balance -= amount;651 Self::set_lock_unchecked(staker, locked_balance);652 }653654 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {655 Self::get_locked_balance(staker)656 .map_or(<BalanceOf<T>>::default(), |l| l.amount)657 .checked_add(&amount)658 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))659 .ok_or(ArithmeticError::Overflow.into())660 }661662 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {663 if amount.is_zero() {664 <T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);665 } else {666 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(667 LOCK_IDENTIFIER,668 staker,669 amount,670 WithdrawReasons::all(),671 )672 }673 }674675 pub fn get_locked_balance(676 staker: impl EncodeLike<T::AccountId>,677 ) -> Option<BalanceLock<BalanceOf<T>>> {678 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)679 .into_iter()680 .find(|l| l.id == LOCK_IDENTIFIER)681 }682683 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {684 let staked = Staked::<T>::iter_prefix((staker,))685 .into_iter()686 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {687 acc + amount688 });689 if staked != <BalanceOf<T>>::default() {690 Some(staked)691 } else {692 None693 }694 }695696 pub fn total_staked_by_id_per_block(697 staker: impl EncodeLike<T::AccountId>,698 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {699 let mut staked = Staked::<T>::iter_prefix((staker,))700 .into_iter()701 .map(|(block, (amount, _))| (block, amount))702 .collect::<Vec<_>>();703 staked.sort_by_key(|(block, _)| *block);704 if !staked.is_empty() {705 Some(staked)706 } else {707 None708 }709 }710711 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {712 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {713 Self::total_staked_by_id(s.as_sub())714 })715 // Self::total_staked_by_id(staker.as_sub())716 }717718 pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {719 Self::get_locked_balance(staker.as_sub())720 .map(|l| l.amount)721 .unwrap_or_default()722 }723724 pub fn cross_id_total_staked_per_block(725 staker: T::CrossAccountId,726 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {727 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()728 }729730 fn recalculate_and_insert_stake(731 staker: &T::AccountId,732 staked_block: T::BlockNumber,733 next_recalc_block: T::BlockNumber,734 base: BalanceOf<T>,735 iters: u32,736 income_acc: &mut BalanceOf<T>,737 ) {738 let income = Self::calculate_income(base, iters);739740 base.checked_add(&income).map(|res| {741 <Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));742 *income_acc += income;743 });744 }745746 fn calculate_income<I>(base: I, iters: u32) -> I747 where748 I: EncodeLike<BalanceOf<T>> + Balance,749 {750 let mut income = base;751752 (0..iters).for_each(|_| income += T::IntervalIncome::get() * income);753754 income - base755 }756757 fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {758 (current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()759 }760761 // fn get_next_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {762 // Self::get_current_recalc_block(current_relay_block) + T::RecalculationInterval::get()763 // }764765 fn get_next_calculated_key() -> Option<Vec<u8>> {766 Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))767 }768}769770impl<T: Config> Pallet<T>771where772 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,773{774 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {775 staker.map_or(776 PendingUnstake::<T>::iter_values()777 .flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))778 .sum(),779 |s| {780 PendingUnstake::<T>::iter_values()781 .flatten()782 .filter_map(|(id, amount)| {783 if id == *s.as_sub() {784 Some(amount)785 } else {786 None787 }788 })789 .sum()790 },791 )792 }793794 pub fn cross_id_pending_unstake_per_block(795 staker: T::CrossAccountId,796 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {797 let mut unsorted_res = vec![];798 PendingUnstake::<T>::iter().for_each(|(block, pendings)| {799 pendings.into_iter().for_each(|(id, amount)| {800 if id == *staker.as_sub() {801 unsorted_res.push((block, amount));802 };803 })804 });805806 unsorted_res.sort_by_key(|(block, _)| *block);807 unsorted_res808 }809}