difftreelog
fix stake and unstake extrinsics
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 the address of the staker 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_last_calculated_staker)]198 pub type LastCalcucaltedStaker<T: Config> =199 StorageValue<Value = T::AccountId, 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>,263 {264 #[pallet::weight(T::WeightInfo::set_admin_address())]265 pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {266 ensure_root(origin)?;267 <Admin<T>>::set(Some(admin.as_sub().to_owned()));268269 Ok(())270 }271272 #[pallet::weight(T::WeightInfo::start_app_promotion())]273 pub fn start_app_promotion(274 origin: OriginFor<T>,275 promotion_start_relay_block: Option<T::BlockNumber>,276 ) -> DispatchResult277 where278 <T as frame_system::Config>::BlockNumber: From<u32>,279 {280 ensure_root(origin)?;281282 // Start app-promotion mechanics if it has not been yet initialized283 if <StartBlock<T>>::get() == 0u32.into() {284 let start_block = promotion_start_relay_block285 .unwrap_or(T::RelayBlockNumberProvider::current_block_number());286287 // Set promotion global start block288 <StartBlock<T>>::set(start_block);289290 <NextInterestBlock<T>>::set(start_block + T::RecalculationInterval::get());291 }292293 Ok(())294 }295296 #[pallet::weight(T::WeightInfo::stop_app_promotion())]297 pub fn stop_app_promotion(origin: OriginFor<T>) -> DispatchResult298 where299 <T as frame_system::Config>::BlockNumber: From<u32>,300 {301 ensure_root(origin)?;302303 if <StartBlock<T>>::get() != 0u32.into() {304 <StartBlock<T>>::set(T::BlockNumber::default());305 <NextInterestBlock<T>>::set(T::BlockNumber::default());306 }307308 Ok(())309 }310311 #[pallet::weight(T::WeightInfo::stake())]312 pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {313 let staker_id = ensure_signed(staker)?;314315 ensure!(amount >= T::Nominal::get(), ArithmeticError::Underflow);316317 let balance =318 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);319320 ensure!(balance >= amount, ArithmeticError::Underflow);321322 <<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(323 &staker_id,324 amount,325 WithdrawReasons::all(),326 balance - amount,327 )?;328329 Self::add_lock_balance(&staker_id, amount)?;330331 let block_number = T::RelayBlockNumberProvider::current_block_number();332 let recalc_block = (block_number / T::RecalculationInterval::get() + 2u32.into())333 * T::RecalculationInterval::get();334335 <Staked<T>>::insert((&staker_id, block_number), {336 let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));337 balance_and_recalc_block.0 = balance_and_recalc_block338 .0339 .checked_add(&amount)340 .ok_or(ArithmeticError::Overflow)?;341 balance_and_recalc_block.1 = recalc_block;342 balance_and_recalc_block343 });344345 // <TotalStaked<T>>::set(346 // <TotalStaked<T>>::get()347 // .checked_add(&amount)348 // .ok_or(ArithmeticError::Overflow)?,349 // );350351 Ok(())352 }353354 #[pallet::weight(T::WeightInfo::unstake())]355 pub fn unstake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {356 let staker_id = ensure_signed(staker)?;357358 let mut stakes = Staked::<T>::drain_prefix((&staker_id,));359360 // let total_staked = stakes361 // .iter()362 // .fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);363364 // ensure!(total_staked >= amount, ArithmeticError::Underflow);365366 // <TotalStaked<T>>::set(367 // <TotalStaked<T>>::get()368 // .checked_sub(&amount)369 // .ok_or(ArithmeticError::Underflow)?,370 // );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(&amount)378 // .ok_or(ArithmeticError::Overflow)?,379 // );380381 // stakes.sort_by_key(|(block, _)| *block);382383 // let mut acc_amount = amount;384 // let new_state = stakes385 // .into_iter()386 // .map_while(|(block, balance_per_block)| {387 // if acc_amount == <BalanceOf<T>>::default() {388 // return None;389 // }390 // if acc_amount <= balance_per_block {391 // let res = (block, balance_per_block - acc_amount, acc_amount);392 // acc_amount = <BalanceOf<T>>::default();393 // return Some(res);394 // } else {395 // acc_amount -= balance_per_block;396 // return Some((block, <BalanceOf<T>>::default(), acc_amount));397 // }398 // })399 // .collect::<Vec<_>>();400401 // new_state402 // .into_iter()403 // .for_each(|(block, to_staked, _to_pending)| {404 // if to_staked == <BalanceOf<T>>::default() {405 // <Staked<T>>::remove((&staker_id, block));406 // } else {407 // <Staked<T>>::insert((&staker_id, block), to_staked);408 // }409 // });410411 Ok(())412413 // let staker_id = ensure_signed(staker)?;414415 // let mut stakes = Staked::<T>::iter_prefix((&staker_id,)).collect::<Vec<_>>();416417 // let total_staked = stakes418 // .iter()419 // .fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);420421 // ensure!(total_staked >= amount, ArithmeticError::Underflow);422423 // <TotalStaked<T>>::set(424 // <TotalStaked<T>>::get()425 // .checked_sub(&amount)426 // .ok_or(ArithmeticError::Underflow)?,427 // );428429 // let block =430 // T::RelayBlockNumberProvider::current_block_number() + T::PendingInterval::get();431 // <PendingUnstake<T>>::insert(432 // (&staker_id, block),433 // <PendingUnstake<T>>::get((&staker_id, block))434 // .checked_add(&amount)435 // .ok_or(ArithmeticError::Overflow)?,436 // );437438 // stakes.sort_by_key(|(block, _)| *block);439440 // let mut acc_amount = amount;441 // let new_state = stakes442 // .into_iter()443 // .map_while(|(block, balance_per_block)| {444 // if acc_amount == <BalanceOf<T>>::default() {445 // return None;446 // }447 // if acc_amount <= balance_per_block {448 // let res = (block, balance_per_block - acc_amount, acc_amount);449 // acc_amount = <BalanceOf<T>>::default();450 // return Some(res);451 // } else {452 // acc_amount -= balance_per_block;453 // return Some((block, <BalanceOf<T>>::default(), acc_amount));454 // }455 // })456 // .collect::<Vec<_>>();457458 // new_state459 // .into_iter()460 // .for_each(|(block, to_staked, _to_pending)| {461 // if to_staked == <BalanceOf<T>>::default() {462 // <Staked<T>>::remove((&staker_id, block));463 // } else {464 // <Staked<T>>::insert((&staker_id, block), to_staked);465 // }466 // });467468 // Ok(())469 }470471 #[pallet::weight(T::WeightInfo::sponsor_collection())]472 pub fn sponsor_collection(473 admin: OriginFor<T>,474 collection_id: CollectionId,475 ) -> DispatchResult {476 let admin_id = ensure_signed(admin)?;477 ensure!(478 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,479 Error::<T>::NoPermission480 );481482 T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)483 }484 #[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]485 pub fn stop_sponsoring_collection(486 admin: OriginFor<T>,487 collection_id: CollectionId,488 ) -> DispatchResult {489 let admin_id = ensure_signed(admin)?;490491 ensure!(492 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,493 Error::<T>::NoPermission494 );495496 ensure!(497 T::CollectionHandler::get_sponsor(collection_id)?498 .ok_or(<Error<T>>::InvalidArgument)?499 == Self::account_id(),500 <Error<T>>::NoPermission501 );502 T::CollectionHandler::remove_collection_sponsor(collection_id)503 }504505 #[pallet::weight(T::WeightInfo::sponsor_contract())]506 pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {507 let admin_id = ensure_signed(admin)?;508509 ensure!(510 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,511 Error::<T>::NoPermission512 );513514 T::ContractHandler::set_sponsor(515 T::CrossAccountId::from_sub(Self::account_id()),516 contract_id,517 )518 }519520 #[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]521 pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {522 let admin_id = ensure_signed(admin)?;523524 ensure!(525 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,526 Error::<T>::NoPermission527 );528529 ensure!(530 T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?531 == T::CrossAccountId::from_sub(Self::account_id()),532 <Error<T>>::NoPermission533 );534 T::ContractHandler::remove_contract_sponsor(contract_id)535 }536537 #[pallet::weight(0)]538 pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {539 let admin_id = ensure_signed(admin)?;540541 ensure!(542 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,543 Error::<T>::NoPermission544 );545546 Ok(())547 }548 }549}550551impl<T: Config> Pallet<T> {552 pub fn account_id() -> T::AccountId {553 T::PalletId::get().into_account_truncating()554 }555556 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {557 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();558 locked_balance -= amount;559 Self::set_lock_unchecked(staker, locked_balance);560 }561562 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {563 Self::get_locked_balance(staker)564 .map_or(<BalanceOf<T>>::default(), |l| l.amount)565 .checked_add(&amount)566 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))567 .ok_or(ArithmeticError::Overflow.into())568 }569570 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {571 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(572 LOCK_IDENTIFIER,573 staker,574 amount,575 WithdrawReasons::all(),576 )577 }578579 pub fn get_locked_balance(580 staker: impl EncodeLike<T::AccountId>,581 ) -> Option<BalanceLock<BalanceOf<T>>> {582 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)583 .into_iter()584 .find(|l| l.id == LOCK_IDENTIFIER)585 }586587 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {588 let staked = Staked::<T>::iter_prefix((staker,))589 .into_iter()590 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {591 acc + amount592 });593 if staked != <BalanceOf<T>>::default() {594 Some(staked)595 } else {596 None597 }598 }599600 pub fn total_staked_by_id_per_block(601 staker: impl EncodeLike<T::AccountId>,602 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {603 let mut staked = Staked::<T>::iter_prefix((staker,))604 .into_iter()605 .map(|(block, (amount, _))| (block, amount))606 .collect::<Vec<_>>();607 staked.sort_by_key(|(block, _)| *block);608 if !staked.is_empty() {609 Some(staked)610 } else {611 None612 }613 }614615 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {616 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {617 Self::total_staked_by_id(s.as_sub())618 })619 // Self::total_staked_by_id(staker.as_sub())620 }621622 pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {623 Self::get_locked_balance(staker.as_sub())624 .map(|l| l.amount)625 .unwrap_or_default()626 }627628 pub fn cross_id_total_staked_per_block(629 staker: T::CrossAccountId,630 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {631 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()632 }633634 fn recalculate_stake(635 staker: &T::AccountId,636 block: T::BlockNumber,637 base: BalanceOf<T>,638 income_acc: &mut BalanceOf<T>,639 ) {640 let income = Self::calculate_income(base);641 // base.checked_add(&income).map(|res| {642 // <Staked<T>>::insert((staker, block), res);643 // *income_acc += income;644 // <T::Currency as Currency<T::AccountId>>::transfer(645 // &T::TreasuryAccountId::get(),646 // staker,647 // income,648 // ExistenceRequirement::KeepAlive,649 // )650 // .and_then(|_| Self::add_lock_balance(staker, income));651 // });652 }653654 fn calculate_income<I>(base: I) -> I655 where656 I: EncodeLike<BalanceOf<T>> + Balance,657 {658 T::IntervalIncome::get() * base659 }660}661662impl<T: Config> Pallet<T>663where664 <<T as pallet::Config>::Currency as Currency<T::AccountId>>::Balance: Sum,665{666 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {667 staker.map_or(PendingUnstake::<T>::iter_values().sum(), |s| {668 PendingUnstake::<T>::iter_prefix_values((s.as_sub(),)).sum()669 })670 }671672 pub fn cross_id_pending_unstake_per_block(673 staker: T::CrossAccountId,674 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {675 let mut unsorted_res = PendingUnstake::<T>::iter_prefix((staker.as_sub(),))676 .into_iter()677 .collect::<Vec<_>>();678 unsorted_res.sort_by_key(|(block, _)| *block);679 unsorted_res680 }681}