123456789101112131415161718192021222324252627#![cfg_attr(not(feature = "std"), no_std)]2829#[cfg(feature = "runtime-benchmarks")]30mod benchmarking;3132pub mod types;33pub mod weights;3435use sp_std::{36 vec::{Vec},37 vec,38 iter::Sum,39 borrow::ToOwned,40 cell::RefCell,41};42use sp_core::H160;43use codec::EncodeLike;44use pallet_balances::BalanceLock;45pub use types::*;4647use up_data_structs::CollectionId;4849use frame_support::{50 dispatch::{DispatchResult},51 traits::{52 Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,53 },54 ensure,55};5657use weights::WeightInfo;5859pub use pallet::*;60use pallet_evm::account::CrossAccountId;61use sp_runtime::{62 Perbill,63 traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion, Zero},64 ArithmeticError,65};6667pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";6869const PENDING_LIMIT_PER_BLOCK: u32 = 3;7071type BalanceOf<T> =72 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;7374#[frame_support::pallet]75pub mod pallet {76 use super::*;77 use frame_support::{78 Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId,79 traits::ReservableCurrency,80 };81 use frame_system::pallet_prelude::*;8283 #[pallet::config]84 pub trait Config: frame_system::Config + pallet_evm::account::Config {85 86 type Currency: ExtendedLockableCurrency<Self::AccountId>87 + ReservableCurrency<Self::AccountId>;8889 90 type CollectionHandler: CollectionHandler<91 AccountId = Self::AccountId,92 CollectionId = CollectionId,93 >;9495 96 type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;9798 99 type TreasuryAccountId: Get<Self::AccountId>;100101 102 #[pallet::constant]103 type PalletId: Get<PalletId>;104105 106 #[pallet::constant]107 type RecalculationInterval: Get<Self::BlockNumber>;108109 110 #[pallet::constant]111 type PendingInterval: Get<Self::BlockNumber>;112113 114 #[pallet::constant]115 type IntervalIncome: Get<Perbill>;116117 118 #[pallet::constant]119 type Nominal: Get<BalanceOf<Self>>;120121 122 type WeightInfo: WeightInfo;123124 125 type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;126127 128 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;129 }130131 #[pallet::pallet]132 #[pallet::generate_store(pub(super) trait Store)]133 pub struct Pallet<T>(_);134135 #[pallet::event]136 #[pallet::generate_deposit(fn deposit_event)]137 pub enum Event<T: Config> {138 139 140 141 142 143 144 StakingRecalculation(145 146 T::AccountId,147 148 BalanceOf<T>,149 150 BalanceOf<T>,151 ),152153 154 155 156 157 158 Stake(T::AccountId, BalanceOf<T>),159160 161 162 163 164 165 Unstake(T::AccountId, BalanceOf<T>),166167 168 169 170 171 SetAdmin(T::AccountId),172 }173174 #[pallet::error]175 pub enum Error<T> {176 177 AdminNotSet,178 179 NoPermission,180 181 NotSufficientFunds,182 183 PendingForBlockOverflow,184 185 SponsorNotSet,186 187 IncorrectLockedBalanceOperation,188 }189190 #[pallet::storage]191 pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;192193 #[pallet::storage]194 pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;195196 197 #[pallet::storage]198 pub type Staked<T: Config> = StorageNMap<199 Key = (200 Key<Blake2_128Concat, T::AccountId>,201 Key<Twox64Concat, T::BlockNumber>,202 ),203 Value = (BalanceOf<T>, T::BlockNumber),204 QueryKind = ValueQuery,205 >;206 207 #[pallet::storage]208 pub type StakesPerAccount<T: Config> =209 StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;210211 #[pallet::storage]212 pub type PendingUnstake<T: Config> = StorageMap<213 _,214 Twox64Concat,215 T::BlockNumber,216 BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,217 ValueQuery,218 >;219220 221 222 #[pallet::storage]223 #[pallet::getter(fn get_next_calculated_record)]224 pub type NextCalculatedRecord<T: Config> =225 StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;226227 #[pallet::hooks]228 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {229 230 231 232 fn on_initialize(current_block_number: T::BlockNumber) -> Weight233 where234 <T as frame_system::Config>::BlockNumber: From<u32>,235 {236 let block_pending = PendingUnstake::<T>::take(current_block_number);237 let counter = block_pending.len() as u32;238239 if !block_pending.is_empty() {240 block_pending.into_iter().for_each(|(staker, amount)| {241 <T::Currency as ReservableCurrency<T::AccountId>>::unreserve(&staker, amount);242 });243 }244245 T::WeightInfo::on_initialize(counter)246 }247 }248249 #[pallet::call]250 impl<T: Config> Pallet<T>251 where252 T::BlockNumber: From<u32> + Into<u32>,253 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,254 {255 #[pallet::weight(T::WeightInfo::set_admin_address())]256 pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {257 ensure_root(origin)?;258259 <Admin<T>>::set(Some(admin.as_sub().to_owned()));260261 Self::deposit_event(Event::SetAdmin(admin.as_sub().to_owned()));262263 Ok(())264 }265266 #[pallet::weight(T::WeightInfo::stake())]267 pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {268 let staker_id = ensure_signed(staker)?;269270 ensure!(271 StakesPerAccount::<T>::get(&staker_id) < 10,272 Error::<T>::NoPermission273 );274275 ensure!(276 amount >= <BalanceOf<T>>::from(100u128) * T::Nominal::get(),277 ArithmeticError::Underflow278 );279280 let balance =281 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);282283 <<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(284 &staker_id,285 amount,286 WithdrawReasons::all(),287 balance288 .checked_sub(&amount)289 .ok_or(ArithmeticError::Underflow)?,290 )?;291292 Self::add_lock_balance(&staker_id, amount)?;293294 let block_number = T::RelayBlockNumberProvider::current_block_number();295296 let recalculate_after_interval: T::BlockNumber =297 if block_number % T::RecalculationInterval::get() == 0u32.into() {298 1u32.into()299 } else {300 2u32.into()301 };302303 let recalc_block = (block_number / T::RecalculationInterval::get()304 + recalculate_after_interval)305 * T::RecalculationInterval::get();306307 <Staked<T>>::insert((&staker_id, block_number), {308 let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));309 balance_and_recalc_block.0 = balance_and_recalc_block310 .0311 .checked_add(&amount)312 .ok_or(ArithmeticError::Overflow)?;313 balance_and_recalc_block.1 = recalc_block;314 balance_and_recalc_block315 });316317 <TotalStaked<T>>::set(318 <TotalStaked<T>>::get()319 .checked_add(&amount)320 .ok_or(ArithmeticError::Overflow)?,321 );322323 StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);324325 Self::deposit_event(Event::Stake(staker_id, amount));326327 Ok(())328 }329330 #[pallet::weight(T::WeightInfo::unstake())]331 pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {332 let staker_id = ensure_signed(staker)?;333 let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();334 let mut pendings = <PendingUnstake<T>>::get(block);335336 ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);337338 let mut total_stakes = 0u64;339340 let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))341 .map(|(_, (amount, _))| {342 total_stakes += 1;343 amount344 })345 .sum();346347 if total_staked.is_zero() {348 return Ok(None.into()); 349 }350351 pendings352 .try_push((staker_id.clone(), total_staked))353 .map_err(|_| Error::<T>::PendingForBlockOverflow)?;354355 <PendingUnstake<T>>::insert(block, pendings);356357 Self::unlock_balance(&staker_id, total_staked)?;358359 <T::Currency as ReservableCurrency<T::AccountId>>::reserve(&staker_id, total_staked)?;360361 TotalStaked::<T>::set(362 TotalStaked::<T>::get()363 .checked_sub(&total_staked)364 .ok_or(ArithmeticError::Underflow)?,365 );366367 StakesPerAccount::<T>::remove(&staker_id);368369 Self::deposit_event(Event::Unstake(staker_id, total_staked));370371 Ok(None.into())372 }373374 #[pallet::weight(T::WeightInfo::sponsor_collection())]375 pub fn sponsor_collection(376 admin: OriginFor<T>,377 collection_id: CollectionId,378 ) -> DispatchResult {379 let admin_id = ensure_signed(admin)?;380 ensure!(381 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,382 Error::<T>::NoPermission383 );384385 T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)386 }387 #[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]388 pub fn stop_sponsoring_collection(389 admin: OriginFor<T>,390 collection_id: CollectionId,391 ) -> DispatchResult {392 let admin_id = ensure_signed(admin)?;393394 ensure!(395 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,396 Error::<T>::NoPermission397 );398399 ensure!(400 T::CollectionHandler::sponsor(collection_id)?.ok_or(<Error<T>>::SponsorNotSet)?401 == Self::account_id(),402 <Error<T>>::NoPermission403 );404 T::CollectionHandler::remove_collection_sponsor(collection_id)405 }406407 #[pallet::weight(T::WeightInfo::sponsor_contract())]408 pub fn sponsor_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {409 let admin_id = ensure_signed(admin)?;410411 ensure!(412 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,413 Error::<T>::NoPermission414 );415416 T::ContractHandler::set_sponsor(417 T::CrossAccountId::from_sub(Self::account_id()),418 contract_id,419 )420 }421422 #[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]423 pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {424 let admin_id = ensure_signed(admin)?;425426 ensure!(427 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,428 Error::<T>::NoPermission429 );430431 ensure!(432 T::ContractHandler::sponsor(contract_id)?433 .ok_or(<Error<T>>::SponsorNotSet)?434 .as_sub() == &Self::account_id(),435 <Error<T>>::NoPermission436 );437 T::ContractHandler::remove_contract_sponsor(contract_id)438 }439440 #[pallet::weight(T::WeightInfo::payout_stakers(stakers_number.unwrap_or(20) as u32))]441 pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {442 let admin_id = ensure_signed(admin)?;443444 ensure!(445 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,446 Error::<T>::NoPermission447 );448449 let current_recalc_block =450 Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());451 let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();452453 let mut storage_iterator = Self::get_next_calculated_key()454 .map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));455456 NextCalculatedRecord::<T>::set(None);457458 459 460 461 462 463464 465 466 467 468 469 470 471 472 473 474 475 476 477 478479 480 481 482 483484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506507 {508 let mut stakers_number = stakers_number.unwrap_or(20);509 let last_id = RefCell::new(None);510 let income_acc = RefCell::new(BalanceOf::<T>::default());511 let amount_acc = RefCell::new(BalanceOf::<T>::default());512513 let flush_stake = || -> DispatchResult {514 if let Some(last_id) = &*last_id.borrow() {515 if !income_acc.borrow().is_zero() {516 <T::Currency as Currency<T::AccountId>>::transfer(517 &T::TreasuryAccountId::get(),518 last_id,519 *income_acc.borrow(),520 ExistenceRequirement::KeepAlive,521 )522 .and_then(|_| {523 Self::add_lock_balance(last_id, *income_acc.borrow())?;524 <TotalStaked<T>>::try_mutate(|staked| {525 staked526 .checked_add(&*income_acc.borrow())527 .ok_or(ArithmeticError::Overflow.into())528 })529 })?;530531 Self::deposit_event(Event::StakingRecalculation(532 last_id.clone(),533 *amount_acc.borrow(),534 *income_acc.borrow(),535 ));536 }537538 *income_acc.borrow_mut() = BalanceOf::<T>::default();539 *amount_acc.borrow_mut() = BalanceOf::<T>::default();540 }541 Ok(())542 };543544 while let Some((545 (current_id, staked_block),546 (amount, next_recalc_block_for_stake),547 )) = storage_iterator.next()548 {549 if stakers_number == 0 {550 NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));551 break;552 }553 if last_id.borrow().as_ref() != Some(¤t_id) {554 flush_stake()?;555 *last_id.borrow_mut() = Some(current_id.clone());556 stakers_number -= 1;557 };558 if current_recalc_block >= next_recalc_block_for_stake {559 *amount_acc.borrow_mut() += amount;560 Self::recalculate_and_insert_stake(561 ¤t_id,562 staked_block,563 next_recalc_block,564 amount,565 ((current_recalc_block - next_recalc_block_for_stake)566 / T::RecalculationInterval::get())567 .into() + 1,568 &mut *income_acc.borrow_mut(),569 );570 }571 }572 flush_stake()?;573 }574575 Ok(())576 }577 }578}579580impl<T: Config> Pallet<T> {581 pub fn account_id() -> T::AccountId {582 T::PalletId::get().into_account_truncating()583 }584585 fn unlock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {586 let locked_balance = Self::get_locked_balance(staker)587 .map(|l| l.amount)588 .ok_or(<Error<T>>::IncorrectLockedBalanceOperation)?;589590 591 592 Self::set_lock_unchecked(593 staker,594 locked_balance595 .checked_sub(&amount)596 .ok_or(ArithmeticError::Underflow)?,597 );598 Ok(())599 }600601 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {602 Self::get_locked_balance(staker)603 .map_or(<BalanceOf<T>>::default(), |l| l.amount)604 .checked_add(&amount)605 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))606 .ok_or(ArithmeticError::Overflow.into())607 }608609 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {610 if amount.is_zero() {611 <T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);612 } else {613 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(614 LOCK_IDENTIFIER,615 staker,616 amount,617 WithdrawReasons::all(),618 )619 }620 }621622 pub fn get_locked_balance(623 staker: impl EncodeLike<T::AccountId>,624 ) -> Option<BalanceLock<BalanceOf<T>>> {625 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)626 .into_iter()627 .find(|l| l.id == LOCK_IDENTIFIER)628 }629630 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {631 let staked = Staked::<T>::iter_prefix((staker,))632 .into_iter()633 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {634 acc + amount635 });636 if staked != <BalanceOf<T>>::default() {637 Some(staked)638 } else {639 None640 }641 }642643 pub fn total_staked_by_id_per_block(644 staker: impl EncodeLike<T::AccountId>,645 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {646 let mut staked = Staked::<T>::iter_prefix((staker,))647 .into_iter()648 .map(|(block, (amount, _))| (block, amount))649 .collect::<Vec<_>>();650 staked.sort_by_key(|(block, _)| *block);651 if !staked.is_empty() {652 Some(staked)653 } else {654 None655 }656 }657658 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {659 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {660 Self::total_staked_by_id(s.as_sub())661 })662 }663664 665 666 667 668 669670 pub fn cross_id_total_staked_per_block(671 staker: T::CrossAccountId,672 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {673 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()674 }675676 fn recalculate_and_insert_stake(677 staker: &T::AccountId,678 staked_block: T::BlockNumber,679 next_recalc_block: T::BlockNumber,680 base: BalanceOf<T>,681 iters: u32,682 income_acc: &mut BalanceOf<T>,683 ) {684 let income = Self::calculate_income(base, iters);685686 base.checked_add(&income).map(|res| {687 <Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));688 *income_acc += income;689 });690 }691692 fn calculate_income<I>(base: I, iters: u32) -> I693 where694 I: EncodeLike<BalanceOf<T>> + Balance,695 {696 let mut income = base;697698 (0..iters).for_each(|_| income += T::IntervalIncome::get() * income);699700 income - base701 }702703 fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {704 (current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()705 }706707 708 709 710711 fn get_next_calculated_key() -> Option<Vec<u8>> {712 Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))713 }714}715716impl<T: Config> Pallet<T>717where718 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,719{720 721 722 723 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {724 staker.map_or(725 PendingUnstake::<T>::iter_values()726 .flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))727 .sum(),728 |s| {729 PendingUnstake::<T>::iter_values()730 .flatten()731 .filter_map(|(id, amount)| {732 if id == *s.as_sub() {733 Some(amount)734 } else {735 None736 }737 })738 .sum()739 },740 )741 }742743 pub fn cross_id_pending_unstake_per_block(744 staker: T::CrossAccountId,745 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {746 let mut unsorted_res = vec![];747 PendingUnstake::<T>::iter().for_each(|(block, pendings)| {748 pendings.into_iter().for_each(|(id, amount)| {749 if id == *staker.as_sub() {750 unsorted_res.push((block, amount));751 };752 })753 });754755 unsorted_res.sort_by_key(|(block, _)| *block);756 unsorted_res757 }758}