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::{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::*;505152use 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 103 #[pallet::constant]104 type PalletId: Get<PalletId>;105106 107 #[pallet::constant]108 type RecalculationInterval: Get<Self::BlockNumber>;109 110 #[pallet::constant]111 type PendingInterval: Get<Self::BlockNumber>;112113 114 115 116117 #[pallet::constant]118 type Nominal: Get<BalanceOf<Self>>;119120 #[pallet::constant]121 type IntervalIncome: Get<Perbill>;122123 124 type WeightInfo: WeightInfo;125126 127 type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;128129 130 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;131 }132133 #[pallet::pallet]134 #[pallet::generate_store(pub(super) trait Store)]135 pub struct Pallet<T>(_);136137 #[pallet::event]138 #[pallet::generate_deposit(fn deposit_event)]139 pub enum Event<T: Config> {140 StakingRecalculation(141 142 T::AccountId,143 144 BalanceOf<T>,145 146 BalanceOf<T>,147 ),148 Stake(T::AccountId, BalanceOf<T>),149 Unstake(T::AccountId, BalanceOf<T>),150 SetAdmin(T::AccountId),151 }152153 #[pallet::error]154 pub enum Error<T> {155 156 AdminNotSet,157 158 NoPermission,159 160 NotSufficientFunds,161 PendingForBlockOverflow,162 163 InvalidArgument,164 }165166 #[pallet::storage]167 pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;168169 #[pallet::storage]170 pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;171172 173 #[pallet::storage]174 pub type Staked<T: Config> = StorageNMap<175 Key = (176 Key<Blake2_128Concat, T::AccountId>,177 Key<Twox64Concat, T::BlockNumber>,178 ),179 Value = (BalanceOf<T>, T::BlockNumber),180 QueryKind = ValueQuery,181 >;182 183 #[pallet::storage]184 pub type StakesPerAccount<T: Config> =185 StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;186187 188 189 190 191 192 193 194 195 196 197 #[pallet::storage]198 pub type PendingUnstake<T: Config> = StorageMap<199 _,200 Twox64Concat,201 T::BlockNumber,202 BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,203 ValueQuery,204 >;205206 207 208 209210 211 212 213 214 215216 217 218 #[pallet::storage]219 #[pallet::getter(fn get_next_calculated_record)]220 pub type NextCalculatedRecord<T: Config> =221 StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;222223 #[pallet::hooks]224 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {225 fn on_initialize(current_block_number: T::BlockNumber) -> Weight226 where227 <T as frame_system::Config>::BlockNumber: From<u32>,228 {229 let mut consumed_weight = 0;230 let mut add_weight = |reads, writes, weight| {231 consumed_weight += T::DbWeight::get().reads_writes(reads, writes);232 consumed_weight += weight;233 };234235 let block_pending = PendingUnstake::<T>::take(current_block_number);236237 add_weight(0, 1, 0);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 consumed_weight246 }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 >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(),277 ArithmeticError::Underflow278 );279280 let balance =281 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);282283 ensure!(balance >= amount, ArithmeticError::Underflow);284285 <<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(286 &staker_id,287 amount,288 WithdrawReasons::RESERVE,289 balance - amount,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_unchecked(&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::get_sponsor(collection_id)?401 .ok_or(<Error<T>>::InvalidArgument)?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_conract(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::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?434 == T::CrossAccountId::from_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 509 510 511 512513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530531 532 533 534 535 536 537538 539 540 541 542 543544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574575 {576 let mut stakers_number = stakers_number.unwrap_or(20);577 let last_id = RefCell::new(None);578 let income_acc = RefCell::new(BalanceOf::<T>::default());579 let amount_acc = RefCell::new(BalanceOf::<T>::default());580581 let flush_stake = || -> DispatchResult {582 if let Some(last_id) = &*last_id.borrow() {583 if !income_acc.borrow().is_zero() {584 <T::Currency as Currency<T::AccountId>>::transfer(585 &T::TreasuryAccountId::get(),586 last_id,587 *income_acc.borrow(),588 ExistenceRequirement::KeepAlive,589 )590 .and_then(|_| {591 Self::add_lock_balance(last_id, *income_acc.borrow())?;592 <TotalStaked<T>>::try_mutate(|staked| {593 staked594 .checked_add(&*income_acc.borrow())595 .ok_or(ArithmeticError::Overflow.into())596 })597 })?;598599 Self::deposit_event(Event::StakingRecalculation(600 last_id.clone(),601 *amount_acc.borrow(),602 *income_acc.borrow(),603 ));604 }605606 *income_acc.borrow_mut() = BalanceOf::<T>::default();607 *amount_acc.borrow_mut() = BalanceOf::<T>::default();608 }609 Ok(())610 };611612 while let Some((613 (current_id, staked_block),614 (amount, next_recalc_block_for_stake),615 )) = storage_iterator.next()616 {617 if stakers_number == 0 {618 NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));619 break;620 }621 if last_id.borrow().as_ref() != Some(¤t_id) {622 flush_stake()?;623 *last_id.borrow_mut() = Some(current_id.clone());624 stakers_number -= 1;625 };626 if current_recalc_block >= next_recalc_block_for_stake {627 *amount_acc.borrow_mut() += amount;628 Self::recalculate_and_insert_stake(629 ¤t_id,630 staked_block,631 next_recalc_block,632 amount,633 ((current_recalc_block - next_recalc_block_for_stake)634 / T::RecalculationInterval::get())635 .into() + 1,636 &mut *income_acc.borrow_mut(),637 );638 }639 }640 flush_stake()?;641 }642643 Ok(())644 }645 }646}647648impl<T: Config> Pallet<T> {649 pub fn account_id() -> T::AccountId {650 T::PalletId::get().into_account_truncating()651 }652653 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {654 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();655 locked_balance -= amount;656 Self::set_lock_unchecked(staker, locked_balance);657 }658659 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {660 Self::get_locked_balance(staker)661 .map_or(<BalanceOf<T>>::default(), |l| l.amount)662 .checked_add(&amount)663 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))664 .ok_or(ArithmeticError::Overflow.into())665 }666667 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {668 if amount.is_zero() {669 <T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);670 } else {671 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(672 LOCK_IDENTIFIER,673 staker,674 amount,675 WithdrawReasons::all(),676 )677 }678 }679680 pub fn get_locked_balance(681 staker: impl EncodeLike<T::AccountId>,682 ) -> Option<BalanceLock<BalanceOf<T>>> {683 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)684 .into_iter()685 .find(|l| l.id == LOCK_IDENTIFIER)686 }687688 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {689 let staked = Staked::<T>::iter_prefix((staker,))690 .into_iter()691 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {692 acc + amount693 });694 if staked != <BalanceOf<T>>::default() {695 Some(staked)696 } else {697 None698 }699 }700701 pub fn total_staked_by_id_per_block(702 staker: impl EncodeLike<T::AccountId>,703 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {704 let mut staked = Staked::<T>::iter_prefix((staker,))705 .into_iter()706 .map(|(block, (amount, _))| (block, amount))707 .collect::<Vec<_>>();708 staked.sort_by_key(|(block, _)| *block);709 if !staked.is_empty() {710 Some(staked)711 } else {712 None713 }714 }715716 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {717 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {718 Self::total_staked_by_id(s.as_sub())719 })720 721 }722723 pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {724 Self::get_locked_balance(staker.as_sub())725 .map(|l| l.amount)726 .unwrap_or_default()727 }728729 pub fn cross_id_total_staked_per_block(730 staker: T::CrossAccountId,731 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {732 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()733 }734735 fn recalculate_and_insert_stake(736 staker: &T::AccountId,737 staked_block: T::BlockNumber,738 next_recalc_block: T::BlockNumber,739 base: BalanceOf<T>,740 iters: u32,741 income_acc: &mut BalanceOf<T>,742 ) {743 let income = Self::calculate_income(base, iters);744745 base.checked_add(&income).map(|res| {746 <Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));747 *income_acc += income;748 });749 }750751 fn calculate_income<I>(base: I, iters: u32) -> I752 where753 I: EncodeLike<BalanceOf<T>> + Balance,754 {755 let mut income = base;756757 (0..iters).for_each(|_| income += T::IntervalIncome::get() * income);758759 income - base760 }761762 fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {763 (current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()764 }765766 767 768 769770 fn get_next_calculated_key() -> Option<Vec<u8>> {771 Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))772 }773}774775impl<T: Config> Pallet<T>776where777 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,778{779 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {780 staker.map_or(781 PendingUnstake::<T>::iter_values()782 .flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))783 .sum(),784 |s| {785 PendingUnstake::<T>::iter_values()786 .flatten()787 .filter_map(|(id, amount)| {788 if id == *s.as_sub() {789 Some(amount)790 } else {791 None792 }793 })794 .sum()795 },796 )797 }798799 pub fn cross_id_pending_unstake_per_block(800 staker: T::CrossAccountId,801 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {802 let mut unsorted_res = vec![];803 PendingUnstake::<T>::iter().for_each(|(block, pendings)| {804 pendings.into_iter().for_each(|(id, amount)| {805 if id == *staker.as_sub() {806 unsorted_res.push((block, amount));807 };808 })809 });810811 unsorted_res.sort_by_key(|(block, _)| *block);812 unsorted_res813 }814}