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 codec::EncodeLike;41use pallet_balances::BalanceLock;42pub use types::ExtendedLockableCurrency;434445use up_data_structs::CollectionId;4647use frame_support::{48 dispatch::{DispatchResult},49 traits::{50 Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,51 },52 ensure,53};5455use weights::WeightInfo;5657pub use pallet::*;58use pallet_evm::account::CrossAccountId;59use sp_runtime::{60 Perbill,61 traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion},62 ArithmeticError,63};6465type BalanceOf<T> =66 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;6768697071727374pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";7576#[frame_support::pallet]77pub mod pallet {78 use super::*;79 use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId};80 use frame_system::pallet_prelude::*;81 use types::CollectionHandler;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 TreasuryAccountId: Get<Self::AccountId>;9394 95 #[pallet::constant]96 type PalletId: Get<PalletId>;9798 99 #[pallet::constant]100 type RecalculationInterval: Get<Self::BlockNumber>;101 102 #[pallet::constant]103 type PendingInterval: Get<Self::BlockNumber>;104105 106 #[pallet::constant]107 type Day: Get<Self::BlockNumber>; 108109 #[pallet::constant]110 type Nominal: Get<BalanceOf<Self>>;111112 #[pallet::constant]113 type IntervalIncome: Get<Perbill>;114115 116 type WeightInfo: WeightInfo;117118 119 type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;120121 122 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;123124 125 126 127128 129 130 }131132 #[pallet::pallet]133 #[pallet::generate_store(pub(super) trait Store)]134 pub struct Pallet<T>(_);135136 #[pallet::event]137 #[pallet::generate_deposit(fn deposit_event)]138 pub enum Event<T: Config> {139 StakingRecalculation(140 141 BalanceOf<T>,142 143 BalanceOf<T>,144 ),145 }146147 #[pallet::error]148 pub enum Error<T> {149 AdminNotSet,150 151 NoPermission,152 153 NotSufficientFounds,154 InvalidArgument,155 AlreadySponsored,156 }157158 #[pallet::storage]159 pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;160161 #[pallet::storage]162 pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;163164 165 #[pallet::storage]166 pub type Staked<T: Config> = StorageNMap<167 Key = (168 Key<Blake2_128Concat, T::AccountId>,169 Key<Twox64Concat, T::BlockNumber>,170 ),171 Value = BalanceOf<T>,172 QueryKind = ValueQuery,173 >;174175 176 #[pallet::storage]177 pub type PendingUnstake<T: Config> = StorageNMap<178 Key = (179 Key<Blake2_128Concat, T::AccountId>,180 Key<Twox64Concat, T::BlockNumber>,181 ),182 Value = BalanceOf<T>,183 QueryKind = ValueQuery,184 >;185186 187 #[pallet::storage]188 pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;189190 191 #[pallet::storage]192 #[pallet::getter(fn get_interest_block)]193 pub type NextInterestBlock<T: Config> =194 StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;195196 #[pallet::hooks]197 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {198 fn on_initialize(current_block: T::BlockNumber) -> Weight199 where200 <T as frame_system::Config>::BlockNumber: From<u32>,201 {202 let mut consumed_weight = 0;203 let mut add_weight = |reads, writes, weight| {204 consumed_weight += T::DbWeight::get().reads_writes(reads, writes);205 consumed_weight += weight;206 };207208 PendingUnstake::<T>::iter()209 .filter_map(|((staker, block), amount)| {210 if block <= current_block {211 Some((staker, block, amount))212 } else {213 None214 }215 })216 .for_each(|(staker, block, amount)| {217 Self::unlock_balance_unchecked(&staker, amount); 218 <PendingUnstake<T>>::remove((staker, block));219 });220221 let next_interest_block = Self::get_interest_block();222 let current_relay_block = T::RelayBlockNumberProvider::current_block_number();223 if next_interest_block != 0.into() && current_relay_block >= next_interest_block {224 let mut acc = <BalanceOf<T>>::default();225 let mut base_acc = <BalanceOf<T>>::default();226227 NextInterestBlock::<T>::set(228 NextInterestBlock::<T>::get() + T::RecalculationInterval::get(),229 );230 add_weight(0, 1, 0);231232 Staked::<T>::iter()233 .filter(|((_, block), _)| {234 *block + T::RecalculationInterval::get() <= current_relay_block235 })236 .for_each(|((staker, block), amount)| {237 Self::recalculate_stake(&staker, block, amount, &mut acc);238 add_weight(0, 0, T::WeightInfo::recalculate_stake());239 base_acc += amount;240 });241 <TotalStaked<T>>::get()242 .checked_add(&acc)243 .map(|res| <TotalStaked<T>>::set(res));244245 Self::deposit_event(Event::StakingRecalculation(base_acc, acc));246 add_weight(0, 1, 0);247 } else {248 add_weight(1, 0, 0)249 };250 consumed_weight251 }252 }253254 #[pallet::call]255 impl<T: Config> Pallet<T> {256 #[pallet::weight(T::WeightInfo::set_admin_address())]257 pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {258 ensure_root(origin)?;259 <Admin<T>>::set(Some(admin.as_sub().to_owned()));260261 Ok(())262 }263264 #[pallet::weight(T::WeightInfo::start_app_promotion())]265 pub fn start_app_promotion(266 origin: OriginFor<T>,267 promotion_start_relay_block: Option<T::BlockNumber>,268 ) -> DispatchResult269 where270 <T as frame_system::Config>::BlockNumber: From<u32>,271 {272 ensure_root(origin)?;273274 275 if <StartBlock<T>>::get() == 0u32.into() {276 let start_block = promotion_start_relay_block277 .unwrap_or(T::RelayBlockNumberProvider::current_block_number());278279 280 <StartBlock<T>>::set(start_block);281282 <NextInterestBlock<T>>::set(start_block + T::RecalculationInterval::get());283 }284285 Ok(())286 }287288 #[pallet::weight(T::WeightInfo::stake())]289 pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {290 let staker_id = ensure_signed(staker)?;291292 ensure!(amount >= T::Nominal::get(), ArithmeticError::Underflow);293294 let balance =295 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);296297 ensure!(balance >= amount, ArithmeticError::Underflow);298299 <<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(300 &staker_id,301 amount,302 WithdrawReasons::all(),303 balance - amount,304 )?;305306 Self::add_lock_balance(&staker_id, amount)?;307308 let block_number = T::RelayBlockNumberProvider::current_block_number();309310 <Staked<T>>::insert(311 (&staker_id, block_number),312 <Staked<T>>::get((&staker_id, block_number))313 .checked_add(&amount)314 .ok_or(ArithmeticError::Overflow)?,315 );316317 <TotalStaked<T>>::set(318 <TotalStaked<T>>::get()319 .checked_add(&amount)320 .ok_or(ArithmeticError::Overflow)?,321 );322323 Ok(())324 }325326 #[pallet::weight(T::WeightInfo::unstake())]327 pub fn unstake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {328 let staker_id = ensure_signed(staker)?;329330 let mut stakes = Staked::<T>::iter_prefix((&staker_id,)).collect::<Vec<_>>();331332 let total_staked = stakes333 .iter()334 .fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);335336 ensure!(total_staked >= amount, ArithmeticError::Underflow);337338 <TotalStaked<T>>::set(339 <TotalStaked<T>>::get()340 .checked_sub(&amount)341 .ok_or(ArithmeticError::Underflow)?,342 );343344 let block = frame_system::Pallet::<T>::block_number() + T::PendingInterval::get();345 <PendingUnstake<T>>::insert(346 (&staker_id, block),347 <PendingUnstake<T>>::get((&staker_id, block))348 .checked_add(&amount)349 .ok_or(ArithmeticError::Overflow)?,350 );351352 stakes.sort_by_key(|(block, _)| *block);353354 let mut acc_amount = amount;355 let new_state = stakes356 .into_iter()357 .map_while(|(block, balance_per_block)| {358 if acc_amount == <BalanceOf<T>>::default() {359 return None;360 }361 if acc_amount <= balance_per_block {362 let res = (block, balance_per_block - acc_amount, acc_amount);363 acc_amount = <BalanceOf<T>>::default();364 return Some(res);365 } else {366 acc_amount -= balance_per_block;367 return Some((block, <BalanceOf<T>>::default(), acc_amount));368 }369 })370 .collect::<Vec<_>>();371372 new_state373 .into_iter()374 .for_each(|(block, to_staked, _to_pending)| {375 if to_staked == <BalanceOf<T>>::default() {376 <Staked<T>>::remove((&staker_id, block));377 } else {378 <Staked<T>>::insert((&staker_id, block), to_staked);379 }380 });381382 Ok(())383 }384385 #[pallet::weight(0)]386 pub fn sponsor_collection(387 admin: OriginFor<T>,388 collection_id: CollectionId,389 ) -> DispatchResult {390 let admin_id = ensure_signed(admin)?;391 ensure!(392 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,393 Error::<T>::NoPermission394 );395396 T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)397 }398 #[pallet::weight(0)]399 pub fn stop_sponsorign_collection(400 admin: OriginFor<T>,401 collection_id: CollectionId,402 ) -> 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 ensure!(411 T::CollectionHandler::get_sponsor(collection_id)?412 .ok_or(<Error<T>>::InvalidArgument)?413 == Self::account_id(),414 <Error<T>>::NoPermission415 );416 T::CollectionHandler::remove_collection_sponsor(collection_id)417 }418 }419}420421impl<T: Config> Pallet<T> {422 423 424425 426427 428429 430431 432 433 434 435 436 437438 439 440 441 442 443444 445 446447 448 449450 451 452 453454 455456 457 458 459 460 461462 463 464 465 466 467 468 469470 471472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489490 491 492 493 494 495 496 497 498 499500 501 502503 504 505 506507 508 509 510511 pub fn sponsor_conract(admin: T::AccountId, app_id: u32) -> DispatchResult {512 Ok(())513 }514515 pub fn stop_sponsorign_contract(admin: T::AccountId, app_id: u32) -> DispatchResult {516 Ok(())517 }518519 pub fn account_id() -> T::AccountId {520 T::PalletId::get().into_account_truncating()521 }522}523524impl<T: Config> Pallet<T> {525 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {526 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();527 locked_balance -= amount;528 Self::set_lock_unchecked(staker, locked_balance);529 }530531 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {532 Self::get_locked_balance(staker)533 .map_or(<BalanceOf<T>>::default(), |l| l.amount)534 .checked_add(&amount)535 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))536 .ok_or(ArithmeticError::Overflow.into())537 }538539 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {540 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(541 LOCK_IDENTIFIER,542 staker,543 amount,544 WithdrawReasons::all(),545 )546 }547548 pub fn get_locked_balance(549 staker: impl EncodeLike<T::AccountId>,550 ) -> Option<BalanceLock<BalanceOf<T>>> {551 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)552 .into_iter()553 .find(|l| l.id == LOCK_IDENTIFIER)554 }555556 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {557 let staked = Staked::<T>::iter_prefix((staker,))558 .into_iter()559 .fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + amount);560 if staked != <BalanceOf<T>>::default() {561 Some(staked)562 } else {563 None564 }565 }566567 pub fn total_staked_by_id_per_block(568 staker: impl EncodeLike<T::AccountId>,569 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {570 let mut staked = Staked::<T>::iter_prefix((staker,))571 .into_iter()572 .map(|(block, amount)| (block, amount))573 .collect::<Vec<_>>();574 staked.sort_by_key(|(block, _)| *block);575 if !staked.is_empty() {576 Some(staked)577 } else {578 None579 }580 }581582 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {583 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {584 Self::total_staked_by_id(s.as_sub())585 })586 587 }588589 pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {590 Self::get_locked_balance(staker.as_sub())591 .map(|l| l.amount)592 .unwrap_or_default()593 }594595 pub fn cross_id_total_staked_per_block(596 staker: T::CrossAccountId,597 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {598 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()599 }600601 fn recalculate_stake(602 staker: &T::AccountId,603 block: T::BlockNumber,604 base: BalanceOf<T>,605 income_acc: &mut BalanceOf<T>,606 ) {607 let income = Self::calculate_income(base);608 base.checked_add(&income).map(|res| {609 <Staked<T>>::insert((staker, block), res);610 *income_acc += income;611 <T::Currency as Currency<T::AccountId>>::transfer(612 &T::TreasuryAccountId::get(),613 staker,614 income,615 ExistenceRequirement::KeepAlive,616 )617 .and_then(|_| Self::add_lock_balance(staker, income));618 });619 }620621 fn calculate_income<I>(base: I) -> I622 where623 I: EncodeLike<BalanceOf<T>> + Balance,624 {625 T::IntervalIncome::get() * base626 }627}628629impl<T: Config> Pallet<T>630where631 <<T as pallet::Config>::Currency as Currency<T::AccountId>>::Balance: Sum,632{633 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {634 staker.map_or(PendingUnstake::<T>::iter_values().sum(), |s| {635 PendingUnstake::<T>::iter_prefix_values((s.as_sub(),)).sum()636 })637 }638639 pub fn cross_id_pending_unstake_per_block(640 staker: T::CrossAccountId,641 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {642 let mut unsorted_res = PendingUnstake::<T>::iter_prefix((staker.as_sub(),))643 .into_iter()644 .collect::<Vec<_>>();645 unsorted_res.sort_by_key(|(block, _)| *block);646 unsorted_res647 }648}