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::{vec::Vec, iter::Sum, borrow::ToOwned};40use sp_core::H160;41use codec::EncodeLike;42use pallet_balances::BalanceLock;43pub use types::*;444546use up_data_structs::CollectionId;4748use frame_support::{49 dispatch::{DispatchResult},50 traits::{51 Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,52 },53 ensure,54};5556use weights::WeightInfo;5758pub use pallet::*;59use pallet_evm::account::CrossAccountId;60use sp_runtime::{61 Perbill,62 traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion},63 ArithmeticError,64};6566type BalanceOf<T> =67 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;6869707172737475pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";7677#[frame_support::pallet]78pub mod pallet {79 use super::*;80 use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId};81 use frame_system::pallet_prelude::*;8283 #[pallet::config]84 pub trait Config: frame_system::Config + pallet_evm::account::Config {85 type Currency: ExtendedLockableCurrency<Self::AccountId>;8687 type CollectionHandler: CollectionHandler<88 AccountId = Self::AccountId,89 CollectionId = CollectionId,90 >;9192 type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;9394 type TreasuryAccountId: Get<Self::AccountId>;9596 97 #[pallet::constant]98 type PalletId: Get<PalletId>;99100 101 #[pallet::constant]102 type RecalculationInterval: Get<Self::BlockNumber>;103 104 #[pallet::constant]105 type PendingInterval: Get<Self::BlockNumber>;106107 108 #[pallet::constant]109 type Day: Get<Self::BlockNumber>; 110111 #[pallet::constant]112 type Nominal: Get<BalanceOf<Self>>;113114 #[pallet::constant]115 type IntervalIncome: Get<Perbill>;116117 118 type WeightInfo: WeightInfo;119120 121 type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;122123 124 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;125 }126127 #[pallet::pallet]128 #[pallet::generate_store(pub(super) trait Store)]129 pub struct Pallet<T>(_);130131 #[pallet::event]132 #[pallet::generate_deposit(fn deposit_event)]133 pub enum Event<T: Config> {134 StakingRecalculation(135 136 T::AccountId,137 138 BalanceOf<T>,139 140 BalanceOf<T>,141 ),142 }143144 #[pallet::error]145 pub enum Error<T> {146 147 AdminNotSet,148 149 NoPermission,150 151 NotSufficientFounds,152 153 InvalidArgument,154 }155156 #[pallet::storage]157 pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;158159 #[pallet::storage]160 pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;161162 163 #[pallet::storage]164 pub type Staked<T: Config> = StorageNMap<165 Key = (166 Key<Blake2_128Concat, T::AccountId>,167 Key<Twox64Concat, T::BlockNumber>,168 ),169 Value = (BalanceOf<T>, T::BlockNumber),170 QueryKind = ValueQuery,171 >;172173 174 #[pallet::storage]175 pub type PendingUnstake<T: Config> = StorageNMap<176 Key = (177 Key<Blake2_128Concat, T::AccountId>,178 Key<Twox64Concat, T::BlockNumber>,179 ),180 Value = BalanceOf<T>,181 QueryKind = ValueQuery,182 >;183184 185 #[pallet::storage]186 pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;187188 189 #[pallet::storage]190 #[pallet::getter(fn get_interest_block)]191 pub type NextInterestBlock<T: Config> =192 StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;193194 195 196 #[pallet::storage]197 #[pallet::getter(fn get_last_calculated_staker)]198 pub type LastCalcucaltedStaker<T: Config> =199 StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;200201 #[pallet::hooks]202 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {203 fn on_initialize(current_block: T::BlockNumber) -> Weight204 where205 <T as frame_system::Config>::BlockNumber: From<u32>,206 {207 let mut consumed_weight = 0;208 209 210 211 212213 PendingUnstake::<T>::iter()214 .filter_map(|((staker, block), amount)| {215 if block <= current_block {216 Some((staker, block, amount))217 } else {218 None219 }220 })221 .for_each(|(staker, block, amount)| {222 Self::unlock_balance_unchecked(&staker, amount); 223 <PendingUnstake<T>>::remove((staker, block));224 });225226 227 228 229 230 231232 233 234 235 236237 238 239 240 241 242 243 244 245 246 247 248 249250 251 252 253 254 255 consumed_weight256 }257 }258259 #[pallet::call]260 impl<T: Config> Pallet<T>261 where262 T::BlockNumber: From<u32>,263 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>264 {265 #[pallet::weight(T::WeightInfo::set_admin_address())]266 pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {267 ensure_root(origin)?;268 <Admin<T>>::set(Some(admin.as_sub().to_owned()));269270 Ok(())271 }272273 #[pallet::weight(T::WeightInfo::start_app_promotion())]274 pub fn start_app_promotion(275 origin: OriginFor<T>,276 promotion_start_relay_block: Option<T::BlockNumber>,277 ) -> DispatchResult278 where279 <T as frame_system::Config>::BlockNumber: From<u32>,280 {281 ensure_root(origin)?;282283 284 if <StartBlock<T>>::get() == 0u32.into() {285 let start_block = promotion_start_relay_block286 .unwrap_or(T::RelayBlockNumberProvider::current_block_number());287288 289 <StartBlock<T>>::set(start_block);290291 <NextInterestBlock<T>>::set(start_block + T::RecalculationInterval::get());292 }293294 Ok(())295 }296297 #[pallet::weight(T::WeightInfo::stop_app_promotion())]298 pub fn stop_app_promotion(origin: OriginFor<T>) -> DispatchResult299 where300 <T as frame_system::Config>::BlockNumber: From<u32>,301 {302 ensure_root(origin)?;303304 if <StartBlock<T>>::get() != 0u32.into() {305 <StartBlock<T>>::set(T::BlockNumber::default());306 <NextInterestBlock<T>>::set(T::BlockNumber::default());307 }308309 Ok(())310 }311312 #[pallet::weight(T::WeightInfo::stake())]313 pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {314 let staker_id = ensure_signed(staker)?;315 316317 ensure!(amount >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(), ArithmeticError::Underflow);318319 let balance =320 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);321322 ensure!(balance >= amount, ArithmeticError::Underflow);323324 <<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(325 &staker_id,326 amount,327 WithdrawReasons::all(),328 balance - amount,329 )?;330331 Self::add_lock_balance(&staker_id, amount)?;332333 let block_number = T::RelayBlockNumberProvider::current_block_number();334 let recalc_block = (block_number / T::RecalculationInterval::get() + 2u32.into())335 * T::RecalculationInterval::get();336337 <Staked<T>>::insert((&staker_id, block_number), {338 let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));339 balance_and_recalc_block.0 = balance_and_recalc_block340 .0341 .checked_add(&amount)342 .ok_or(ArithmeticError::Overflow)?;343 balance_and_recalc_block.1 = recalc_block;344 balance_and_recalc_block345 });346347 <TotalStaked<T>>::set(348 <TotalStaked<T>>::get()349 .checked_add(&amount)350 .ok_or(ArithmeticError::Overflow)?,351 );352353 Ok(())354 }355356 #[pallet::weight(T::WeightInfo::unstake())]357 pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {358 let staker_id = ensure_signed(staker)?;359360 let mut total_stakes = 0u64;361362 let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))363 .map(|(_, (amount, _))| {364 *&mut total_stakes += 1;365 amount366 })367 .sum();368 369 let block =370 T::RelayBlockNumberProvider::current_block_number() + T::PendingInterval::get();371 <PendingUnstake<T>>::insert(372 (&staker_id, block),373 <PendingUnstake<T>>::get((&staker_id, block))374 .checked_add(&total_staked)375 .ok_or(ArithmeticError::Overflow)?,376 );377 378 TotalStaked::<T>::set(TotalStaked::<T>::get().checked_sub(&total_staked).ok_or(ArithmeticError::Underflow)?); 379380 Ok(None.into())381382 383384 385386 387 388 389390 391392 393 394 395 396 397398 399 400 401 402 403 404 405 406407 408409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426427 428 429 430 431 432 433 434 435 436437 438 }439440 #[pallet::weight(T::WeightInfo::sponsor_collection())]441 pub fn sponsor_collection(442 admin: OriginFor<T>,443 collection_id: CollectionId,444 ) -> DispatchResult {445 let admin_id = ensure_signed(admin)?;446 ensure!(447 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,448 Error::<T>::NoPermission449 );450451 T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)452 }453 #[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]454 pub fn stop_sponsoring_collection(455 admin: OriginFor<T>,456 collection_id: CollectionId,457 ) -> DispatchResult {458 let admin_id = ensure_signed(admin)?;459460 ensure!(461 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,462 Error::<T>::NoPermission463 );464465 ensure!(466 T::CollectionHandler::get_sponsor(collection_id)?467 .ok_or(<Error<T>>::InvalidArgument)?468 == Self::account_id(),469 <Error<T>>::NoPermission470 );471 T::CollectionHandler::remove_collection_sponsor(collection_id)472 }473474 #[pallet::weight(T::WeightInfo::sponsor_contract())]475 pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {476 let admin_id = ensure_signed(admin)?;477478 ensure!(479 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,480 Error::<T>::NoPermission481 );482483 T::ContractHandler::set_sponsor(484 T::CrossAccountId::from_sub(Self::account_id()),485 contract_id,486 )487 }488489 #[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]490 pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {491 let admin_id = ensure_signed(admin)?;492493 ensure!(494 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,495 Error::<T>::NoPermission496 );497498 ensure!(499 T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?500 == T::CrossAccountId::from_sub(Self::account_id()),501 <Error<T>>::NoPermission502 );503 T::ContractHandler::remove_contract_sponsor(contract_id)504 }505506 #[pallet::weight(0)]507 pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {508 let admin_id = ensure_signed(admin)?;509510 ensure!(511 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,512 Error::<T>::NoPermission513 );514 515 let raw_key = Staked::<T>::hashed_key_for((admin_id, T::BlockNumber::default()));516 517 let key_iterator = Staked::<T>::iter_keys_from(raw_key).skip(1).into_iter();518 519 match Self::get_last_calculated_staker() {520 Some(last_staker) => {},521 None => {}522 };523524 Ok(())525 }526 }527}528529impl<T: Config> Pallet<T> {530 pub fn account_id() -> T::AccountId {531 T::PalletId::get().into_account_truncating()532 }533534 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {535 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();536 locked_balance -= amount;537 Self::set_lock_unchecked(staker, locked_balance);538 }539540 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {541 Self::get_locked_balance(staker)542 .map_or(<BalanceOf<T>>::default(), |l| l.amount)543 .checked_add(&amount)544 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))545 .ok_or(ArithmeticError::Overflow.into())546 }547548 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {549 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(550 LOCK_IDENTIFIER,551 staker,552 amount,553 WithdrawReasons::all(),554 )555 }556557 pub fn get_locked_balance(558 staker: impl EncodeLike<T::AccountId>,559 ) -> Option<BalanceLock<BalanceOf<T>>> {560 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)561 .into_iter()562 .find(|l| l.id == LOCK_IDENTIFIER)563 }564565 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {566 let staked = Staked::<T>::iter_prefix((staker,))567 .into_iter()568 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {569 acc + amount570 });571 if staked != <BalanceOf<T>>::default() {572 Some(staked)573 } else {574 None575 }576 }577578 pub fn total_staked_by_id_per_block(579 staker: impl EncodeLike<T::AccountId>,580 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {581 let mut staked = Staked::<T>::iter_prefix((staker,))582 .into_iter()583 .map(|(block, (amount, _))| (block, amount))584 .collect::<Vec<_>>();585 staked.sort_by_key(|(block, _)| *block);586 if !staked.is_empty() {587 Some(staked)588 } else {589 None590 }591 }592593 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {594 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {595 Self::total_staked_by_id(s.as_sub())596 })597 598 }599600 pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {601 Self::get_locked_balance(staker.as_sub())602 .map(|l| l.amount)603 .unwrap_or_default()604 }605606 pub fn cross_id_total_staked_per_block(607 staker: T::CrossAccountId,608 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {609 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()610 }611612 fn recalculate_stake(613 staker: &T::AccountId,614 block: T::BlockNumber,615 base: BalanceOf<T>,616 income_acc: &mut BalanceOf<T>,617 ) {618 let income = Self::calculate_income(base);619 620 621 622 623 624 625 626 627 628 629 630 }631632 fn calculate_income<I>(base: I) -> I633 where634 I: EncodeLike<BalanceOf<T>> + Balance,635 {636 T::IntervalIncome::get() * base637 }638}639640impl<T: Config> Pallet<T>641where642 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,643{644 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {645 staker.map_or(PendingUnstake::<T>::iter_values().sum(), |s| {646 PendingUnstake::<T>::iter_prefix_values((s.as_sub(),)).sum()647 })648 }649650 pub fn cross_id_pending_unstake_per_block(651 staker: T::CrossAccountId,652 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {653 let mut unsorted_res = PendingUnstake::<T>::iter_prefix((staker.as_sub(),))654 .into_iter()655 .collect::<Vec<_>>();656 unsorted_res.sort_by_key(|(block, _)| *block);657 unsorted_res658 }659}