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 {264 #[pallet::weight(T::WeightInfo::set_admin_address())]265 pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {266 ensure_root(origin)?;267 <Admin<T>>::set(Some(admin.as_sub().to_owned()));268269 Ok(())270 }271272 #[pallet::weight(T::WeightInfo::start_app_promotion())]273 pub fn start_app_promotion(274 origin: OriginFor<T>,275 promotion_start_relay_block: Option<T::BlockNumber>,276 ) -> DispatchResult277 where278 <T as frame_system::Config>::BlockNumber: From<u32>,279 {280 ensure_root(origin)?;281282 283 if <StartBlock<T>>::get() == 0u32.into() {284 let start_block = promotion_start_relay_block285 .unwrap_or(T::RelayBlockNumberProvider::current_block_number());286287 288 <StartBlock<T>>::set(start_block);289290 <NextInterestBlock<T>>::set(start_block + T::RecalculationInterval::get());291 }292293 Ok(())294 }295296 #[pallet::weight(T::WeightInfo::stop_app_promotion())]297 pub fn stop_app_promotion(origin: OriginFor<T>) -> DispatchResult298 where299 <T as frame_system::Config>::BlockNumber: From<u32>,300 {301 ensure_root(origin)?;302303 if <StartBlock<T>>::get() != 0u32.into() {304 <StartBlock<T>>::set(T::BlockNumber::default());305 <NextInterestBlock<T>>::set(T::BlockNumber::default());306 }307308 Ok(())309 }310311 #[pallet::weight(T::WeightInfo::stake())]312 pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {313 let staker_id = ensure_signed(staker)?;314315 ensure!(amount >= T::Nominal::get(), ArithmeticError::Underflow);316317 let balance =318 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);319320 ensure!(balance >= amount, ArithmeticError::Underflow);321322 <<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(323 &staker_id,324 amount,325 WithdrawReasons::all(),326 balance - amount,327 )?;328329 Self::add_lock_balance(&staker_id, amount)?;330331 let block_number = T::RelayBlockNumberProvider::current_block_number();332 let recalc_block = (block_number / T::RecalculationInterval::get() + 2u32.into())333 * T::RecalculationInterval::get();334335 <Staked<T>>::insert((&staker_id, block_number), {336 let mut balance_and_recalc_block = <Staked<T>>::get((&staker_id, block_number));337 balance_and_recalc_block.0 = balance_and_recalc_block338 .0339 .checked_add(&amount)340 .ok_or(ArithmeticError::Overflow)?;341 balance_and_recalc_block.1 = recalc_block;342 balance_and_recalc_block343 });344345 346 347 348 349 350351 Ok(())352 }353354 #[pallet::weight(T::WeightInfo::unstake())]355 pub fn unstake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {356 let staker_id = ensure_signed(staker)?;357358 let mut stakes = Staked::<T>::drain_prefix((&staker_id,));359360 361 362 363364 365366 367 368 369 370 371372 373 374 375 376 377 378 379 380381 382383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400401 402 403 404 405 406 407 408 409 410411 Ok(())412413 414415 416417 418 419 420421 422423 424 425 426 427 428429 430 431 432 433 434 435 436 437438 439440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457458 459 460 461 462 463 464 465 466 467468 469 }470471 #[pallet::weight(T::WeightInfo::sponsor_collection())]472 pub fn sponsor_collection(473 admin: OriginFor<T>,474 collection_id: CollectionId,475 ) -> DispatchResult {476 let admin_id = ensure_signed(admin)?;477 ensure!(478 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,479 Error::<T>::NoPermission480 );481482 T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)483 }484 #[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]485 pub fn stop_sponsoring_collection(486 admin: OriginFor<T>,487 collection_id: CollectionId,488 ) -> DispatchResult {489 let admin_id = ensure_signed(admin)?;490491 ensure!(492 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,493 Error::<T>::NoPermission494 );495496 ensure!(497 T::CollectionHandler::get_sponsor(collection_id)?498 .ok_or(<Error<T>>::InvalidArgument)?499 == Self::account_id(),500 <Error<T>>::NoPermission501 );502 T::CollectionHandler::remove_collection_sponsor(collection_id)503 }504505 #[pallet::weight(T::WeightInfo::sponsor_contract())]506 pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {507 let admin_id = ensure_signed(admin)?;508509 ensure!(510 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,511 Error::<T>::NoPermission512 );513514 T::ContractHandler::set_sponsor(515 T::CrossAccountId::from_sub(Self::account_id()),516 contract_id,517 )518 }519520 #[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]521 pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {522 let admin_id = ensure_signed(admin)?;523524 ensure!(525 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,526 Error::<T>::NoPermission527 );528529 ensure!(530 T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?531 == T::CrossAccountId::from_sub(Self::account_id()),532 <Error<T>>::NoPermission533 );534 T::ContractHandler::remove_contract_sponsor(contract_id)535 }536537 #[pallet::weight(0)]538 pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {539 let admin_id = ensure_signed(admin)?;540541 ensure!(542 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,543 Error::<T>::NoPermission544 );545546 Ok(())547 }548 }549}550551impl<T: Config> Pallet<T> {552 pub fn account_id() -> T::AccountId {553 T::PalletId::get().into_account_truncating()554 }555556 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {557 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();558 locked_balance -= amount;559 Self::set_lock_unchecked(staker, locked_balance);560 }561562 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {563 Self::get_locked_balance(staker)564 .map_or(<BalanceOf<T>>::default(), |l| l.amount)565 .checked_add(&amount)566 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))567 .ok_or(ArithmeticError::Overflow.into())568 }569570 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {571 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(572 LOCK_IDENTIFIER,573 staker,574 amount,575 WithdrawReasons::all(),576 )577 }578579 pub fn get_locked_balance(580 staker: impl EncodeLike<T::AccountId>,581 ) -> Option<BalanceLock<BalanceOf<T>>> {582 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)583 .into_iter()584 .find(|l| l.id == LOCK_IDENTIFIER)585 }586587 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {588 let staked = Staked::<T>::iter_prefix((staker,))589 .into_iter()590 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {591 acc + amount592 });593 if staked != <BalanceOf<T>>::default() {594 Some(staked)595 } else {596 None597 }598 }599600 pub fn total_staked_by_id_per_block(601 staker: impl EncodeLike<T::AccountId>,602 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {603 let mut staked = Staked::<T>::iter_prefix((staker,))604 .into_iter()605 .map(|(block, (amount, _))| (block, amount))606 .collect::<Vec<_>>();607 staked.sort_by_key(|(block, _)| *block);608 if !staked.is_empty() {609 Some(staked)610 } else {611 None612 }613 }614615 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {616 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {617 Self::total_staked_by_id(s.as_sub())618 })619 620 }621622 pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {623 Self::get_locked_balance(staker.as_sub())624 .map(|l| l.amount)625 .unwrap_or_default()626 }627628 pub fn cross_id_total_staked_per_block(629 staker: T::CrossAccountId,630 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {631 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()632 }633634 fn recalculate_stake(635 staker: &T::AccountId,636 block: T::BlockNumber,637 base: BalanceOf<T>,638 income_acc: &mut BalanceOf<T>,639 ) {640 let income = Self::calculate_income(base);641 642 643 644 645 646 647 648 649 650 651 652 }653654 fn calculate_income<I>(base: I) -> I655 where656 I: EncodeLike<BalanceOf<T>> + Balance,657 {658 T::IntervalIncome::get() * base659 }660}661662impl<T: Config> Pallet<T>663where664 <<T as pallet::Config>::Currency as Currency<T::AccountId>>::Balance: Sum,665{666 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {667 staker.map_or(PendingUnstake::<T>::iter_values().sum(), |s| {668 PendingUnstake::<T>::iter_prefix_values((s.as_sub(),)).sum()669 })670 }671672 pub fn cross_id_pending_unstake_per_block(673 staker: T::CrossAccountId,674 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {675 let mut unsorted_res = PendingUnstake::<T>::iter_prefix((staker.as_sub(),))676 .into_iter()677 .collect::<Vec<_>>();678 unsorted_res.sort_by_key(|(block, _)| *block);679 unsorted_res680 }681}