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