difftreelog
code refactor & comments
in: master
12 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -12147,7 +12147,7 @@
[[package]]
name = "uc-rpc"
-version = "0.1.3"
+version = "0.1.4"
dependencies = [
"anyhow",
"app-promotion-rpc",
client/rpc/CHANGELOG.mddiffbeforeafterboth--- a/client/rpc/CHANGELOG.md
+++ b/client/rpc/CHANGELOG.md
@@ -3,15 +3,21 @@
All notable changes to this project will be documented in this file.
<!-- bureaucrate goes here -->
+
+## [v0.1.4] 2022-09-08
+
+### Added
+- Support RPC for `AppPromotion` pallet.
+
## [v0.1.3] 2022-08-16
### Other changes
-- build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
+- build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
-- build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
+- build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
-- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
+- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
## [0.1.2] - 2022-08-12
client/rpc/Cargo.tomldiffbeforeafterboth--- a/client/rpc/Cargo.toml
+++ b/client/rpc/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "uc-rpc"
-version = "0.1.3"
+version = "0.1.4"
license = "GPLv3"
edition = "2021"
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 #[pallet::constant]114 type Nominal: Get<BalanceOf<Self>>;115116 #[pallet::constant]117 type IntervalIncome: Get<Perbill>;118119 /// Weight information for extrinsics in this pallet.120 type WeightInfo: WeightInfo;121122 // The relay block number provider123 type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;124125 /// Events compatible with [`frame_system::Config::Event`].126 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;127 }128129 #[pallet::pallet]130 #[pallet::generate_store(pub(super) trait Store)]131 pub struct Pallet<T>(_);132133 #[pallet::event]134 #[pallet::generate_deposit(fn deposit_event)]135 pub enum Event<T: Config> {136 StakingRecalculation(137 /// An recalculated staker138 T::AccountId,139 /// Base on which interest is calculated140 BalanceOf<T>,141 /// Amount of accrued interest142 BalanceOf<T>,143 ),144 Stake(T::AccountId, BalanceOf<T>),145 Unstake(T::AccountId, BalanceOf<T>),146 SetAdmin(T::AccountId),147 }148149 #[pallet::error]150 pub enum Error<T> {151 /// Error due to action requiring admin to be set152 AdminNotSet,153 /// No permission to perform an action154 NoPermission,155 /// Insufficient funds to perform an action156 NotSufficientFunds,157 PendingForBlockOverflow,158 /// An error related to the fact that an invalid argument was passed to perform an action159 SponsorNotSet,160 }161162 #[pallet::storage]163 pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;164165 #[pallet::storage]166 pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;167168 /// Amount of tokens staked by account in the blocknumber.169 #[pallet::storage]170 pub type Staked<T: Config> = StorageNMap<171 Key = (172 Key<Blake2_128Concat, T::AccountId>,173 Key<Twox64Concat, T::BlockNumber>,174 ),175 Value = (BalanceOf<T>, T::BlockNumber),176 QueryKind = ValueQuery,177 >;178 /// Amount of stakes for an Account179 #[pallet::storage]180 pub type StakesPerAccount<T: Config> =181 StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;182183 #[pallet::storage]184 pub type PendingUnstake<T: Config> = StorageMap<185 _,186 Twox64Concat,187 T::BlockNumber,188 BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,189 ValueQuery,190 >;191192 /// Stores hash a record for which the last revenue recalculation was performed.193 /// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.194 #[pallet::storage]195 #[pallet::getter(fn get_next_calculated_record)]196 pub type NextCalculatedRecord<T: Config> =197 StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;198199 #[pallet::hooks]200 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {201 fn on_initialize(current_block_number: T::BlockNumber) -> Weight202 where203 <T as frame_system::Config>::BlockNumber: From<u32>,204 {205 let block_pending = PendingUnstake::<T>::take(current_block_number);206 let counter = block_pending.len() as u32;207208 if !block_pending.is_empty() {209 block_pending.into_iter().for_each(|(staker, amount)| {210 <T::Currency as ReservableCurrency<T::AccountId>>::unreserve(&staker, amount);211 });212 }213214 T::WeightInfo::on_initialize(counter)215 }216 }217218 #[pallet::call]219 impl<T: Config> Pallet<T>220 where221 T::BlockNumber: From<u32> + Into<u32>,222 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,223 {224 #[pallet::weight(T::WeightInfo::set_admin_address())]225 pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {226 ensure_root(origin)?;227228 <Admin<T>>::set(Some(admin.as_sub().to_owned()));229230 Self::deposit_event(Event::SetAdmin(admin.as_sub().to_owned()));231232 Ok(())233 }234235 #[pallet::weight(T::WeightInfo::stake())]236 pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {237 let staker_id = ensure_signed(staker)?;238239 ensure!(240 StakesPerAccount::<T>::get(&staker_id) < 10,241 Error::<T>::NoPermission242 );243244 ensure!(245 amount >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(),246 ArithmeticError::Underflow247 );248249 let balance =250 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);251252 // ensure!(balance >= amount, ArithmeticError::Underflow);253254 <<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(255 &staker_id,256 amount,257 WithdrawReasons::all(),258 balance259 .checked_sub(&amount)260 .ok_or(ArithmeticError::Underflow)?,261 )?;262263 Self::add_lock_balance(&staker_id, amount)?;264265 let block_number = T::RelayBlockNumberProvider::current_block_number();266267 let recalculate_after_interval: T::BlockNumber =268 if block_number % T::RecalculationInterval::get() == 0u32.into() {269 1u32.into()270 } else {271 2u32.into()272 };273274 let recalc_block = (block_number / T::RecalculationInterval::get()275 + recalculate_after_interval)276 * T::RecalculationInterval::get();277278 <Staked<T>>::insert((&staker_id, block_number), {279 let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));280 balance_and_recalc_block.0 = balance_and_recalc_block281 .0282 .checked_add(&amount)283 .ok_or(ArithmeticError::Overflow)?;284 balance_and_recalc_block.1 = recalc_block;285 balance_and_recalc_block286 });287288 <TotalStaked<T>>::set(289 <TotalStaked<T>>::get()290 .checked_add(&amount)291 .ok_or(ArithmeticError::Overflow)?,292 );293294 StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);295296 Self::deposit_event(Event::Stake(staker_id, amount));297298 Ok(())299 }300301 #[pallet::weight(T::WeightInfo::unstake())]302 pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {303 let staker_id = ensure_signed(staker)?;304 let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();305 let mut pendings = <PendingUnstake<T>>::get(block);306307 ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);308309 let mut total_stakes = 0u64;310311 let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))312 .map(|(_, (amount, _))| {313 total_stakes += 1;314 amount315 })316 .sum();317318 if total_staked.is_zero() {319 return Ok(None.into()); // TO-DO320 }321322 pendings323 .try_push((staker_id.clone(), total_staked))324 .map_err(|_| Error::<T>::PendingForBlockOverflow)?;325326 <PendingUnstake<T>>::insert(block, pendings);327328 Self::unlock_balance_unchecked(&staker_id, total_staked);329330 <T::Currency as ReservableCurrency<T::AccountId>>::reserve(&staker_id, total_staked)?;331332 TotalStaked::<T>::set(333 TotalStaked::<T>::get()334 .checked_sub(&total_staked)335 .ok_or(ArithmeticError::Underflow)?,336 );337338 StakesPerAccount::<T>::remove(&staker_id);339340 Self::deposit_event(Event::Unstake(staker_id, total_staked));341342 Ok(None.into())343 }344345 #[pallet::weight(T::WeightInfo::sponsor_collection())]346 pub fn sponsor_collection(347 admin: OriginFor<T>,348 collection_id: CollectionId,349 ) -> DispatchResult {350 let admin_id = ensure_signed(admin)?;351 ensure!(352 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,353 Error::<T>::NoPermission354 );355356 T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)357 }358 #[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]359 pub fn stop_sponsoring_collection(360 admin: OriginFor<T>,361 collection_id: CollectionId,362 ) -> DispatchResult {363 let admin_id = ensure_signed(admin)?;364365 ensure!(366 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,367 Error::<T>::NoPermission368 );369370 ensure!(371 T::CollectionHandler::sponsor(collection_id)?.ok_or(<Error<T>>::SponsorNotSet)?372 == Self::account_id(),373 <Error<T>>::NoPermission374 );375 T::CollectionHandler::remove_collection_sponsor(collection_id)376 }377378 #[pallet::weight(T::WeightInfo::sponsor_contract())]379 pub fn sponsor_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {380 let admin_id = ensure_signed(admin)?;381382 ensure!(383 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,384 Error::<T>::NoPermission385 );386387 T::ContractHandler::set_sponsor(388 T::CrossAccountId::from_sub(Self::account_id()),389 contract_id,390 )391 }392393 #[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]394 pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {395 let admin_id = ensure_signed(admin)?;396397 ensure!(398 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,399 Error::<T>::NoPermission400 );401402 ensure!(403 T::ContractHandler::sponsor(contract_id)?.ok_or(<Error<T>>::SponsorNotSet)?404 == T::CrossAccountId::from_sub(Self::account_id()),405 <Error<T>>::NoPermission406 );407 T::ContractHandler::remove_contract_sponsor(contract_id)408 }409410 #[pallet::weight(T::WeightInfo::payout_stakers(stakers_number.unwrap_or(20) as u32))]411 pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {412 let admin_id = ensure_signed(admin)?;413414 ensure!(415 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,416 Error::<T>::NoPermission417 );418419 let current_recalc_block =420 Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());421 let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();422423 let mut storage_iterator = Self::get_next_calculated_key()424 .map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));425426 NextCalculatedRecord::<T>::set(None);427428 // {429 // let mut stakers_number = stakers_number.unwrap_or(20);430 // let mut last_id = admin_id;431 // let mut income_acc = BalanceOf::<T>::default();432 // let mut amount_acc = BalanceOf::<T>::default();433434 // while let Some((435 // (current_id, staked_block),436 // (amount, next_recalc_block_for_stake),437 // )) = storage_iterator.next()438 // {439 // if last_id != current_id {440 // if income_acc != BalanceOf::<T>::default() {441 // <T::Currency as Currency<T::AccountId>>::transfer(442 // &T::TreasuryAccountId::get(),443 // &last_id,444 // income_acc,445 // ExistenceRequirement::KeepAlive,446 // )447 // .and_then(|_| Self::add_lock_balance(&last_id, income_acc))?;448449 // Self::deposit_event(Event::StakingRecalculation(450 // last_id, amount, income_acc,451 // ));452 // }453454 // if stakers_number == 0 {455 // NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));456 // break;457 // }458 // stakers_number -= 1;459 // income_acc = BalanceOf::<T>::default();460 // last_id = current_id;461 // };462 // if current_recalc_block >= next_recalc_block_for_stake {463 // Self::recalculate_and_insert_stake(464 // &last_id,465 // staked_block,466 // next_recalc_block,467 // amount,468 // ((current_recalc_block - next_recalc_block_for_stake)469 // / T::RecalculationInterval::get())470 // .into() + 1,471 // &mut income_acc,472 // );473 // }474 // }475 // }476477 // {478 // let mut stakers_number = stakers_number.unwrap_or(20);479 // let last_id = RefCell::new(None);480 // let income_acc = RefCell::new(BalanceOf::<T>::default());481 // let amount_acc = RefCell::new(BalanceOf::<T>::default());482483 // let flush_stake = || -> DispatchResult {484 // if let Some(last_id) = &*last_id.borrow() {485 // if !income_acc.borrow().is_zero() {486 // <T::Currency as Currency<T::AccountId>>::transfer(487 // &T::TreasuryAccountId::get(),488 // last_id,489 // *income_acc.borrow(),490 // ExistenceRequirement::KeepAlive,491 // )492 // .and_then(|_| {493 // Self::add_lock_balance(last_id, *income_acc.borrow());494 // <TotalStaked<T>>::try_mutate(|staked| {495 // staked496 // .checked_add(&*income_acc.borrow())497 // .ok_or(ArithmeticError::Overflow.into())498 // })499 // })?;500501 // Self::deposit_event(Event::StakingRecalculation(502 // last_id.clone(),503 // *amount_acc.borrow(),504 // *income_acc.borrow(),505 // ));506 // }507508 // *income_acc.borrow_mut() = BalanceOf::<T>::default();509 // *amount_acc.borrow_mut() = BalanceOf::<T>::default();510 // }511 // Ok(())512 // };513514 // while let Some((515 // (current_id, staked_block),516 // (amount, next_recalc_block_for_stake),517 // )) = storage_iterator.next()518 // {519 // if stakers_number == 0 {520 // NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));521 // break;522 // }523 // stakers_number -= 1;524 // if last_id.borrow().as_ref() != Some(¤t_id) {525 // flush_stake()?;526 // };527 // *last_id.borrow_mut() = Some(current_id.clone());528 // if current_recalc_block >= next_recalc_block_for_stake {529 // *amount_acc.borrow_mut() += amount;530 // Self::recalculate_and_insert_stake(531 // ¤t_id,532 // staked_block,533 // next_recalc_block,534 // amount,535 // ((current_recalc_block - next_recalc_block_for_stake)536 // / T::RecalculationInterval::get())537 // .into() + 1,538 // &mut *income_acc.borrow_mut(),539 // );540 // }541 // }542 // flush_stake()?;543 // }544545 {546 let mut stakers_number = stakers_number.unwrap_or(20);547 let last_id = RefCell::new(None);548 let income_acc = RefCell::new(BalanceOf::<T>::default());549 let amount_acc = RefCell::new(BalanceOf::<T>::default());550551 let flush_stake = || -> DispatchResult {552 if let Some(last_id) = &*last_id.borrow() {553 if !income_acc.borrow().is_zero() {554 <T::Currency as Currency<T::AccountId>>::transfer(555 &T::TreasuryAccountId::get(),556 last_id,557 *income_acc.borrow(),558 ExistenceRequirement::KeepAlive,559 )560 .and_then(|_| {561 Self::add_lock_balance(last_id, *income_acc.borrow())?;562 <TotalStaked<T>>::try_mutate(|staked| {563 staked564 .checked_add(&*income_acc.borrow())565 .ok_or(ArithmeticError::Overflow.into())566 })567 })?;568569 Self::deposit_event(Event::StakingRecalculation(570 last_id.clone(),571 *amount_acc.borrow(),572 *income_acc.borrow(),573 ));574 }575576 *income_acc.borrow_mut() = BalanceOf::<T>::default();577 *amount_acc.borrow_mut() = BalanceOf::<T>::default();578 }579 Ok(())580 };581582 while let Some((583 (current_id, staked_block),584 (amount, next_recalc_block_for_stake),585 )) = storage_iterator.next()586 {587 if stakers_number == 0 {588 NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));589 break;590 }591 if last_id.borrow().as_ref() != Some(¤t_id) {592 flush_stake()?;593 *last_id.borrow_mut() = Some(current_id.clone());594 stakers_number -= 1;595 };596 if current_recalc_block >= next_recalc_block_for_stake {597 *amount_acc.borrow_mut() += amount;598 Self::recalculate_and_insert_stake(599 ¤t_id,600 staked_block,601 next_recalc_block,602 amount,603 ((current_recalc_block - next_recalc_block_for_stake)604 / T::RecalculationInterval::get())605 .into() + 1,606 &mut *income_acc.borrow_mut(),607 );608 }609 }610 flush_stake()?;611 }612613 Ok(())614 }615 }616}617618impl<T: Config> Pallet<T> {619 pub fn account_id() -> T::AccountId {620 T::PalletId::get().into_account_truncating()621 }622623 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {624 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();625 locked_balance -= amount;626 Self::set_lock_unchecked(staker, locked_balance);627 }628629 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {630 Self::get_locked_balance(staker)631 .map_or(<BalanceOf<T>>::default(), |l| l.amount)632 .checked_add(&amount)633 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))634 .ok_or(ArithmeticError::Overflow.into())635 }636637 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {638 if amount.is_zero() {639 <T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);640 } else {641 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(642 LOCK_IDENTIFIER,643 staker,644 amount,645 WithdrawReasons::all(),646 )647 }648 }649650 pub fn get_locked_balance(651 staker: impl EncodeLike<T::AccountId>,652 ) -> Option<BalanceLock<BalanceOf<T>>> {653 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)654 .into_iter()655 .find(|l| l.id == LOCK_IDENTIFIER)656 }657658 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {659 let staked = Staked::<T>::iter_prefix((staker,))660 .into_iter()661 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {662 acc + amount663 });664 if staked != <BalanceOf<T>>::default() {665 Some(staked)666 } else {667 None668 }669 }670671 pub fn total_staked_by_id_per_block(672 staker: impl EncodeLike<T::AccountId>,673 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {674 let mut staked = Staked::<T>::iter_prefix((staker,))675 .into_iter()676 .map(|(block, (amount, _))| (block, amount))677 .collect::<Vec<_>>();678 staked.sort_by_key(|(block, _)| *block);679 if !staked.is_empty() {680 Some(staked)681 } else {682 None683 }684 }685686 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {687 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {688 Self::total_staked_by_id(s.as_sub())689 })690 }691692 // pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {693 // Self::get_locked_balance(staker.as_sub())694 // .map(|l| l.amount)695 // .unwrap_or_default()696 // }697698 pub fn cross_id_total_staked_per_block(699 staker: T::CrossAccountId,700 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {701 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()702 }703704 fn recalculate_and_insert_stake(705 staker: &T::AccountId,706 staked_block: T::BlockNumber,707 next_recalc_block: T::BlockNumber,708 base: BalanceOf<T>,709 iters: u32,710 income_acc: &mut BalanceOf<T>,711 ) {712 let income = Self::calculate_income(base, iters);713714 base.checked_add(&income).map(|res| {715 <Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));716 *income_acc += income;717 });718 }719720 fn calculate_income<I>(base: I, iters: u32) -> I721 where722 I: EncodeLike<BalanceOf<T>> + Balance,723 {724 let mut income = base;725726 (0..iters).for_each(|_| income += T::IntervalIncome::get() * income);727728 income - base729 }730731 fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {732 (current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()733 }734735 // fn get_next_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {736 // Self::get_current_recalc_block(current_relay_block) + T::RecalculationInterval::get()737 // }738739 fn get_next_calculated_key() -> Option<Vec<u8>> {740 Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))741 }742}743744impl<T: Config> Pallet<T>745where746 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,747{748 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {749 staker.map_or(750 PendingUnstake::<T>::iter_values()751 .flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))752 .sum(),753 |s| {754 PendingUnstake::<T>::iter_values()755 .flatten()756 .filter_map(|(id, amount)| {757 if id == *s.as_sub() {758 Some(amount)759 } else {760 None761 }762 })763 .sum()764 },765 )766 }767768 pub fn cross_id_pending_unstake_per_block(769 staker: T::CrossAccountId,770 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {771 let mut unsorted_res = vec![];772 PendingUnstake::<T>::iter().for_each(|(block, pendings)| {773 pendings.into_iter().for_each(|(id, amount)| {774 if id == *staker.as_sub() {775 unsorted_res.push((block, amount));776 };777 })778 });779780 unsorted_res.sort_by_key(|(block, _)| *block);781 unsorted_res782 }783}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//!2526// #![recursion_limit = "1024"]27#![cfg_attr(not(feature = "std"), no_std)]2829#[cfg(feature = "runtime-benchmarks")]30mod benchmarking;31#[cfg(test)]32mod tests;33pub mod types;34pub mod weights;3536use sp_std::{37 vec::{Vec},38 vec,39 iter::Sum,40 borrow::ToOwned,41 cell::RefCell,42};43use sp_core::H160;44use codec::EncodeLike;45use pallet_balances::BalanceLock;46pub use types::*;4748use up_data_structs::CollectionId;4950use frame_support::{51 dispatch::{DispatchResult},52 traits::{53 Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,54 },55 ensure,56};5758use weights::WeightInfo;5960pub use pallet::*;61use pallet_evm::account::CrossAccountId;62use sp_runtime::{63 Perbill,64 traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion, Zero},65 ArithmeticError,66};6768pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";6970const PENDING_LIMIT_PER_BLOCK: u32 = 3;7172type BalanceOf<T> =73 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;7475#[frame_support::pallet]76pub mod pallet {77 use super::*;78 use frame_support::{79 Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId,80 traits::ReservableCurrency,81 };82 use frame_system::pallet_prelude::*;8384 #[pallet::config]85 pub trait Config: frame_system::Config + pallet_evm::account::Config {86 /// Type to interact with the native token87 type Currency: ExtendedLockableCurrency<Self::AccountId>88 + ReservableCurrency<Self::AccountId>;8990 /// Type for interacting with collections91 type CollectionHandler: CollectionHandler<92 AccountId = Self::AccountId,93 CollectionId = CollectionId,94 >;9596 /// Type for interacting with conrtacts97 type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;9899 /// ID for treasury100 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>;109110 /// In parachain blocks.111 #[pallet::constant]112 type PendingInterval: Get<Self::BlockNumber>;113114 /// Rate of return for interval in blocks defined in `RecalculationInterval`.115 #[pallet::constant]116 type IntervalIncome: Get<Perbill>;117118 /// Decimals for the `Currency`.119 #[pallet::constant]120 type Nominal: Get<BalanceOf<Self>>;121122 /// Weight information for extrinsics in this pallet.123 type WeightInfo: WeightInfo;124125 // The relay block number provider126 type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;127128 /// Events compatible with [`frame_system::Config::Event`].129 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;130 }131132 #[pallet::pallet]133 #[pallet::generate_store(pub(super) trait Store)]134 pub struct Pallet<T>(_);135136 #[pallet::event]137 #[pallet::generate_deposit(fn deposit_event)]138 pub enum Event<T: Config> {139 /// Staking recalculation was performed140 ///141 /// # Arguments142 /// * AccountId: ID of the staker.143 /// * Balance : recalculation base144 /// * Balance : total income145 StakingRecalculation(146 /// An recalculated staker147 T::AccountId,148 /// Base on which interest is calculated149 BalanceOf<T>,150 /// Amount of accrued interest151 BalanceOf<T>,152 ),153 154 /// Staking was performed155 ///156 /// # Arguments157 /// * AccountId: ID of the staker158 /// * Balance : staking amount159 Stake(T::AccountId, BalanceOf<T>),160 161 /// Unstaking was performed162 ///163 /// # Arguments164 /// * AccountId: ID of the staker165 /// * Balance : unstaking amount166 Unstake(T::AccountId, BalanceOf<T>),167 168 /// The admin was set169 ///170 /// # Arguments171 /// * AccountId: ID of the admin172 SetAdmin(T::AccountId),173 }174175 #[pallet::error]176 pub enum Error<T> {177 /// Error due to action requiring admin to be set.178 AdminNotSet,179 /// No permission to perform an action.180 NoPermission,181 /// Insufficient funds to perform an action.182 NotSufficientFunds,183 /// Occurs when a pending unstake cannot be added in this block. PENDING_LIMIT_PER_BLOCK` limits exceeded.184 PendingForBlockOverflow,185 /// The error is due to the fact that the collection/contract must already be sponsored in order to perform the action.186 SponsorNotSet,187 /// Errors caused by incorrect actions with a locked balance.188 IncorrectLockedBalanceOperation,189 }190191 #[pallet::storage]192 pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;193194 #[pallet::storage]195 pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;196197 /// Amount of tokens staked by account in the blocknumber.198 #[pallet::storage]199 pub type Staked<T: Config> = StorageNMap<200 Key = (201 Key<Blake2_128Concat, T::AccountId>,202 Key<Twox64Concat, T::BlockNumber>,203 ),204 Value = (BalanceOf<T>, T::BlockNumber),205 QueryKind = ValueQuery,206 >;207 /// Amount of stakes for an Account208 #[pallet::storage]209 pub type StakesPerAccount<T: Config> =210 StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;211212 #[pallet::storage]213 pub type PendingUnstake<T: Config> = StorageMap<214 _,215 Twox64Concat,216 T::BlockNumber,217 BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,218 ValueQuery,219 >;220221 /// Stores a key for record for which the next revenue recalculation would be performed.222 /// If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.223 #[pallet::storage]224 #[pallet::getter(fn get_next_calculated_record)]225 pub type NextCalculatedRecord<T: Config> =226 StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;227228 #[pallet::hooks]229 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {230 /// Block overflow is impossible due to the fact that the unstake algorithm in on_initialize231 /// implies the execution of a strictly limited number of relatively lightweight operations.232 /// A separate benchmark has been implemented to scale the weight depending on the number of pendings.233 fn on_initialize(current_block_number: T::BlockNumber) -> Weight234 where235 <T as frame_system::Config>::BlockNumber: From<u32>,236 {237 let block_pending = PendingUnstake::<T>::take(current_block_number);238 let counter = block_pending.len() as u32;239240 if !block_pending.is_empty() {241 block_pending.into_iter().for_each(|(staker, amount)| {242 <T::Currency as ReservableCurrency<T::AccountId>>::unreserve(&staker, amount);243 });244 }245246 T::WeightInfo::on_initialize(counter)247 }248 }249250 #[pallet::call]251 impl<T: Config> Pallet<T>252 where253 T::BlockNumber: From<u32> + Into<u32>,254 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,255 {256 #[pallet::weight(T::WeightInfo::set_admin_address())]257 pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {258 ensure_root(origin)?;259260 <Admin<T>>::set(Some(admin.as_sub().to_owned()));261262 Self::deposit_event(Event::SetAdmin(admin.as_sub().to_owned()));263264 Ok(())265 }266267 #[pallet::weight(T::WeightInfo::stake())]268 pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {269 let staker_id = ensure_signed(staker)?;270271 ensure!(272 StakesPerAccount::<T>::get(&staker_id) < 10,273 Error::<T>::NoPermission274 );275276 ensure!(277 amount >= <BalanceOf<T>>::from(100u128) * T::Nominal::get(),278 ArithmeticError::Underflow279 );280281 let balance =282 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);283284 <<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(285 &staker_id,286 amount,287 WithdrawReasons::all(),288 balance289 .checked_sub(&amount)290 .ok_or(ArithmeticError::Underflow)?,291 )?;292293 Self::add_lock_balance(&staker_id, amount)?;294295 let block_number = T::RelayBlockNumberProvider::current_block_number();296297 let recalculate_after_interval: T::BlockNumber =298 if block_number % T::RecalculationInterval::get() == 0u32.into() {299 1u32.into()300 } else {301 2u32.into()302 };303304 let recalc_block = (block_number / T::RecalculationInterval::get()305 + recalculate_after_interval)306 * T::RecalculationInterval::get();307308 <Staked<T>>::insert((&staker_id, block_number), {309 let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));310 balance_and_recalc_block.0 = balance_and_recalc_block311 .0312 .checked_add(&amount)313 .ok_or(ArithmeticError::Overflow)?;314 balance_and_recalc_block.1 = recalc_block;315 balance_and_recalc_block316 });317318 <TotalStaked<T>>::set(319 <TotalStaked<T>>::get()320 .checked_add(&amount)321 .ok_or(ArithmeticError::Overflow)?,322 );323324 StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);325326 Self::deposit_event(Event::Stake(staker_id, amount));327328 Ok(())329 }330331 #[pallet::weight(T::WeightInfo::unstake())]332 pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {333 let staker_id = ensure_signed(staker)?;334 let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();335 let mut pendings = <PendingUnstake<T>>::get(block);336337 ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);338339 let mut total_stakes = 0u64;340341 let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))342 .map(|(_, (amount, _))| {343 total_stakes += 1;344 amount345 })346 .sum();347348 if total_staked.is_zero() {349 return Ok(None.into()); // TO-DO350 }351352 pendings353 .try_push((staker_id.clone(), total_staked))354 .map_err(|_| Error::<T>::PendingForBlockOverflow)?;355356 <PendingUnstake<T>>::insert(block, pendings);357358 Self::unlock_balance(&staker_id, total_staked)?;359360 <T::Currency as ReservableCurrency<T::AccountId>>::reserve(&staker_id, total_staked)?;361362 TotalStaked::<T>::set(363 TotalStaked::<T>::get()364 .checked_sub(&total_staked)365 .ok_or(ArithmeticError::Underflow)?,366 );367368 StakesPerAccount::<T>::remove(&staker_id);369370 Self::deposit_event(Event::Unstake(staker_id, total_staked));371372 Ok(None.into())373 }374375 #[pallet::weight(T::WeightInfo::sponsor_collection())]376 pub fn sponsor_collection(377 admin: OriginFor<T>,378 collection_id: CollectionId,379 ) -> DispatchResult {380 let admin_id = ensure_signed(admin)?;381 ensure!(382 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,383 Error::<T>::NoPermission384 );385386 T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)387 }388 #[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]389 pub fn stop_sponsoring_collection(390 admin: OriginFor<T>,391 collection_id: CollectionId,392 ) -> DispatchResult {393 let admin_id = ensure_signed(admin)?;394395 ensure!(396 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,397 Error::<T>::NoPermission398 );399400 ensure!(401 T::CollectionHandler::sponsor(collection_id)?.ok_or(<Error<T>>::SponsorNotSet)?402 == Self::account_id(),403 <Error<T>>::NoPermission404 );405 T::CollectionHandler::remove_collection_sponsor(collection_id)406 }407408 #[pallet::weight(T::WeightInfo::sponsor_contract())]409 pub fn sponsor_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {410 let admin_id = ensure_signed(admin)?;411412 ensure!(413 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,414 Error::<T>::NoPermission415 );416417 T::ContractHandler::set_sponsor(418 T::CrossAccountId::from_sub(Self::account_id()),419 contract_id,420 )421 }422423 #[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]424 pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> 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::ContractHandler::sponsor(contract_id)?434 .ok_or(<Error<T>>::SponsorNotSet)?435 .as_sub() == &Self::account_id(),436 <Error<T>>::NoPermission437 );438 T::ContractHandler::remove_contract_sponsor(contract_id)439 }440441 #[pallet::weight(T::WeightInfo::payout_stakers(stakers_number.unwrap_or(20) as u32))]442 pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> 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 let current_recalc_block =451 Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());452 let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();453454 let mut storage_iterator = Self::get_next_calculated_key()455 .map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));456457 NextCalculatedRecord::<T>::set(None);458459 // {460 // let mut stakers_number = stakers_number.unwrap_or(20);461 // let mut last_id = admin_id;462 // let mut income_acc = BalanceOf::<T>::default();463 // let mut amount_acc = BalanceOf::<T>::default();464465 // while let Some((466 // (current_id, staked_block),467 // (amount, next_recalc_block_for_stake),468 // )) = storage_iterator.next()469 // {470 // if last_id != current_id {471 // if income_acc != BalanceOf::<T>::default() {472 // <T::Currency as Currency<T::AccountId>>::transfer(473 // &T::TreasuryAccountId::get(),474 // &last_id,475 // income_acc,476 // ExistenceRequirement::KeepAlive,477 // )478 // .and_then(|_| Self::add_lock_balance(&last_id, income_acc))?;479480 // Self::deposit_event(Event::StakingRecalculation(481 // last_id, amount, income_acc,482 // ));483 // }484485 // if stakers_number == 0 {486 // NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));487 // break;488 // }489 // stakers_number -= 1;490 // income_acc = BalanceOf::<T>::default();491 // last_id = current_id;492 // };493 // if current_recalc_block >= next_recalc_block_for_stake {494 // Self::recalculate_and_insert_stake(495 // &last_id,496 // staked_block,497 // next_recalc_block,498 // amount,499 // ((current_recalc_block - next_recalc_block_for_stake)500 // / T::RecalculationInterval::get())501 // .into() + 1,502 // &mut income_acc,503 // );504 // }505 // }506 // }507508 {509 let mut stakers_number = stakers_number.unwrap_or(20);510 let last_id = RefCell::new(None);511 let income_acc = RefCell::new(BalanceOf::<T>::default());512 let amount_acc = RefCell::new(BalanceOf::<T>::default());513514 let flush_stake = || -> DispatchResult {515 if let Some(last_id) = &*last_id.borrow() {516 if !income_acc.borrow().is_zero() {517 <T::Currency as Currency<T::AccountId>>::transfer(518 &T::TreasuryAccountId::get(),519 last_id,520 *income_acc.borrow(),521 ExistenceRequirement::KeepAlive,522 )523 .and_then(|_| {524 Self::add_lock_balance(last_id, *income_acc.borrow())?;525 <TotalStaked<T>>::try_mutate(|staked| {526 staked527 .checked_add(&*income_acc.borrow())528 .ok_or(ArithmeticError::Overflow.into())529 })530 })?;531532 Self::deposit_event(Event::StakingRecalculation(533 last_id.clone(),534 *amount_acc.borrow(),535 *income_acc.borrow(),536 ));537 }538539 *income_acc.borrow_mut() = BalanceOf::<T>::default();540 *amount_acc.borrow_mut() = BalanceOf::<T>::default();541 }542 Ok(())543 };544545 while let Some((546 (current_id, staked_block),547 (amount, next_recalc_block_for_stake),548 )) = storage_iterator.next()549 {550 if stakers_number == 0 {551 NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));552 break;553 }554 if last_id.borrow().as_ref() != Some(¤t_id) {555 flush_stake()?;556 *last_id.borrow_mut() = Some(current_id.clone());557 stakers_number -= 1;558 };559 if current_recalc_block >= next_recalc_block_for_stake {560 *amount_acc.borrow_mut() += amount;561 Self::recalculate_and_insert_stake(562 ¤t_id,563 staked_block,564 next_recalc_block,565 amount,566 ((current_recalc_block - next_recalc_block_for_stake)567 / T::RecalculationInterval::get())568 .into() + 1,569 &mut *income_acc.borrow_mut(),570 );571 }572 }573 flush_stake()?;574 }575576 Ok(())577 }578 }579}580581impl<T: Config> Pallet<T> {582 pub fn account_id() -> T::AccountId {583 T::PalletId::get().into_account_truncating()584 }585586 fn unlock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {587 let locked_balance = Self::get_locked_balance(staker)588 .map(|l| l.amount)589 .ok_or(<Error<T>>::IncorrectLockedBalanceOperation)?;590591 // It is understood that we cannot unlock more funds than were locked by staking.592 // Therefore, if implemented correctly, this error should not occur.593 Self::set_lock_unchecked(594 staker,595 locked_balance596 .checked_sub(&amount)597 .ok_or(ArithmeticError::Underflow)?,598 );599 Ok(())600 }601602 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {603 Self::get_locked_balance(staker)604 .map_or(<BalanceOf<T>>::default(), |l| l.amount)605 .checked_add(&amount)606 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))607 .ok_or(ArithmeticError::Overflow.into())608 }609610 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {611 if amount.is_zero() {612 <T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);613 } else {614 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(615 LOCK_IDENTIFIER,616 staker,617 amount,618 WithdrawReasons::all(),619 )620 }621 }622623 pub fn get_locked_balance(624 staker: impl EncodeLike<T::AccountId>,625 ) -> Option<BalanceLock<BalanceOf<T>>> {626 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)627 .into_iter()628 .find(|l| l.id == LOCK_IDENTIFIER)629 }630631 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {632 let staked = Staked::<T>::iter_prefix((staker,))633 .into_iter()634 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {635 acc + amount636 });637 if staked != <BalanceOf<T>>::default() {638 Some(staked)639 } else {640 None641 }642 }643644 pub fn total_staked_by_id_per_block(645 staker: impl EncodeLike<T::AccountId>,646 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {647 let mut staked = Staked::<T>::iter_prefix((staker,))648 .into_iter()649 .map(|(block, (amount, _))| (block, amount))650 .collect::<Vec<_>>();651 staked.sort_by_key(|(block, _)| *block);652 if !staked.is_empty() {653 Some(staked)654 } else {655 None656 }657 }658659 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {660 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {661 Self::total_staked_by_id(s.as_sub())662 })663 }664665 // pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {666 // Self::get_locked_balance(staker.as_sub())667 // .map(|l| l.amount)668 // .unwrap_or_default()669 // }670671 pub fn cross_id_total_staked_per_block(672 staker: T::CrossAccountId,673 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {674 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()675 }676677 fn recalculate_and_insert_stake(678 staker: &T::AccountId,679 staked_block: T::BlockNumber,680 next_recalc_block: T::BlockNumber,681 base: BalanceOf<T>,682 iters: u32,683 income_acc: &mut BalanceOf<T>,684 ) {685 let income = Self::calculate_income(base, iters);686687 base.checked_add(&income).map(|res| {688 <Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));689 *income_acc += income;690 });691 }692693 fn calculate_income<I>(base: I, iters: u32) -> I694 where695 I: EncodeLike<BalanceOf<T>> + Balance,696 {697 let mut income = base;698699 (0..iters).for_each(|_| income += T::IntervalIncome::get() * income);700701 income - base702 }703704 fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {705 (current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()706 }707708 // fn get_next_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {709 // Self::get_current_recalc_block(current_relay_block) + T::RecalculationInterval::get()710 // }711712 fn get_next_calculated_key() -> Option<Vec<u8>> {713 Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))714 }715}716717impl<T: Config> Pallet<T>718where719 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,720{721 /// Since user funds are not transferred anywhere by staking, overflow protection is provided722 /// at the level of the associated type `Balance` of `Currency` trait. In order to overflow,723 /// the staker must have more funds on his account than the maximum set for `Balance` type.724 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {725 staker.map_or(726 PendingUnstake::<T>::iter_values()727 .flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))728 .sum(),729 |s| {730 PendingUnstake::<T>::iter_values()731 .flatten()732 .filter_map(|(id, amount)| {733 if id == *s.as_sub() {734 Some(amount)735 } else {736 None737 }738 })739 .sum()740 },741 )742 }743744 pub fn cross_id_pending_unstake_per_block(745 staker: T::CrossAccountId,746 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {747 let mut unsorted_res = vec![];748 PendingUnstake::<T>::iter().for_each(|(block, pendings)| {749 pendings.into_iter().for_each(|(id, amount)| {750 if id == *staker.as_sub() {751 unsorted_res.push((block, amount));752 };753 })754 });755756 unsorted_res.sort_by_key(|(block, _)| *block);757 unsorted_res758 }759}pallets/unique/CHANGELOG.mddiffbeforeafterboth--- a/pallets/unique/CHANGELOG.md
+++ b/pallets/unique/CHANGELOG.md
@@ -8,6 +8,8 @@
### Added
+- Methods `force_set_sponsor` , `force_remove_collection_sponsor` to be able to administer sponsorships with other pallets. Added to implement `AppPromotion` pallet logic.
+
## [v0.1.3] 2022-08-16
### Other changes
tests/src/interfaces/augment-api-consts.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-consts.ts
+++ b/tests/src/interfaces/augment-api-consts.ts
@@ -16,14 +16,20 @@
declare module '@polkadot/api-base/types/consts' {
interface AugmentedConsts<ApiType extends ApiTypes> {
appPromotion: {
+ /**
+ * Rate of return for interval in blocks defined in `RecalculationInterval`.
+ **/
intervalIncome: Perbill & AugmentedConst<ApiType>;
+ /**
+ * Decimals for the `Currency`.
+ **/
nominal: u128 & AugmentedConst<ApiType>;
/**
* The app's pallet id, used for deriving its sovereign account ID.
**/
palletId: FrameSupportPalletId & AugmentedConst<ApiType>;
/**
- * In relay blocks.
+ * In parachain blocks.
**/
pendingInterval: u32 & AugmentedConst<ApiType>;
/**
tests/src/interfaces/augment-api-errors.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-errors.ts
+++ b/tests/src/interfaces/augment-api-errors.ts
@@ -13,23 +13,30 @@
interface AugmentedErrors<ApiType extends ApiTypes> {
appPromotion: {
/**
- * Error due to action requiring admin to be set
+ * Error due to action requiring admin to be set.
**/
AdminNotSet: AugmentedError<ApiType>;
/**
- * An error related to the fact that an invalid argument was passed to perform an action
+ * Errors caused by incorrect actions with a locked balance.
**/
- InvalidArgument: AugmentedError<ApiType>;
+ IncorrectLockedBalanceOperation: AugmentedError<ApiType>;
/**
- * No permission to perform an action
+ * No permission to perform an action.
**/
NoPermission: AugmentedError<ApiType>;
/**
- * Insufficient funds to perform an action
+ * Insufficient funds to perform an action.
**/
NotSufficientFunds: AugmentedError<ApiType>;
+ /**
+ * Occurs when a pending unstake cannot be added in this block. PENDING_LIMIT_PER_BLOCK` limits exceeded.
+ **/
PendingForBlockOverflow: AugmentedError<ApiType>;
/**
+ * The error is due to the fact that the collection/contract must already be sponsored in order to perform the action.
+ **/
+ SponsorNotSet: AugmentedError<ApiType>;
+ /**
* Generic error
**/
[key: string]: AugmentedError<ApiType>;
tests/src/interfaces/augment-api-events.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-events.ts
+++ b/tests/src/interfaces/augment-api-events.ts
@@ -16,9 +16,37 @@
declare module '@polkadot/api-base/types/events' {
interface AugmentedEvents<ApiType extends ApiTypes> {
appPromotion: {
+ /**
+ * The admin was set
+ *
+ * # Arguments
+ * * AccountId: ID of the admin
+ **/
SetAdmin: AugmentedEvent<ApiType, [AccountId32]>;
+ /**
+ * Staking was performed
+ *
+ * # Arguments
+ * * AccountId: ID of the staker
+ * * Balance : staking amount
+ **/
Stake: AugmentedEvent<ApiType, [AccountId32, u128]>;
+ /**
+ * Staking recalculation was performed
+ *
+ * # Arguments
+ * * AccountId: ID of the staker.
+ * * Balance : recalculation base
+ * * Balance : total income
+ **/
StakingRecalculation: AugmentedEvent<ApiType, [AccountId32, u128, u128]>;
+ /**
+ * Unstaking was performed
+ *
+ * # Arguments
+ * * AccountId: ID of the staker
+ * * Balance : unstaking amount
+ **/
Unstake: AugmentedEvent<ApiType, [AccountId32, u128]>;
/**
* Generic event
tests/src/interfaces/augment-api-query.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-query.ts
+++ b/tests/src/interfaces/augment-api-query.ts
@@ -20,13 +20,10 @@
appPromotion: {
admin: AugmentedQuery<ApiType, () => Observable<Option<AccountId32>>, []> & QueryableStorageEntry<ApiType, []>;
/**
- * Stores hash a record for which the last revenue recalculation was performed.
+ * Stores a key for record for which the next revenue recalculation would be performed.
* If `None`, then recalculation has not yet been performed or calculations have been completed for all stakers.
**/
nextCalculatedRecord: AugmentedQuery<ApiType, () => Observable<Option<ITuple<[AccountId32, u32]>>>, []> & QueryableStorageEntry<ApiType, []>;
- /**
- * Amount of tokens pending unstake per user per block.
- **/
pendingUnstake: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<Vec<ITuple<[AccountId32, u128]>>>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
/**
* Amount of tokens staked by account in the blocknumber.
tests/src/interfaces/default/types.tsdiffbeforeafterboth--- a/tests/src/interfaces/default/types.ts
+++ b/tests/src/interfaces/default/types.ts
@@ -846,8 +846,9 @@
readonly isNoPermission: boolean;
readonly isNotSufficientFunds: boolean;
readonly isPendingForBlockOverflow: boolean;
- readonly isInvalidArgument: boolean;
- readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFunds' | 'PendingForBlockOverflow' | 'InvalidArgument';
+ readonly isSponsorNotSet: boolean;
+ readonly isIncorrectLockedBalanceOperation: boolean;
+ readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFunds' | 'PendingForBlockOverflow' | 'SponsorNotSet' | 'IncorrectLockedBalanceOperation';
}
/** @name PalletAppPromotionEvent */
tests/src/interfaces/lookup.tsdiffbeforeafterboth--- a/tests/src/interfaces/lookup.ts
+++ b/tests/src/interfaces/lookup.ts
@@ -3115,7 +3115,7 @@
* Lookup416: pallet_app_promotion::pallet::Error<T>
**/
PalletAppPromotionError: {
- _enum: ['AdminNotSet', 'NoPermission', 'NotSufficientFunds', 'PendingForBlockOverflow', 'InvalidArgument']
+ _enum: ['AdminNotSet', 'NoPermission', 'NotSufficientFunds', 'PendingForBlockOverflow', 'SponsorNotSet', 'IncorrectLockedBalanceOperation']
},
/**
* Lookup419: pallet_evm::pallet::Error<T>
tests/src/interfaces/types-lookup.tsdiffbeforeafterboth--- a/tests/src/interfaces/types-lookup.ts
+++ b/tests/src/interfaces/types-lookup.ts
@@ -3309,8 +3309,9 @@
readonly isNoPermission: boolean;
readonly isNotSufficientFunds: boolean;
readonly isPendingForBlockOverflow: boolean;
- readonly isInvalidArgument: boolean;
- readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFunds' | 'PendingForBlockOverflow' | 'InvalidArgument';
+ readonly isSponsorNotSet: boolean;
+ readonly isIncorrectLockedBalanceOperation: boolean;
+ readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFunds' | 'PendingForBlockOverflow' | 'SponsorNotSet' | 'IncorrectLockedBalanceOperation';
}
/** @name PalletEvmError (419) */