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";73const PENDING_LIMIT_PER_BLOCK: u32 = 3;7475type BalanceOf<T> =76 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;7778798081828384#[frame_support::pallet]85pub mod pallet {86 use super::*;87 use frame_support::{88 Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId,89 traits::ReservableCurrency,90 };91 use frame_system::pallet_prelude::*;9293 #[pallet::config]94 pub trait Config: frame_system::Config + pallet_evm::account::Config {95 type Currency: ExtendedLockableCurrency<Self::AccountId>96 + ReservableCurrency<Self::AccountId>;9798 type CollectionHandler: CollectionHandler<99 AccountId = Self::AccountId,100 CollectionId = CollectionId,101 >;102103 type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;104105 type TreasuryAccountId: Get<Self::AccountId>;106107 108 #[pallet::constant]109 type PalletId: Get<PalletId>;110111 112 #[pallet::constant]113 type RecalculationInterval: Get<Self::BlockNumber>;114 115 #[pallet::constant]116 type PendingInterval: Get<Self::BlockNumber>;117118 119 #[pallet::constant]120 type Day: Get<Self::BlockNumber>; 121122 #[pallet::constant]123 type Nominal: Get<BalanceOf<Self>>;124125 #[pallet::constant]126 type IntervalIncome: Get<Perbill>;127128 129 type WeightInfo: WeightInfo;130131 132 type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;133134 135 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;136 }137138 #[pallet::pallet]139 #[pallet::generate_store(pub(super) trait Store)]140 pub struct Pallet<T>(_);141142 #[pallet::event]143 #[pallet::generate_deposit(fn deposit_event)]144 pub enum Event<T: Config> {145 StakingRecalculation(146 147 T::AccountId,148 149 BalanceOf<T>,150 151 BalanceOf<T>,152 ),153 }154155 #[pallet::error]156 pub enum Error<T> {157 158 AdminNotSet,159 160 NoPermission,161 162 NotSufficientFounds,163 PendingForBlockOverflow,164 165 InvalidArgument,166 }167168 #[pallet::storage]169 pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;170171 #[pallet::storage]172 pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;173174 175 #[pallet::storage]176 pub type Staked<T: Config> = StorageNMap<177 Key = (178 Key<Blake2_128Concat, T::AccountId>,179 Key<Twox64Concat, T::BlockNumber>,180 ),181 Value = (BalanceOf<T>, T::BlockNumber),182 QueryKind = ValueQuery,183 >;184 185 #[pallet::storage]186 pub type StakesPerAccount<T: Config> =187 StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;188189 190 191 192 193 194 195 196 197 198 199 #[pallet::storage]200 pub type PendingUnstake<T: Config> = StorageMap<201 _,202 Twox64Concat,203 T::BlockNumber,204 BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,205 ValueQuery,206 >;207208 209 #[pallet::storage]210 pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;211212 213 214 215 216 217218 219 220 #[pallet::storage]221 #[pallet::getter(fn get_next_calculated_record)]222 pub type NextCalculatedRecord<T: Config> =223 StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;224225 #[pallet::hooks]226 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {227 fn on_initialize(current_block_number: T::BlockNumber) -> Weight228 where229 <T as frame_system::Config>::BlockNumber: From<u32>,230 {231 let mut consumed_weight = 0;232 let mut add_weight = |reads, writes, weight| {233 consumed_weight += T::DbWeight::get().reads_writes(reads, writes);234 consumed_weight += weight;235 };236237 let block_pending = PendingUnstake::<T>::take(current_block_number);238239 add_weight(0, 1, 0);240241 if !block_pending.is_empty() {242 block_pending.into_iter().for_each(|(staker, amount)| {243 <T::Currency as ReservableCurrency<T::AccountId>>::unreserve(&staker, amount);244 });245 }246247 consumed_weight248 }249 }250251 #[pallet::call]252 impl<T: Config> Pallet<T>253 where254 T::BlockNumber: From<u32> + Into<u32>,255 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,256 {257 #[pallet::weight(T::WeightInfo::set_admin_address())]258 pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {259 ensure_root(origin)?;260 <Admin<T>>::set(Some(admin.as_sub().to_owned()));261262 Ok(())263 }264265 266 267 268 269 270 271 272 273 274275 276 277 278 279280 281 282283 284 285286 287 288289 290 291 292 293 294 295296 297 298 299 300301 302 303304 #[pallet::weight(T::WeightInfo::stake())]305 pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {306 let staker_id = ensure_signed(staker)?;307308 ensure!(309 StakesPerAccount::<T>::get(&staker_id) < 10,310 Error::<T>::NoPermission311 );312313 ensure!(314 amount >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(),315 ArithmeticError::Underflow316 );317318 let balance =319 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);320321 ensure!(balance >= amount, ArithmeticError::Underflow);322323 <<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(324 &staker_id,325 amount,326 WithdrawReasons::all(),327 balance - amount,328 )?;329330 Self::add_lock_balance(&staker_id, amount)?;331332 let block_number = T::RelayBlockNumberProvider::current_block_number();333 let recalc_block = (block_number / T::RecalculationInterval::get() + 2u32.into())334 * T::RecalculationInterval::get();335336 <Staked<T>>::insert((&staker_id, block_number), {337 let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));338 balance_and_recalc_block.0 = balance_and_recalc_block339 .0340 .checked_add(&amount)341 .ok_or(ArithmeticError::Overflow)?;342 balance_and_recalc_block.1 = recalc_block;343 balance_and_recalc_block344 });345346 <TotalStaked<T>>::set(347 <TotalStaked<T>>::get()348 .checked_add(&amount)349 .ok_or(ArithmeticError::Overflow)?,350 );351352 StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);353 Ok(())354 }355356 #[pallet::weight(T::WeightInfo::unstake())]357 pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {358 let staker_id = ensure_signed(staker)?;359 let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();360 let mut pendings = <PendingUnstake<T>>::get(block);361362 ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);363364 let mut total_stakes = 0u64;365366 let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))367 .map(|(_, (amount, _))| {368 total_stakes += 1;369 amount370 })371 .sum();372373 if total_staked.is_zero() {374 return Ok(None.into()); 375 }376377 pendings378 .try_push((staker_id.clone(), total_staked))379 .map_err(|_| Error::<T>::PendingForBlockOverflow)?;380381 <PendingUnstake<T>>::insert(block, pendings);382383 Self::unlock_balance_unchecked(&staker_id, total_staked);384385 <T::Currency as ReservableCurrency<T::AccountId>>::reserve(&staker_id, total_staked)?;386387 TotalStaked::<T>::set(388 TotalStaked::<T>::get()389 .checked_sub(&total_staked)390 .ok_or(ArithmeticError::Underflow)?,391 );392393 StakesPerAccount::<T>::remove(&staker_id);394395 Ok(None.into())396 }397398 #[pallet::weight(T::WeightInfo::sponsor_collection())]399 pub fn sponsor_collection(400 admin: OriginFor<T>,401 collection_id: CollectionId,402 ) -> DispatchResult {403 let admin_id = ensure_signed(admin)?;404 ensure!(405 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,406 Error::<T>::NoPermission407 );408409 T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)410 }411 #[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]412 pub fn stop_sponsoring_collection(413 admin: OriginFor<T>,414 collection_id: CollectionId,415 ) -> DispatchResult {416 let admin_id = ensure_signed(admin)?;417418 ensure!(419 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,420 Error::<T>::NoPermission421 );422423 ensure!(424 T::CollectionHandler::get_sponsor(collection_id)?425 .ok_or(<Error<T>>::InvalidArgument)?426 == Self::account_id(),427 <Error<T>>::NoPermission428 );429 T::CollectionHandler::remove_collection_sponsor(collection_id)430 }431432 #[pallet::weight(T::WeightInfo::sponsor_contract())]433 pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {434 let admin_id = ensure_signed(admin)?;435436 ensure!(437 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,438 Error::<T>::NoPermission439 );440441 T::ContractHandler::set_sponsor(442 T::CrossAccountId::from_sub(Self::account_id()),443 contract_id,444 )445 }446447 #[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]448 pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {449 let admin_id = ensure_signed(admin)?;450451 ensure!(452 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,453 Error::<T>::NoPermission454 );455456 ensure!(457 T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?458 == T::CrossAccountId::from_sub(Self::account_id()),459 <Error<T>>::NoPermission460 );461 T::ContractHandler::remove_contract_sponsor(contract_id)462 }463464 #[pallet::weight(0)]465 pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {466 let admin_id = ensure_signed(admin)?;467468 ensure!(469 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,470 Error::<T>::NoPermission471 );472473 let current_recalc_block =474 Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());475 let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();476477 let mut storage_iterator = Self::get_next_calculated_key()478 .map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));479480 NextCalculatedRecord::<T>::set(None);481482 483 484 485 486 487488 489 490 491 492 493 494 495 496 497 498 499 500 501 502503 504 505 506 507508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 {532 let mut stakers_number = stakers_number.unwrap_or(20);533 let last_id = RefCell::new(None);534 let income_acc = RefCell::new(BalanceOf::<T>::default());535 let amount_acc = RefCell::new(BalanceOf::<T>::default());536 537 let flush_stake = || -> DispatchResult {538 if let Some(last_id) = &*last_id.borrow() {539 if !income_acc.borrow().is_zero() {540 <T::Currency as Currency<T::AccountId>>::transfer(541 &T::TreasuryAccountId::get(),542 last_id,543 *income_acc.borrow(),544 ExistenceRequirement::KeepAlive,545 )546 .and_then(|_| Self::add_lock_balance(last_id, *income_acc.borrow()))?;547548 Self::deposit_event(Event::StakingRecalculation(549 last_id.clone(),550 *amount_acc.borrow(),551 *income_acc.borrow(),552 ));553 }554555 *income_acc.borrow_mut() = BalanceOf::<T>::default();556 *amount_acc.borrow_mut() = BalanceOf::<T>::default();557 }558 Ok(())559 };560561 while let Some((562 (current_id, staked_block),563 (amount, next_recalc_block_for_stake),564 )) = storage_iterator.next()565 {566 if stakers_number == 0 {567 NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));568 break;569 }570 stakers_number -= 1;571 if last_id.borrow().as_ref() != Some(¤t_id) {572 flush_stake()?;573 };574 *last_id.borrow_mut() = Some(current_id.clone());575 if current_recalc_block >= next_recalc_block_for_stake {576 *amount_acc.borrow_mut() += amount; 577 Self::recalculate_and_insert_stake(578 ¤t_id,579 staked_block,580 next_recalc_block,581 amount,582 ((current_recalc_block - next_recalc_block_for_stake)583 / T::RecalculationInterval::get())584 .into() + 1,585 &mut *income_acc.borrow_mut(),586 );587 }588 }589 flush_stake()?;590 }591592593 Ok(())594 }595 }596}597598impl<T: Config> Pallet<T> {599 pub fn account_id() -> T::AccountId {600 T::PalletId::get().into_account_truncating()601 }602603 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {604 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();605 locked_balance -= amount;606 Self::set_lock_unchecked(staker, locked_balance);607 }608609 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {610 Self::get_locked_balance(staker)611 .map_or(<BalanceOf<T>>::default(), |l| l.amount)612 .checked_add(&amount)613 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))614 .ok_or(ArithmeticError::Overflow.into())615 }616617 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {618 if amount.is_zero() {619 <T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);620 } else {621 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(622 LOCK_IDENTIFIER,623 staker,624 amount,625 WithdrawReasons::all(),626 )627 }628 }629630 pub fn get_locked_balance(631 staker: impl EncodeLike<T::AccountId>,632 ) -> Option<BalanceLock<BalanceOf<T>>> {633 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)634 .into_iter()635 .find(|l| l.id == LOCK_IDENTIFIER)636 }637638 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {639 let staked = Staked::<T>::iter_prefix((staker,))640 .into_iter()641 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {642 acc + amount643 });644 if staked != <BalanceOf<T>>::default() {645 Some(staked)646 } else {647 None648 }649 }650651 pub fn total_staked_by_id_per_block(652 staker: impl EncodeLike<T::AccountId>,653 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {654 let mut staked = Staked::<T>::iter_prefix((staker,))655 .into_iter()656 .map(|(block, (amount, _))| (block, amount))657 .collect::<Vec<_>>();658 staked.sort_by_key(|(block, _)| *block);659 if !staked.is_empty() {660 Some(staked)661 } else {662 None663 }664 }665666 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {667 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {668 Self::total_staked_by_id(s.as_sub())669 })670 671 }672673 pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {674 Self::get_locked_balance(staker.as_sub())675 .map(|l| l.amount)676 .unwrap_or_default()677 }678679 pub fn cross_id_total_staked_per_block(680 staker: T::CrossAccountId,681 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {682 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()683 }684685 fn recalculate_and_insert_stake(686 staker: &T::AccountId,687 staked_block: T::BlockNumber,688 next_recalc_block: T::BlockNumber,689 base: BalanceOf<T>,690 iters: u32,691 income_acc: &mut BalanceOf<T>,692 ) {693 let income = Self::calculate_income(base, iters);694695 base.checked_add(&income).map(|res| {696 <Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));697 *income_acc += income;698 });699 }700701 fn calculate_income<I>(base: I, iters: u32) -> I702 where703 I: EncodeLike<BalanceOf<T>> + Balance,704 {705 let mut income = base;706707 (0..iters).for_each(|_| income += T::IntervalIncome::get() * income);708709 income - base710 }711712 fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {713 (current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()714 }715716 717 718 719720 fn get_next_calculated_key() -> Option<Vec<u8>> {721 Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))722 }723}724725impl<T: Config> Pallet<T>726where727 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,728{729 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {730 staker.map_or(731 PendingUnstake::<T>::iter_values()732 .flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))733 .sum(),734 |s| {735 PendingUnstake::<T>::iter_values()736 .flatten()737 .filter_map(|(id, amount)| {738 if id == *s.as_sub() {739 Some(amount)740 } else {741 None742 }743 })744 .sum()745 },746 )747 }748749 pub fn cross_id_pending_unstake_per_block(750 staker: T::CrossAccountId,751 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {752 let mut unsorted_res = vec![];753 PendingUnstake::<T>::iter().for_each(|(block, pendings)| {754 pendings.into_iter().for_each(|(id, amount)| {755 if id == *staker.as_sub() {756 unsorted_res.push((block, amount));757 };758 })759 });760761 unsorted_res.sort_by_key(|(block, _)| *block);762 unsorted_res763 }764}