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 block_pending = PendingUnstake::<T>::take(current_block_number);230 let counter = block_pending.len() as u32;231 232233 if !block_pending.is_empty() {234 block_pending.into_iter().for_each(|(staker, amount)| {235 <T::Currency as ReservableCurrency<T::AccountId>>::unreserve(&staker, amount);236 });237 }238239 T::WeightInfo::on_initialize(counter)240 }241 }242243 #[pallet::call]244 impl<T: Config> Pallet<T>245 where246 T::BlockNumber: From<u32> + Into<u32>,247 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,248 {249 #[pallet::weight(T::WeightInfo::set_admin_address())]250 pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {251 ensure_root(origin)?;252253 <Admin<T>>::set(Some(admin.as_sub().to_owned()));254255 Self::deposit_event(Event::SetAdmin(admin.as_sub().to_owned()));256257 Ok(())258 }259260 #[pallet::weight(T::WeightInfo::stake())]261 pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {262 let staker_id = ensure_signed(staker)?;263264 ensure!(265 StakesPerAccount::<T>::get(&staker_id) < 10,266 Error::<T>::NoPermission267 );268269 ensure!(270 amount >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(),271 ArithmeticError::Underflow272 );273274 let balance =275 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);276277 278279 <<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(280 &staker_id,281 amount,282 WithdrawReasons::all(),283 balance - amount,284 )?;285286 Self::add_lock_balance(&staker_id, amount)?;287288 let block_number = T::RelayBlockNumberProvider::current_block_number();289290 let recalculate_after_interval: T::BlockNumber =291 if block_number % T::RecalculationInterval::get() == 0u32.into() {292 1u32.into()293 } else {294 2u32.into()295 };296297 let recalc_block = (block_number / T::RecalculationInterval::get()298 + recalculate_after_interval)299 * T::RecalculationInterval::get();300301 <Staked<T>>::insert((&staker_id, block_number), {302 let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));303 balance_and_recalc_block.0 = balance_and_recalc_block304 .0305 .checked_add(&amount)306 .ok_or(ArithmeticError::Overflow)?;307 balance_and_recalc_block.1 = recalc_block;308 balance_and_recalc_block309 });310311 <TotalStaked<T>>::set(312 <TotalStaked<T>>::get()313 .checked_add(&amount)314 .ok_or(ArithmeticError::Overflow)?,315 );316317 StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);318319 Self::deposit_event(Event::Stake(staker_id, amount));320321 Ok(())322 }323324 #[pallet::weight(T::WeightInfo::unstake())]325 pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {326 let staker_id = ensure_signed(staker)?;327 let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();328 let mut pendings = <PendingUnstake<T>>::get(block);329330 ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);331332 let mut total_stakes = 0u64;333334 let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))335 .map(|(_, (amount, _))| {336 total_stakes += 1;337 amount338 })339 .sum();340341 if total_staked.is_zero() {342 return Ok(None.into()); 343 }344345 pendings346 .try_push((staker_id.clone(), total_staked))347 .map_err(|_| Error::<T>::PendingForBlockOverflow)?;348349 <PendingUnstake<T>>::insert(block, pendings);350351 Self::unlock_balance_unchecked(&staker_id, total_staked);352353 <T::Currency as ReservableCurrency<T::AccountId>>::reserve(&staker_id, total_staked)?;354355 TotalStaked::<T>::set(356 TotalStaked::<T>::get()357 .checked_sub(&total_staked)358 .ok_or(ArithmeticError::Underflow)?,359 );360361 StakesPerAccount::<T>::remove(&staker_id);362363 Self::deposit_event(Event::Unstake(staker_id, total_staked));364365 Ok(None.into())366 }367368 #[pallet::weight(T::WeightInfo::sponsor_collection())]369 pub fn sponsor_collection(370 admin: OriginFor<T>,371 collection_id: CollectionId,372 ) -> DispatchResult {373 let admin_id = ensure_signed(admin)?;374 ensure!(375 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,376 Error::<T>::NoPermission377 );378379 T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)380 }381 #[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]382 pub fn stop_sponsoring_collection(383 admin: OriginFor<T>,384 collection_id: CollectionId,385 ) -> DispatchResult {386 let admin_id = ensure_signed(admin)?;387388 ensure!(389 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,390 Error::<T>::NoPermission391 );392393 ensure!(394 T::CollectionHandler::sponsor(collection_id)?.ok_or(<Error<T>>::InvalidArgument)?395 == Self::account_id(),396 <Error<T>>::NoPermission397 );398 T::CollectionHandler::remove_collection_sponsor(collection_id)399 }400401 #[pallet::weight(T::WeightInfo::sponsor_contract())]402 pub fn sponsor_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {403 let admin_id = ensure_signed(admin)?;404405 ensure!(406 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,407 Error::<T>::NoPermission408 );409410 T::ContractHandler::set_sponsor(411 T::CrossAccountId::from_sub(Self::account_id()),412 contract_id,413 )414 }415416 #[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]417 pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {418 let admin_id = ensure_signed(admin)?;419420 ensure!(421 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,422 Error::<T>::NoPermission423 );424425 ensure!(426 T::ContractHandler::sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?427 == T::CrossAccountId::from_sub(Self::account_id()),428 <Error<T>>::NoPermission429 );430 T::ContractHandler::remove_contract_sponsor(contract_id)431 }432433 #[pallet::weight(T::WeightInfo::payout_stakers(stakers_number.unwrap_or(20) as u32))]434 pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {435 let admin_id = ensure_signed(admin)?;436437 ensure!(438 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,439 Error::<T>::NoPermission440 );441442 let current_recalc_block =443 Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());444 let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();445446 let mut storage_iterator = Self::get_next_calculated_key()447 .map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));448449 NextCalculatedRecord::<T>::set(None);450451 452 453 454 455 456457 458 459 460 461 462 463 464 465 466 467 468 469 470 471472 473 474 475 476477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499500 501 502 503 504 505506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523524 525 526 527 528 529 530531 532 533 534 535 536537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567568 {569 let mut stakers_number = stakers_number.unwrap_or(20);570 let last_id = RefCell::new(None);571 let income_acc = RefCell::new(BalanceOf::<T>::default());572 let amount_acc = RefCell::new(BalanceOf::<T>::default());573574 let flush_stake = || -> DispatchResult {575 if let Some(last_id) = &*last_id.borrow() {576 if !income_acc.borrow().is_zero() {577 <T::Currency as Currency<T::AccountId>>::transfer(578 &T::TreasuryAccountId::get(),579 last_id,580 *income_acc.borrow(),581 ExistenceRequirement::KeepAlive,582 )583 .and_then(|_| {584 Self::add_lock_balance(last_id, *income_acc.borrow())?;585 <TotalStaked<T>>::try_mutate(|staked| {586 staked587 .checked_add(&*income_acc.borrow())588 .ok_or(ArithmeticError::Overflow.into())589 })590 })?;591592 Self::deposit_event(Event::StakingRecalculation(593 last_id.clone(),594 *amount_acc.borrow(),595 *income_acc.borrow(),596 ));597 }598599 *income_acc.borrow_mut() = BalanceOf::<T>::default();600 *amount_acc.borrow_mut() = BalanceOf::<T>::default();601 }602 Ok(())603 };604605 while let Some((606 (current_id, staked_block),607 (amount, next_recalc_block_for_stake),608 )) = storage_iterator.next()609 {610 if stakers_number == 0 {611 NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));612 break;613 }614 if last_id.borrow().as_ref() != Some(¤t_id) {615 flush_stake()?;616 *last_id.borrow_mut() = Some(current_id.clone());617 stakers_number -= 1;618 };619 if current_recalc_block >= next_recalc_block_for_stake {620 *amount_acc.borrow_mut() += amount;621 Self::recalculate_and_insert_stake(622 ¤t_id,623 staked_block,624 next_recalc_block,625 amount,626 ((current_recalc_block - next_recalc_block_for_stake)627 / T::RecalculationInterval::get())628 .into() + 1,629 &mut *income_acc.borrow_mut(),630 );631 }632 }633 flush_stake()?;634 }635636 Ok(())637 }638 }639}640641impl<T: Config> Pallet<T> {642 pub fn account_id() -> T::AccountId {643 T::PalletId::get().into_account_truncating()644 }645646 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {647 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();648 locked_balance -= amount;649 Self::set_lock_unchecked(staker, locked_balance);650 }651652 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {653 Self::get_locked_balance(staker)654 .map_or(<BalanceOf<T>>::default(), |l| l.amount)655 .checked_add(&amount)656 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))657 .ok_or(ArithmeticError::Overflow.into())658 }659660 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {661 if amount.is_zero() {662 <T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);663 } else {664 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(665 LOCK_IDENTIFIER,666 staker,667 amount,668 WithdrawReasons::all(),669 )670 }671 }672673 pub fn get_locked_balance(674 staker: impl EncodeLike<T::AccountId>,675 ) -> Option<BalanceLock<BalanceOf<T>>> {676 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)677 .into_iter()678 .find(|l| l.id == LOCK_IDENTIFIER)679 }680681 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {682 let staked = Staked::<T>::iter_prefix((staker,))683 .into_iter()684 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {685 acc + amount686 });687 if staked != <BalanceOf<T>>::default() {688 Some(staked)689 } else {690 None691 }692 }693694 pub fn total_staked_by_id_per_block(695 staker: impl EncodeLike<T::AccountId>,696 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {697 let mut staked = Staked::<T>::iter_prefix((staker,))698 .into_iter()699 .map(|(block, (amount, _))| (block, amount))700 .collect::<Vec<_>>();701 staked.sort_by_key(|(block, _)| *block);702 if !staked.is_empty() {703 Some(staked)704 } else {705 None706 }707 }708709 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {710 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {711 Self::total_staked_by_id(s.as_sub())712 })713 714 }715716 pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {717 Self::get_locked_balance(staker.as_sub())718 .map(|l| l.amount)719 .unwrap_or_default()720 }721722 pub fn cross_id_total_staked_per_block(723 staker: T::CrossAccountId,724 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {725 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()726 }727728 fn recalculate_and_insert_stake(729 staker: &T::AccountId,730 staked_block: T::BlockNumber,731 next_recalc_block: T::BlockNumber,732 base: BalanceOf<T>,733 iters: u32,734 income_acc: &mut BalanceOf<T>,735 ) {736 let income = Self::calculate_income(base, iters);737738 base.checked_add(&income).map(|res| {739 <Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));740 *income_acc += income;741 });742 }743744 fn calculate_income<I>(base: I, iters: u32) -> I745 where746 I: EncodeLike<BalanceOf<T>> + Balance,747 {748 let mut income = base;749750 (0..iters).for_each(|_| income += T::IntervalIncome::get() * income);751752 income - base753 }754755 fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {756 (current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()757 }758759 760 761 762763 fn get_next_calculated_key() -> Option<Vec<u8>> {764 Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))765 }766}767768impl<T: Config> Pallet<T>769where770 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,771{772 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {773 staker.map_or(774 PendingUnstake::<T>::iter_values()775 .flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))776 .sum(),777 |s| {778 PendingUnstake::<T>::iter_values()779 .flatten()780 .filter_map(|(id, amount)| {781 if id == *s.as_sub() {782 Some(amount)783 } else {784 None785 }786 })787 .sum()788 },789 )790 }791792 pub fn cross_id_pending_unstake_per_block(793 staker: T::CrossAccountId,794 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {795 let mut unsorted_res = vec![];796 PendingUnstake::<T>::iter().for_each(|(block, pendings)| {797 pendings.into_iter().for_each(|(id, amount)| {798 if id == *staker.as_sub() {799 unsorted_res.push((block, amount));800 };801 })802 });803804 unsorted_res.sort_by_key(|(block, _)| *block);805 unsorted_res806 }807}