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 balance284 .checked_sub(&amount)285 .ok_or(ArithmeticError::Underflow)?,286 )?;287288 Self::add_lock_balance(&staker_id, amount)?;289290 let block_number = T::RelayBlockNumberProvider::current_block_number();291292 let recalculate_after_interval: T::BlockNumber =293 if block_number % T::RecalculationInterval::get() == 0u32.into() {294 1u32.into()295 } else {296 2u32.into()297 };298299 let recalc_block = (block_number / T::RecalculationInterval::get()300 + recalculate_after_interval)301 * T::RecalculationInterval::get();302303 <Staked<T>>::insert((&staker_id, block_number), {304 let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));305 balance_and_recalc_block.0 = balance_and_recalc_block306 .0307 .checked_add(&amount)308 .ok_or(ArithmeticError::Overflow)?;309 balance_and_recalc_block.1 = recalc_block;310 balance_and_recalc_block311 });312313 <TotalStaked<T>>::set(314 <TotalStaked<T>>::get()315 .checked_add(&amount)316 .ok_or(ArithmeticError::Overflow)?,317 );318319 StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);320321 Self::deposit_event(Event::Stake(staker_id, amount));322323 Ok(())324 }325326 #[pallet::weight(T::WeightInfo::unstake())]327 pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {328 let staker_id = ensure_signed(staker)?;329 let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();330 let mut pendings = <PendingUnstake<T>>::get(block);331332 ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);333334 let mut total_stakes = 0u64;335336 let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))337 .map(|(_, (amount, _))| {338 total_stakes += 1;339 amount340 })341 .sum();342343 if total_staked.is_zero() {344 return Ok(None.into()); 345 }346347 pendings348 .try_push((staker_id.clone(), total_staked))349 .map_err(|_| Error::<T>::PendingForBlockOverflow)?;350351 <PendingUnstake<T>>::insert(block, pendings);352353 Self::unlock_balance_unchecked(&staker_id, total_staked);354355 <T::Currency as ReservableCurrency<T::AccountId>>::reserve(&staker_id, total_staked)?;356357 TotalStaked::<T>::set(358 TotalStaked::<T>::get()359 .checked_sub(&total_staked)360 .ok_or(ArithmeticError::Underflow)?,361 );362363 StakesPerAccount::<T>::remove(&staker_id);364365 Self::deposit_event(Event::Unstake(staker_id, total_staked));366367 Ok(None.into())368 }369370 #[pallet::weight(T::WeightInfo::sponsor_collection())]371 pub fn sponsor_collection(372 admin: OriginFor<T>,373 collection_id: CollectionId,374 ) -> DispatchResult {375 let admin_id = ensure_signed(admin)?;376 ensure!(377 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,378 Error::<T>::NoPermission379 );380381 T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)382 }383 #[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]384 pub fn stop_sponsoring_collection(385 admin: OriginFor<T>,386 collection_id: CollectionId,387 ) -> DispatchResult {388 let admin_id = ensure_signed(admin)?;389390 ensure!(391 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,392 Error::<T>::NoPermission393 );394395 ensure!(396 T::CollectionHandler::sponsor(collection_id)?.ok_or(<Error<T>>::InvalidArgument)?397 == Self::account_id(),398 <Error<T>>::NoPermission399 );400 T::CollectionHandler::remove_collection_sponsor(collection_id)401 }402403 #[pallet::weight(T::WeightInfo::sponsor_contract())]404 pub fn sponsor_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {405 let admin_id = ensure_signed(admin)?;406407 ensure!(408 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,409 Error::<T>::NoPermission410 );411412 T::ContractHandler::set_sponsor(413 T::CrossAccountId::from_sub(Self::account_id()),414 contract_id,415 )416 }417418 #[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]419 pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {420 let admin_id = ensure_signed(admin)?;421422 ensure!(423 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,424 Error::<T>::NoPermission425 );426427 ensure!(428 T::ContractHandler::sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?429 == T::CrossAccountId::from_sub(Self::account_id()),430 <Error<T>>::NoPermission431 );432 T::ContractHandler::remove_contract_sponsor(contract_id)433 }434435 #[pallet::weight(T::WeightInfo::payout_stakers(stakers_number.unwrap_or(20) as u32))]436 pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {437 let admin_id = ensure_signed(admin)?;438439 ensure!(440 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,441 Error::<T>::NoPermission442 );443444 let current_recalc_block =445 Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());446 let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();447448 let mut storage_iterator = Self::get_next_calculated_key()449 .map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));450451 NextCalculatedRecord::<T>::set(None);452453 454 455 456 457 458459 460 461 462 463 464 465 466 467 468 469 470 471 472 473474 475 476 477 478479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501502 503 504 505 506 507508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525526 527 528 529 530 531 532533 534 535 536 537 538539 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 567 568 569570 {571 let mut stakers_number = stakers_number.unwrap_or(20);572 let last_id = RefCell::new(None);573 let income_acc = RefCell::new(BalanceOf::<T>::default());574 let amount_acc = RefCell::new(BalanceOf::<T>::default());575576 let flush_stake = || -> DispatchResult {577 if let Some(last_id) = &*last_id.borrow() {578 if !income_acc.borrow().is_zero() {579 <T::Currency as Currency<T::AccountId>>::transfer(580 &T::TreasuryAccountId::get(),581 last_id,582 *income_acc.borrow(),583 ExistenceRequirement::KeepAlive,584 )585 .and_then(|_| {586 Self::add_lock_balance(last_id, *income_acc.borrow())?;587 <TotalStaked<T>>::try_mutate(|staked| {588 staked589 .checked_add(&*income_acc.borrow())590 .ok_or(ArithmeticError::Overflow.into())591 })592 })?;593594 Self::deposit_event(Event::StakingRecalculation(595 last_id.clone(),596 *amount_acc.borrow(),597 *income_acc.borrow(),598 ));599 }600601 *income_acc.borrow_mut() = BalanceOf::<T>::default();602 *amount_acc.borrow_mut() = BalanceOf::<T>::default();603 }604 Ok(())605 };606607 while let Some((608 (current_id, staked_block),609 (amount, next_recalc_block_for_stake),610 )) = storage_iterator.next()611 {612 if stakers_number == 0 {613 NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));614 break;615 }616 if last_id.borrow().as_ref() != Some(¤t_id) {617 flush_stake()?;618 *last_id.borrow_mut() = Some(current_id.clone());619 stakers_number -= 1;620 };621 if current_recalc_block >= next_recalc_block_for_stake {622 *amount_acc.borrow_mut() += amount;623 Self::recalculate_and_insert_stake(624 ¤t_id,625 staked_block,626 next_recalc_block,627 amount,628 ((current_recalc_block - next_recalc_block_for_stake)629 / T::RecalculationInterval::get())630 .into() + 1,631 &mut *income_acc.borrow_mut(),632 );633 }634 }635 flush_stake()?;636 }637638 Ok(())639 }640 }641}642643impl<T: Config> Pallet<T> {644 pub fn account_id() -> T::AccountId {645 T::PalletId::get().into_account_truncating()646 }647648 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {649 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();650 locked_balance -= amount;651 Self::set_lock_unchecked(staker, locked_balance);652 }653654 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {655 Self::get_locked_balance(staker)656 .map_or(<BalanceOf<T>>::default(), |l| l.amount)657 .checked_add(&amount)658 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))659 .ok_or(ArithmeticError::Overflow.into())660 }661662 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {663 if amount.is_zero() {664 <T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);665 } else {666 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(667 LOCK_IDENTIFIER,668 staker,669 amount,670 WithdrawReasons::all(),671 )672 }673 }674675 pub fn get_locked_balance(676 staker: impl EncodeLike<T::AccountId>,677 ) -> Option<BalanceLock<BalanceOf<T>>> {678 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)679 .into_iter()680 .find(|l| l.id == LOCK_IDENTIFIER)681 }682683 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {684 let staked = Staked::<T>::iter_prefix((staker,))685 .into_iter()686 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {687 acc + amount688 });689 if staked != <BalanceOf<T>>::default() {690 Some(staked)691 } else {692 None693 }694 }695696 pub fn total_staked_by_id_per_block(697 staker: impl EncodeLike<T::AccountId>,698 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {699 let mut staked = Staked::<T>::iter_prefix((staker,))700 .into_iter()701 .map(|(block, (amount, _))| (block, amount))702 .collect::<Vec<_>>();703 staked.sort_by_key(|(block, _)| *block);704 if !staked.is_empty() {705 Some(staked)706 } else {707 None708 }709 }710711 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {712 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {713 Self::total_staked_by_id(s.as_sub())714 })715 716 }717718 pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {719 Self::get_locked_balance(staker.as_sub())720 .map(|l| l.amount)721 .unwrap_or_default()722 }723724 pub fn cross_id_total_staked_per_block(725 staker: T::CrossAccountId,726 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {727 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()728 }729730 fn recalculate_and_insert_stake(731 staker: &T::AccountId,732 staked_block: T::BlockNumber,733 next_recalc_block: T::BlockNumber,734 base: BalanceOf<T>,735 iters: u32,736 income_acc: &mut BalanceOf<T>,737 ) {738 let income = Self::calculate_income(base, iters);739740 base.checked_add(&income).map(|res| {741 <Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));742 *income_acc += income;743 });744 }745746 fn calculate_income<I>(base: I, iters: u32) -> I747 where748 I: EncodeLike<BalanceOf<T>> + Balance,749 {750 let mut income = base;751752 (0..iters).for_each(|_| income += T::IntervalIncome::get() * income);753754 income - base755 }756757 fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {758 (current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()759 }760761 762 763 764765 fn get_next_calculated_key() -> Option<Vec<u8>> {766 Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))767 }768}769770impl<T: Config> Pallet<T>771where772 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,773{774 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {775 staker.map_or(776 PendingUnstake::<T>::iter_values()777 .flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))778 .sum(),779 |s| {780 PendingUnstake::<T>::iter_values()781 .flatten()782 .filter_map(|(id, amount)| {783 if id == *s.as_sub() {784 Some(amount)785 } else {786 None787 }788 })789 .sum()790 },791 )792 }793794 pub fn cross_id_pending_unstake_per_block(795 staker: T::CrossAccountId,796 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {797 let mut unsorted_res = vec![];798 PendingUnstake::<T>::iter().for_each(|(block, pendings)| {799 pendings.into_iter().for_each(|(id, amount)| {800 if id == *staker.as_sub() {801 unsorted_res.push((block, amount));802 };803 })804 });805806 unsorted_res.sort_by_key(|(block, _)| *block);807 unsorted_res808 }809}