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 #[pallet::constant]114 type Nominal: Get<BalanceOf<Self>>;115116 #[pallet::constant]117 type IntervalIncome: Get<Perbill>;118119 120 type WeightInfo: WeightInfo;121122 123 type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;124125 126 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;127 }128129 #[pallet::pallet]130 #[pallet::generate_store(pub(super) trait Store)]131 pub struct Pallet<T>(_);132133 #[pallet::event]134 #[pallet::generate_deposit(fn deposit_event)]135 pub enum Event<T: Config> {136 StakingRecalculation(137 138 T::AccountId,139 140 BalanceOf<T>,141 142 BalanceOf<T>,143 ),144 Stake(T::AccountId, BalanceOf<T>),145 Unstake(T::AccountId, BalanceOf<T>),146 SetAdmin(T::AccountId),147 }148149 #[pallet::error]150 pub enum Error<T> {151 152 AdminNotSet,153 154 NoPermission,155 156 NotSufficientFunds,157 PendingForBlockOverflow,158 159 SponsorNotSet,160 }161162 #[pallet::storage]163 pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;164165 #[pallet::storage]166 pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;167168 169 #[pallet::storage]170 pub type Staked<T: Config> = StorageNMap<171 Key = (172 Key<Blake2_128Concat, T::AccountId>,173 Key<Twox64Concat, T::BlockNumber>,174 ),175 Value = (BalanceOf<T>, T::BlockNumber),176 QueryKind = ValueQuery,177 >;178 179 #[pallet::storage]180 pub type StakesPerAccount<T: Config> =181 StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;182183 #[pallet::storage]184 pub type PendingUnstake<T: Config> = StorageMap<185 _,186 Twox64Concat,187 T::BlockNumber,188 BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,189 ValueQuery,190 >;191192 193 194 #[pallet::storage]195 #[pallet::getter(fn get_next_calculated_record)]196 pub type NextCalculatedRecord<T: Config> =197 StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;198199 #[pallet::hooks]200 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {201 fn on_initialize(current_block_number: T::BlockNumber) -> Weight202 where203 <T as frame_system::Config>::BlockNumber: From<u32>,204 {205 let block_pending = PendingUnstake::<T>::take(current_block_number);206 let counter = block_pending.len() as u32;207208 if !block_pending.is_empty() {209 block_pending.into_iter().for_each(|(staker, amount)| {210 <T::Currency as ReservableCurrency<T::AccountId>>::unreserve(&staker, amount);211 });212 }213214 T::WeightInfo::on_initialize(counter)215 }216 }217218 #[pallet::call]219 impl<T: Config> Pallet<T>220 where221 T::BlockNumber: From<u32> + Into<u32>,222 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,223 {224 #[pallet::weight(T::WeightInfo::set_admin_address())]225 pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {226 ensure_root(origin)?;227228 <Admin<T>>::set(Some(admin.as_sub().to_owned()));229230 Self::deposit_event(Event::SetAdmin(admin.as_sub().to_owned()));231232 Ok(())233 }234235 #[pallet::weight(T::WeightInfo::stake())]236 pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {237 let staker_id = ensure_signed(staker)?;238239 ensure!(240 StakesPerAccount::<T>::get(&staker_id) < 10,241 Error::<T>::NoPermission242 );243244 ensure!(245 amount >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(),246 ArithmeticError::Underflow247 );248249 let balance =250 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);251252 253254 <<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(255 &staker_id,256 amount,257 WithdrawReasons::all(),258 balance259 .checked_sub(&amount)260 .ok_or(ArithmeticError::Underflow)?,261 )?;262263 Self::add_lock_balance(&staker_id, amount)?;264265 let block_number = T::RelayBlockNumberProvider::current_block_number();266267 let recalculate_after_interval: T::BlockNumber =268 if block_number % T::RecalculationInterval::get() == 0u32.into() {269 1u32.into()270 } else {271 2u32.into()272 };273274 let recalc_block = (block_number / T::RecalculationInterval::get()275 + recalculate_after_interval)276 * T::RecalculationInterval::get();277278 <Staked<T>>::insert((&staker_id, block_number), {279 let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));280 balance_and_recalc_block.0 = balance_and_recalc_block281 .0282 .checked_add(&amount)283 .ok_or(ArithmeticError::Overflow)?;284 balance_and_recalc_block.1 = recalc_block;285 balance_and_recalc_block286 });287288 <TotalStaked<T>>::set(289 <TotalStaked<T>>::get()290 .checked_add(&amount)291 .ok_or(ArithmeticError::Overflow)?,292 );293294 StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);295296 Self::deposit_event(Event::Stake(staker_id, amount));297298 Ok(())299 }300301 #[pallet::weight(T::WeightInfo::unstake())]302 pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {303 let staker_id = ensure_signed(staker)?;304 let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();305 let mut pendings = <PendingUnstake<T>>::get(block);306307 ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);308309 let mut total_stakes = 0u64;310311 let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))312 .map(|(_, (amount, _))| {313 total_stakes += 1;314 amount315 })316 .sum();317318 if total_staked.is_zero() {319 return Ok(None.into()); 320 }321322 pendings323 .try_push((staker_id.clone(), total_staked))324 .map_err(|_| Error::<T>::PendingForBlockOverflow)?;325326 <PendingUnstake<T>>::insert(block, pendings);327328 Self::unlock_balance_unchecked(&staker_id, total_staked);329330 <T::Currency as ReservableCurrency<T::AccountId>>::reserve(&staker_id, total_staked)?;331332 TotalStaked::<T>::set(333 TotalStaked::<T>::get()334 .checked_sub(&total_staked)335 .ok_or(ArithmeticError::Underflow)?,336 );337338 StakesPerAccount::<T>::remove(&staker_id);339340 Self::deposit_event(Event::Unstake(staker_id, total_staked));341342 Ok(None.into())343 }344345 #[pallet::weight(T::WeightInfo::sponsor_collection())]346 pub fn sponsor_collection(347 admin: OriginFor<T>,348 collection_id: CollectionId,349 ) -> DispatchResult {350 let admin_id = ensure_signed(admin)?;351 ensure!(352 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,353 Error::<T>::NoPermission354 );355356 T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)357 }358 #[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]359 pub fn stop_sponsoring_collection(360 admin: OriginFor<T>,361 collection_id: CollectionId,362 ) -> DispatchResult {363 let admin_id = ensure_signed(admin)?;364365 ensure!(366 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,367 Error::<T>::NoPermission368 );369370 ensure!(371 T::CollectionHandler::sponsor(collection_id)?.ok_or(<Error<T>>::SponsorNotSet)?372 == Self::account_id(),373 <Error<T>>::NoPermission374 );375 T::CollectionHandler::remove_collection_sponsor(collection_id)376 }377378 #[pallet::weight(T::WeightInfo::sponsor_contract())]379 pub fn sponsor_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {380 let admin_id = ensure_signed(admin)?;381382 ensure!(383 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,384 Error::<T>::NoPermission385 );386387 T::ContractHandler::set_sponsor(388 T::CrossAccountId::from_sub(Self::account_id()),389 contract_id,390 )391 }392393 #[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]394 pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {395 let admin_id = ensure_signed(admin)?;396397 ensure!(398 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,399 Error::<T>::NoPermission400 );401402 ensure!(403 T::ContractHandler::sponsor(contract_id)?.ok_or(<Error<T>>::SponsorNotSet)?404 == T::CrossAccountId::from_sub(Self::account_id()),405 <Error<T>>::NoPermission406 );407 T::ContractHandler::remove_contract_sponsor(contract_id)408 }409410 #[pallet::weight(T::WeightInfo::payout_stakers(stakers_number.unwrap_or(20) as u32))]411 pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {412 let admin_id = ensure_signed(admin)?;413414 ensure!(415 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,416 Error::<T>::NoPermission417 );418419 let current_recalc_block =420 Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());421 let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();422423 let mut storage_iterator = Self::get_next_calculated_key()424 .map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));425426 NextCalculatedRecord::<T>::set(None);427428 429 430 431 432 433434 435 436 437 438 439 440 441 442 443 444 445 446 447 448449 450 451 452 453454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476477 478 479 480 481 482483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500501 502 503 504 505 506 507508 509 510 511 512 513514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544545 {546 let mut stakers_number = stakers_number.unwrap_or(20);547 let last_id = RefCell::new(None);548 let income_acc = RefCell::new(BalanceOf::<T>::default());549 let amount_acc = RefCell::new(BalanceOf::<T>::default());550551 let flush_stake = || -> DispatchResult {552 if let Some(last_id) = &*last_id.borrow() {553 if !income_acc.borrow().is_zero() {554 <T::Currency as Currency<T::AccountId>>::transfer(555 &T::TreasuryAccountId::get(),556 last_id,557 *income_acc.borrow(),558 ExistenceRequirement::KeepAlive,559 )560 .and_then(|_| {561 Self::add_lock_balance(last_id, *income_acc.borrow())?;562 <TotalStaked<T>>::try_mutate(|staked| {563 staked564 .checked_add(&*income_acc.borrow())565 .ok_or(ArithmeticError::Overflow.into())566 })567 })?;568569 Self::deposit_event(Event::StakingRecalculation(570 last_id.clone(),571 *amount_acc.borrow(),572 *income_acc.borrow(),573 ));574 }575576 *income_acc.borrow_mut() = BalanceOf::<T>::default();577 *amount_acc.borrow_mut() = BalanceOf::<T>::default();578 }579 Ok(())580 };581582 while let Some((583 (current_id, staked_block),584 (amount, next_recalc_block_for_stake),585 )) = storage_iterator.next()586 {587 if stakers_number == 0 {588 NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));589 break;590 }591 if last_id.borrow().as_ref() != Some(¤t_id) {592 flush_stake()?;593 *last_id.borrow_mut() = Some(current_id.clone());594 stakers_number -= 1;595 };596 if current_recalc_block >= next_recalc_block_for_stake {597 *amount_acc.borrow_mut() += amount;598 Self::recalculate_and_insert_stake(599 ¤t_id,600 staked_block,601 next_recalc_block,602 amount,603 ((current_recalc_block - next_recalc_block_for_stake)604 / T::RecalculationInterval::get())605 .into() + 1,606 &mut *income_acc.borrow_mut(),607 );608 }609 }610 flush_stake()?;611 }612613 Ok(())614 }615 }616}617618impl<T: Config> Pallet<T> {619 pub fn account_id() -> T::AccountId {620 T::PalletId::get().into_account_truncating()621 }622623 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {624 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();625 locked_balance -= amount;626 Self::set_lock_unchecked(staker, locked_balance);627 }628629 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {630 Self::get_locked_balance(staker)631 .map_or(<BalanceOf<T>>::default(), |l| l.amount)632 .checked_add(&amount)633 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))634 .ok_or(ArithmeticError::Overflow.into())635 }636637 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {638 if amount.is_zero() {639 <T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);640 } else {641 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(642 LOCK_IDENTIFIER,643 staker,644 amount,645 WithdrawReasons::all(),646 )647 }648 }649650 pub fn get_locked_balance(651 staker: impl EncodeLike<T::AccountId>,652 ) -> Option<BalanceLock<BalanceOf<T>>> {653 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)654 .into_iter()655 .find(|l| l.id == LOCK_IDENTIFIER)656 }657658 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {659 let staked = Staked::<T>::iter_prefix((staker,))660 .into_iter()661 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {662 acc + amount663 });664 if staked != <BalanceOf<T>>::default() {665 Some(staked)666 } else {667 None668 }669 }670671 pub fn total_staked_by_id_per_block(672 staker: impl EncodeLike<T::AccountId>,673 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {674 let mut staked = Staked::<T>::iter_prefix((staker,))675 .into_iter()676 .map(|(block, (amount, _))| (block, amount))677 .collect::<Vec<_>>();678 staked.sort_by_key(|(block, _)| *block);679 if !staked.is_empty() {680 Some(staked)681 } else {682 None683 }684 }685686 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {687 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {688 Self::total_staked_by_id(s.as_sub())689 })690 }691692 693 694 695 696 697698 pub fn cross_id_total_staked_per_block(699 staker: T::CrossAccountId,700 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {701 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()702 }703704 fn recalculate_and_insert_stake(705 staker: &T::AccountId,706 staked_block: T::BlockNumber,707 next_recalc_block: T::BlockNumber,708 base: BalanceOf<T>,709 iters: u32,710 income_acc: &mut BalanceOf<T>,711 ) {712 let income = Self::calculate_income(base, iters);713714 base.checked_add(&income).map(|res| {715 <Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));716 *income_acc += income;717 });718 }719720 fn calculate_income<I>(base: I, iters: u32) -> I721 where722 I: EncodeLike<BalanceOf<T>> + Balance,723 {724 let mut income = base;725726 (0..iters).for_each(|_| income += T::IntervalIncome::get() * income);727728 income - base729 }730731 fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {732 (current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()733 }734735 736 737 738739 fn get_next_calculated_key() -> Option<Vec<u8>> {740 Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))741 }742}743744impl<T: Config> Pallet<T>745where746 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,747{748 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {749 staker.map_or(750 PendingUnstake::<T>::iter_values()751 .flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))752 .sum(),753 |s| {754 PendingUnstake::<T>::iter_values()755 .flatten()756 .filter_map(|(id, amount)| {757 if id == *s.as_sub() {758 Some(amount)759 } else {760 None761 }762 })763 .sum()764 },765 )766 }767768 pub fn cross_id_pending_unstake_per_block(769 staker: T::CrossAccountId,770 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {771 let mut unsorted_res = vec![];772 PendingUnstake::<T>::iter().for_each(|(block, pendings)| {773 pendings.into_iter().for_each(|(id, amount)| {774 if id == *staker.as_sub() {775 unsorted_res.push((block, amount));776 };777 })778 });779780 unsorted_res.sort_by_key(|(block, _)| *block);781 unsorted_res782 }783}