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 NotSufficientFounds,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::all(),289 balance - amount,290 )?;291292 Self::add_lock_balance(&staker_id, amount)?;293294 let block_number = T::RelayBlockNumberProvider::current_block_number();295 let recalc_block = (block_number / T::RecalculationInterval::get() + 2u32.into())296 * T::RecalculationInterval::get();297298 <Staked<T>>::insert((&staker_id, block_number), {299 let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));300 balance_and_recalc_block.0 = balance_and_recalc_block301 .0302 .checked_add(&amount)303 .ok_or(ArithmeticError::Overflow)?;304 balance_and_recalc_block.1 = recalc_block;305 balance_and_recalc_block306 });307308 <TotalStaked<T>>::set(309 <TotalStaked<T>>::get()310 .checked_add(&amount)311 .ok_or(ArithmeticError::Overflow)?,312 );313314 StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);315316 Self::deposit_event(Event::Stake(staker_id, amount));317318 Ok(())319 }320321 #[pallet::weight(T::WeightInfo::unstake())]322 pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {323 let staker_id = ensure_signed(staker)?;324 let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();325 let mut pendings = <PendingUnstake<T>>::get(block);326327 ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);328329 let mut total_stakes = 0u64;330331 let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))332 .map(|(_, (amount, _))| {333 total_stakes += 1;334 amount335 })336 .sum();337338 if total_staked.is_zero() {339 return Ok(None.into()); 340 }341342 pendings343 .try_push((staker_id.clone(), total_staked))344 .map_err(|_| Error::<T>::PendingForBlockOverflow)?;345346 <PendingUnstake<T>>::insert(block, pendings);347348 Self::unlock_balance_unchecked(&staker_id, total_staked);349350 <T::Currency as ReservableCurrency<T::AccountId>>::reserve(&staker_id, total_staked)?;351352 TotalStaked::<T>::set(353 TotalStaked::<T>::get()354 .checked_sub(&total_staked)355 .ok_or(ArithmeticError::Underflow)?,356 );357358 StakesPerAccount::<T>::remove(&staker_id);359360 Self::deposit_event(Event::Unstake(staker_id, total_staked));361362 Ok(None.into())363 }364365 #[pallet::weight(T::WeightInfo::sponsor_collection())]366 pub fn sponsor_collection(367 admin: OriginFor<T>,368 collection_id: CollectionId,369 ) -> DispatchResult {370 let admin_id = ensure_signed(admin)?;371 ensure!(372 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,373 Error::<T>::NoPermission374 );375376 T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)377 }378 #[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]379 pub fn stop_sponsoring_collection(380 admin: OriginFor<T>,381 collection_id: CollectionId,382 ) -> DispatchResult {383 let admin_id = ensure_signed(admin)?;384385 ensure!(386 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,387 Error::<T>::NoPermission388 );389390 ensure!(391 T::CollectionHandler::get_sponsor(collection_id)?392 .ok_or(<Error<T>>::InvalidArgument)?393 == Self::account_id(),394 <Error<T>>::NoPermission395 );396 T::CollectionHandler::remove_collection_sponsor(collection_id)397 }398399 #[pallet::weight(T::WeightInfo::sponsor_contract())]400 pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {401 let admin_id = ensure_signed(admin)?;402403 ensure!(404 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,405 Error::<T>::NoPermission406 );407408 T::ContractHandler::set_sponsor(409 T::CrossAccountId::from_sub(Self::account_id()),410 contract_id,411 )412 }413414 #[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]415 pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> 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::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?425 == T::CrossAccountId::from_sub(Self::account_id()),426 <Error<T>>::NoPermission427 );428 T::ContractHandler::remove_contract_sponsor(contract_id)429 }430431 #[pallet::weight(0)]432 pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {433 let admin_id = ensure_signed(admin)?;434435 ensure!(436 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,437 Error::<T>::NoPermission438 );439440 let current_recalc_block =441 Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());442 let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();443444 let mut storage_iterator = Self::get_next_calculated_key()445 .map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));446447 NextCalculatedRecord::<T>::set(None);448449 450 451 452 453 454455 456 457 458 459 460 461 462 463 464 465 466 467 468 469470 471 472 473 474475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497498 {499 let mut stakers_number = stakers_number.unwrap_or(20);500 let last_id = RefCell::new(None);501 let income_acc = RefCell::new(BalanceOf::<T>::default());502 let amount_acc = RefCell::new(BalanceOf::<T>::default());503504 let flush_stake = || -> DispatchResult {505 if let Some(last_id) = &*last_id.borrow() {506 if !income_acc.borrow().is_zero() {507 <T::Currency as Currency<T::AccountId>>::transfer(508 &T::TreasuryAccountId::get(),509 last_id,510 *income_acc.borrow(),511 ExistenceRequirement::KeepAlive,512 )513 .and_then(|_| Self::add_lock_balance(last_id, *income_acc.borrow()))?;514515 Self::deposit_event(Event::StakingRecalculation(516 last_id.clone(),517 *amount_acc.borrow(),518 *income_acc.borrow(),519 ));520 }521522 *income_acc.borrow_mut() = BalanceOf::<T>::default();523 *amount_acc.borrow_mut() = BalanceOf::<T>::default();524 }525 Ok(())526 };527528 while let Some((529 (current_id, staked_block),530 (amount, next_recalc_block_for_stake),531 )) = storage_iterator.next()532 {533 if stakers_number == 0 {534 NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));535 break;536 }537 stakers_number -= 1;538 if last_id.borrow().as_ref() != Some(¤t_id) {539 flush_stake()?;540 };541 *last_id.borrow_mut() = Some(current_id.clone());542 if current_recalc_block >= next_recalc_block_for_stake {543 *amount_acc.borrow_mut() += amount;544 Self::recalculate_and_insert_stake(545 ¤t_id,546 staked_block,547 next_recalc_block,548 amount,549 ((current_recalc_block - next_recalc_block_for_stake)550 / T::RecalculationInterval::get())551 .into() + 1,552 &mut *income_acc.borrow_mut(),553 );554 }555 }556 flush_stake()?;557 }558559 Ok(())560 }561 }562}563564impl<T: Config> Pallet<T> {565 pub fn account_id() -> T::AccountId {566 T::PalletId::get().into_account_truncating()567 }568569 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {570 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();571 locked_balance -= amount;572 Self::set_lock_unchecked(staker, locked_balance);573 }574575 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {576 Self::get_locked_balance(staker)577 .map_or(<BalanceOf<T>>::default(), |l| l.amount)578 .checked_add(&amount)579 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))580 .ok_or(ArithmeticError::Overflow.into())581 }582583 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {584 if amount.is_zero() {585 <T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);586 } else {587 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(588 LOCK_IDENTIFIER,589 staker,590 amount,591 WithdrawReasons::all(),592 )593 }594 }595596 pub fn get_locked_balance(597 staker: impl EncodeLike<T::AccountId>,598 ) -> Option<BalanceLock<BalanceOf<T>>> {599 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)600 .into_iter()601 .find(|l| l.id == LOCK_IDENTIFIER)602 }603604 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {605 let staked = Staked::<T>::iter_prefix((staker,))606 .into_iter()607 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {608 acc + amount609 });610 if staked != <BalanceOf<T>>::default() {611 Some(staked)612 } else {613 None614 }615 }616617 pub fn total_staked_by_id_per_block(618 staker: impl EncodeLike<T::AccountId>,619 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {620 let mut staked = Staked::<T>::iter_prefix((staker,))621 .into_iter()622 .map(|(block, (amount, _))| (block, amount))623 .collect::<Vec<_>>();624 staked.sort_by_key(|(block, _)| *block);625 if !staked.is_empty() {626 Some(staked)627 } else {628 None629 }630 }631632 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {633 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {634 Self::total_staked_by_id(s.as_sub())635 })636 637 }638639 pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {640 Self::get_locked_balance(staker.as_sub())641 .map(|l| l.amount)642 .unwrap_or_default()643 }644645 pub fn cross_id_total_staked_per_block(646 staker: T::CrossAccountId,647 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {648 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()649 }650651 fn recalculate_and_insert_stake(652 staker: &T::AccountId,653 staked_block: T::BlockNumber,654 next_recalc_block: T::BlockNumber,655 base: BalanceOf<T>,656 iters: u32,657 income_acc: &mut BalanceOf<T>,658 ) {659 let income = Self::calculate_income(base, iters);660661 base.checked_add(&income).map(|res| {662 <Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));663 *income_acc += income;664 });665 }666667 fn calculate_income<I>(base: I, iters: u32) -> I668 where669 I: EncodeLike<BalanceOf<T>> + Balance,670 {671 let mut income = base;672673 (0..iters).for_each(|_| income += T::IntervalIncome::get() * income);674675 income - base676 }677678 fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {679 (current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()680 }681682 683 684 685686 fn get_next_calculated_key() -> Option<Vec<u8>> {687 Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))688 }689}690691impl<T: Config> Pallet<T>692where693 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,694{695 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {696 staker.map_or(697 PendingUnstake::<T>::iter_values()698 .flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))699 .sum(),700 |s| {701 PendingUnstake::<T>::iter_values()702 .flatten()703 .filter_map(|(id, amount)| {704 if id == *s.as_sub() {705 Some(amount)706 } else {707 None708 }709 })710 .sum()711 },712 )713 }714715 pub fn cross_id_pending_unstake_per_block(716 staker: T::CrossAccountId,717 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {718 let mut unsorted_res = vec![];719 PendingUnstake::<T>::iter().for_each(|(block, pendings)| {720 pendings.into_iter().for_each(|(id, amount)| {721 if id == *staker.as_sub() {722 unsorted_res.push((block, amount));723 };724 })725 });726727 unsorted_res.sort_by_key(|(block, _)| *block);728 unsorted_res729 }730}