123456789101112131415161718192021222324252627#![cfg_attr(not(feature = "std"), no_std)]2829#[cfg(feature = "runtime-benchmarks")]30mod benchmarking;31#[cfg(test)]32mod tests;33pub mod types;34pub mod weights;3536use sp_std::{37 vec::{Vec},38 vec,39 iter::Sum,40 borrow::ToOwned,41 cell::RefCell,42};43use sp_core::H160;44use codec::EncodeLike;45use pallet_balances::BalanceLock;46pub use types::*;4748use up_data_structs::CollectionId;4950use frame_support::{51 dispatch::{DispatchResult},52 traits::{53 Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,54 },55 ensure,56};5758use weights::WeightInfo;5960pub use pallet::*;61use pallet_evm::account::CrossAccountId;62use sp_runtime::{63 Perbill,64 traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion, Zero},65 ArithmeticError,66};6768pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";6970const PENDING_LIMIT_PER_BLOCK: u32 = 3;7172type BalanceOf<T> =73 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;7475#[frame_support::pallet]76pub mod pallet {77 use super::*;78 use frame_support::{79 Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId,80 traits::ReservableCurrency,81 };82 use frame_system::pallet_prelude::*;8384 #[pallet::config]85 pub trait Config: frame_system::Config + pallet_evm::account::Config {86 87 type Currency: ExtendedLockableCurrency<Self::AccountId>88 + ReservableCurrency<Self::AccountId>;8990 91 type CollectionHandler: CollectionHandler<92 AccountId = Self::AccountId,93 CollectionId = CollectionId,94 >;9596 97 type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;9899 100 type TreasuryAccountId: Get<Self::AccountId>;101102 103 #[pallet::constant]104 type PalletId: Get<PalletId>;105106 107 #[pallet::constant]108 type RecalculationInterval: Get<Self::BlockNumber>;109110 111 #[pallet::constant]112 type PendingInterval: Get<Self::BlockNumber>;113114 115 #[pallet::constant]116 type IntervalIncome: Get<Perbill>;117118 119 #[pallet::constant]120 type Nominal: Get<BalanceOf<Self>>;121122 123 type WeightInfo: WeightInfo;124125 126 type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;127128 129 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;130 }131132 #[pallet::pallet]133 #[pallet::generate_store(pub(super) trait Store)]134 pub struct Pallet<T>(_);135136 #[pallet::event]137 #[pallet::generate_deposit(fn deposit_event)]138 pub enum Event<T: Config> {139 140 141 142 143 144 145 StakingRecalculation(146 147 T::AccountId,148 149 BalanceOf<T>,150 151 BalanceOf<T>,152 ),153 154 155 156 157 158 159 Stake(T::AccountId, BalanceOf<T>),160 161 162 163 164 165 166 Unstake(T::AccountId, BalanceOf<T>),167 168 169 170 171 172 SetAdmin(T::AccountId),173 }174175 #[pallet::error]176 pub enum Error<T> {177 178 AdminNotSet,179 180 NoPermission,181 182 NotSufficientFunds,183 184 PendingForBlockOverflow,185 186 SponsorNotSet,187 188 IncorrectLockedBalanceOperation,189 }190191 #[pallet::storage]192 pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;193194 #[pallet::storage]195 pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;196197 198 #[pallet::storage]199 pub type Staked<T: Config> = StorageNMap<200 Key = (201 Key<Blake2_128Concat, T::AccountId>,202 Key<Twox64Concat, T::BlockNumber>,203 ),204 Value = (BalanceOf<T>, T::BlockNumber),205 QueryKind = ValueQuery,206 >;207 208 #[pallet::storage]209 pub type StakesPerAccount<T: Config> =210 StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;211212 #[pallet::storage]213 pub type PendingUnstake<T: Config> = StorageMap<214 _,215 Twox64Concat,216 T::BlockNumber,217 BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,218 ValueQuery,219 >;220221 222 223 #[pallet::storage]224 #[pallet::getter(fn get_next_calculated_record)]225 pub type NextCalculatedRecord<T: Config> =226 StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;227228 #[pallet::hooks]229 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {230 231 232 233 fn on_initialize(current_block_number: T::BlockNumber) -> Weight234 where235 <T as frame_system::Config>::BlockNumber: From<u32>,236 {237 let block_pending = PendingUnstake::<T>::take(current_block_number);238 let counter = block_pending.len() as u32;239240 if !block_pending.is_empty() {241 block_pending.into_iter().for_each(|(staker, amount)| {242 <T::Currency as ReservableCurrency<T::AccountId>>::unreserve(&staker, amount);243 });244 }245246 T::WeightInfo::on_initialize(counter)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 >= <BalanceOf<T>>::from(100u128) * T::Nominal::get(),278 ArithmeticError::Underflow279 );280281 let balance =282 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);283284 <<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(285 &staker_id,286 amount,287 WithdrawReasons::all(),288 balance289 .checked_sub(&amount)290 .ok_or(ArithmeticError::Underflow)?,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(&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::sponsor(collection_id)?.ok_or(<Error<T>>::SponsorNotSet)?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_contract(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::sponsor(contract_id)?434 .ok_or(<Error<T>>::SponsorNotSet)?435 .as_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 let mut stakers_number = stakers_number.unwrap_or(20);510 let last_id = RefCell::new(None);511 let income_acc = RefCell::new(BalanceOf::<T>::default());512 let amount_acc = RefCell::new(BalanceOf::<T>::default());513514 let flush_stake = || -> DispatchResult {515 if let Some(last_id) = &*last_id.borrow() {516 if !income_acc.borrow().is_zero() {517 <T::Currency as Currency<T::AccountId>>::transfer(518 &T::TreasuryAccountId::get(),519 last_id,520 *income_acc.borrow(),521 ExistenceRequirement::KeepAlive,522 )523 .and_then(|_| {524 Self::add_lock_balance(last_id, *income_acc.borrow())?;525 <TotalStaked<T>>::try_mutate(|staked| {526 staked527 .checked_add(&*income_acc.borrow())528 .ok_or(ArithmeticError::Overflow.into())529 })530 })?;531532 Self::deposit_event(Event::StakingRecalculation(533 last_id.clone(),534 *amount_acc.borrow(),535 *income_acc.borrow(),536 ));537 }538539 *income_acc.borrow_mut() = BalanceOf::<T>::default();540 *amount_acc.borrow_mut() = BalanceOf::<T>::default();541 }542 Ok(())543 };544545 while let Some((546 (current_id, staked_block),547 (amount, next_recalc_block_for_stake),548 )) = storage_iterator.next()549 {550 if stakers_number == 0 {551 NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));552 break;553 }554 if last_id.borrow().as_ref() != Some(¤t_id) {555 flush_stake()?;556 *last_id.borrow_mut() = Some(current_id.clone());557 stakers_number -= 1;558 };559 if current_recalc_block >= next_recalc_block_for_stake {560 *amount_acc.borrow_mut() += amount;561 Self::recalculate_and_insert_stake(562 ¤t_id,563 staked_block,564 next_recalc_block,565 amount,566 ((current_recalc_block - next_recalc_block_for_stake)567 / T::RecalculationInterval::get())568 .into() + 1,569 &mut *income_acc.borrow_mut(),570 );571 }572 }573 flush_stake()?;574 }575576 Ok(())577 }578 }579}580581impl<T: Config> Pallet<T> {582 pub fn account_id() -> T::AccountId {583 T::PalletId::get().into_account_truncating()584 }585586 fn unlock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {587 let locked_balance = Self::get_locked_balance(staker)588 .map(|l| l.amount)589 .ok_or(<Error<T>>::IncorrectLockedBalanceOperation)?;590591 592 593 Self::set_lock_unchecked(594 staker,595 locked_balance596 .checked_sub(&amount)597 .ok_or(ArithmeticError::Underflow)?,598 );599 Ok(())600 }601602 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {603 Self::get_locked_balance(staker)604 .map_or(<BalanceOf<T>>::default(), |l| l.amount)605 .checked_add(&amount)606 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))607 .ok_or(ArithmeticError::Overflow.into())608 }609610 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {611 if amount.is_zero() {612 <T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);613 } else {614 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(615 LOCK_IDENTIFIER,616 staker,617 amount,618 WithdrawReasons::all(),619 )620 }621 }622623 pub fn get_locked_balance(624 staker: impl EncodeLike<T::AccountId>,625 ) -> Option<BalanceLock<BalanceOf<T>>> {626 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)627 .into_iter()628 .find(|l| l.id == LOCK_IDENTIFIER)629 }630631 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {632 let staked = Staked::<T>::iter_prefix((staker,))633 .into_iter()634 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {635 acc + amount636 });637 if staked != <BalanceOf<T>>::default() {638 Some(staked)639 } else {640 None641 }642 }643644 pub fn total_staked_by_id_per_block(645 staker: impl EncodeLike<T::AccountId>,646 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {647 let mut staked = Staked::<T>::iter_prefix((staker,))648 .into_iter()649 .map(|(block, (amount, _))| (block, amount))650 .collect::<Vec<_>>();651 staked.sort_by_key(|(block, _)| *block);652 if !staked.is_empty() {653 Some(staked)654 } else {655 None656 }657 }658659 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {660 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {661 Self::total_staked_by_id(s.as_sub())662 })663 }664665 666 667 668 669 670671 pub fn cross_id_total_staked_per_block(672 staker: T::CrossAccountId,673 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {674 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()675 }676677 fn recalculate_and_insert_stake(678 staker: &T::AccountId,679 staked_block: T::BlockNumber,680 next_recalc_block: T::BlockNumber,681 base: BalanceOf<T>,682 iters: u32,683 income_acc: &mut BalanceOf<T>,684 ) {685 let income = Self::calculate_income(base, iters);686687 base.checked_add(&income).map(|res| {688 <Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));689 *income_acc += income;690 });691 }692693 fn calculate_income<I>(base: I, iters: u32) -> I694 where695 I: EncodeLike<BalanceOf<T>> + Balance,696 {697 let mut income = base;698699 (0..iters).for_each(|_| income += T::IntervalIncome::get() * income);700701 income - base702 }703704 fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {705 (current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()706 }707708 709 710 711712 fn get_next_calculated_key() -> Option<Vec<u8>> {713 Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))714 }715}716717impl<T: Config> Pallet<T>718where719 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,720{721 722 723 724 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {725 staker.map_or(726 PendingUnstake::<T>::iter_values()727 .flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))728 .sum(),729 |s| {730 PendingUnstake::<T>::iter_values()731 .flatten()732 .filter_map(|(id, amount)| {733 if id == *s.as_sub() {734 Some(amount)735 } else {736 None737 }738 })739 .sum()740 },741 )742 }743744 pub fn cross_id_pending_unstake_per_block(745 staker: T::CrossAccountId,746 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {747 let mut unsorted_res = vec![];748 PendingUnstake::<T>::iter().for_each(|(block, pendings)| {749 pendings.into_iter().for_each(|(id, amount)| {750 if id == *staker.as_sub() {751 unsorted_res.push((block, amount));752 };753 })754 });755756 unsorted_res.sort_by_key(|(block, _)| *block);757 unsorted_res758 }759}