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 230 231 232 233 234235 let block_pending = PendingUnstake::<T>::take(current_block_number);236 let counter = block_pending.len() as u32;237 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 }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 >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(),278 ArithmeticError::Underflow279 );280281 let balance =282 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);283284 285286 <<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(287 &staker_id,288 amount,289 WithdrawReasons::RESERVE,290 balance - amount,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()); 350 }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_unchecked(&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::get_sponsor(collection_id)?402 .ok_or(<Error<T>>::InvalidArgument)?403 == Self::account_id(),404 <Error<T>>::NoPermission405 );406 T::CollectionHandler::remove_collection_sponsor(collection_id)407 }408409 #[pallet::weight(T::WeightInfo::sponsor_contract())]410 pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {411 let admin_id = ensure_signed(admin)?;412413 ensure!(414 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,415 Error::<T>::NoPermission416 );417418 T::ContractHandler::set_sponsor(419 T::CrossAccountId::from_sub(Self::account_id()),420 contract_id,421 )422 }423424 #[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]425 pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {426 let admin_id = ensure_signed(admin)?;427428 ensure!(429 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,430 Error::<T>::NoPermission431 );432433 ensure!(434 T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?435 == T::CrossAccountId::from_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 461 462 463 464465 466 467 468 469 470 471 472 473 474 475 476 477 478 479480 481 482 483 484485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507508 509 510 511 512 513514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531532 533 534 535 536 537 538539 540 541 542 543 544545 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 574 575576 {577 let mut stakers_number = stakers_number.unwrap_or(20);578 let last_id = RefCell::new(None);579 let income_acc = RefCell::new(BalanceOf::<T>::default());580 let amount_acc = RefCell::new(BalanceOf::<T>::default());581582 let flush_stake = || -> DispatchResult {583 if let Some(last_id) = &*last_id.borrow() {584 if !income_acc.borrow().is_zero() {585 <T::Currency as Currency<T::AccountId>>::transfer(586 &T::TreasuryAccountId::get(),587 last_id,588 *income_acc.borrow(),589 ExistenceRequirement::KeepAlive,590 )591 .and_then(|_| {592 Self::add_lock_balance(last_id, *income_acc.borrow())?;593 <TotalStaked<T>>::try_mutate(|staked| {594 staked595 .checked_add(&*income_acc.borrow())596 .ok_or(ArithmeticError::Overflow.into())597 })598 })?;599600 Self::deposit_event(Event::StakingRecalculation(601 last_id.clone(),602 *amount_acc.borrow(),603 *income_acc.borrow(),604 ));605 }606607 *income_acc.borrow_mut() = BalanceOf::<T>::default();608 *amount_acc.borrow_mut() = BalanceOf::<T>::default();609 }610 Ok(())611 };612613 while let Some((614 (current_id, staked_block),615 (amount, next_recalc_block_for_stake),616 )) = storage_iterator.next()617 {618 if stakers_number == 0 {619 NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));620 break;621 }622 if last_id.borrow().as_ref() != Some(¤t_id) {623 flush_stake()?;624 *last_id.borrow_mut() = Some(current_id.clone());625 stakers_number -= 1;626 };627 if current_recalc_block >= next_recalc_block_for_stake {628 *amount_acc.borrow_mut() += amount;629 Self::recalculate_and_insert_stake(630 ¤t_id,631 staked_block,632 next_recalc_block,633 amount,634 ((current_recalc_block - next_recalc_block_for_stake)635 / T::RecalculationInterval::get())636 .into() + 1,637 &mut *income_acc.borrow_mut(),638 );639 }640 }641 flush_stake()?;642 }643644 Ok(())645 }646 }647}648649impl<T: Config> Pallet<T> {650 pub fn account_id() -> T::AccountId {651 T::PalletId::get().into_account_truncating()652 }653654 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {655 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();656 locked_balance -= amount;657 Self::set_lock_unchecked(staker, locked_balance);658 }659660 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {661 Self::get_locked_balance(staker)662 .map_or(<BalanceOf<T>>::default(), |l| l.amount)663 .checked_add(&amount)664 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))665 .ok_or(ArithmeticError::Overflow.into())666 }667668 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {669 if amount.is_zero() {670 <T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);671 } else {672 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(673 LOCK_IDENTIFIER,674 staker,675 amount,676 WithdrawReasons::RESERVE,677 )678 }679 }680681 pub fn get_locked_balance(682 staker: impl EncodeLike<T::AccountId>,683 ) -> Option<BalanceLock<BalanceOf<T>>> {684 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)685 .into_iter()686 .find(|l| l.id == LOCK_IDENTIFIER)687 }688689 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {690 let staked = Staked::<T>::iter_prefix((staker,))691 .into_iter()692 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {693 acc + amount694 });695 if staked != <BalanceOf<T>>::default() {696 Some(staked)697 } else {698 None699 }700 }701702 pub fn total_staked_by_id_per_block(703 staker: impl EncodeLike<T::AccountId>,704 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {705 let mut staked = Staked::<T>::iter_prefix((staker,))706 .into_iter()707 .map(|(block, (amount, _))| (block, amount))708 .collect::<Vec<_>>();709 staked.sort_by_key(|(block, _)| *block);710 if !staked.is_empty() {711 Some(staked)712 } else {713 None714 }715 }716717 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {718 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {719 Self::total_staked_by_id(s.as_sub())720 })721 722 }723724 pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {725 Self::get_locked_balance(staker.as_sub())726 .map(|l| l.amount)727 .unwrap_or_default()728 }729730 pub fn cross_id_total_staked_per_block(731 staker: T::CrossAccountId,732 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {733 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()734 }735736 fn recalculate_and_insert_stake(737 staker: &T::AccountId,738 staked_block: T::BlockNumber,739 next_recalc_block: T::BlockNumber,740 base: BalanceOf<T>,741 iters: u32,742 income_acc: &mut BalanceOf<T>,743 ) {744 let income = Self::calculate_income(base, iters);745746 base.checked_add(&income).map(|res| {747 <Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));748 *income_acc += income;749 });750 }751752 fn calculate_income<I>(base: I, iters: u32) -> I753 where754 I: EncodeLike<BalanceOf<T>> + Balance,755 {756 let mut income = base;757758 (0..iters).for_each(|_| income += T::IntervalIncome::get() * income);759760 income - base761 }762763 fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {764 (current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()765 }766767 768 769 770771 fn get_next_calculated_key() -> Option<Vec<u8>> {772 Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))773 }774}775776impl<T: Config> Pallet<T>777where778 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,779{780 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {781 staker.map_or(782 PendingUnstake::<T>::iter_values()783 .flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))784 .sum(),785 |s| {786 PendingUnstake::<T>::iter_values()787 .flatten()788 .filter_map(|(id, amount)| {789 if id == *s.as_sub() {790 Some(amount)791 } else {792 None793 }794 })795 .sum()796 },797 )798 }799800 pub fn cross_id_pending_unstake_per_block(801 staker: T::CrossAccountId,802 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {803 let mut unsorted_res = vec![];804 PendingUnstake::<T>::iter().for_each(|(block, pendings)| {805 pendings.into_iter().for_each(|(id, amount)| {806 if id == *staker.as_sub() {807 unsorted_res.push((block, amount));808 };809 })810 });811812 unsorted_res.sort_by_key(|(block, _)| *block);813 unsorted_res814 }815}