123456789101112131415161718192021222324252627282930#![cfg_attr(not(feature = "std"), no_std)]3132#[cfg(feature = "runtime-benchmarks")]33mod benchmarking;34pub mod types;3536#[cfg(test)]37mod tests;3839use sp_std::vec::Vec;40use codec::EncodeLike;41use pallet_balances::BalanceLock;42pub use types::ExtendedLockableCurrency;4344use frame_support::{45 dispatch::{DispatchResult},46 traits::{47 Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,48 },49 ensure,50};51pub use pallet::*;52use pallet_evm::account::CrossAccountId;53use sp_runtime::{54 Perbill,55 traits::{BlockNumberProvider, CheckedAdd, CheckedSub},56 ArithmeticError,57};5859type BalanceOf<T> =60 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;6162const SECONDS_TO_BLOCK: u32 = 6;63const DAY: u32 = 60 * 60 * 24 / SECONDS_TO_BLOCK;64const WEEK: u32 = 7 * DAY;65const TWO_WEEK: u32 = 2 * WEEK;66const YEAR: u32 = DAY * 365;6768pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";6970#[frame_support::pallet]71pub mod pallet {72 use super::*;73 use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};74 use frame_system::pallet_prelude::*;7576 #[pallet::config]77 pub trait Config: frame_system::Config + pallet_evm::account::Config {78 type Currency: ExtendedLockableCurrency<Self::AccountId>;7980 type TreasuryAccountId: Get<Self::AccountId>;8182 83 type BlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;8485 86 87 8889 90 91 }9293 #[pallet::pallet]94 #[pallet::generate_store(pub(super) trait Store)]95 pub struct Pallet<T>(_);9697 #[pallet::storage]98 pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;99100 #[pallet::storage]101 pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;102103 104 #[pallet::storage]105 pub type Staked<T: Config> = StorageNMap<106 Key = (107 Key<Blake2_128Concat, T::AccountId>,108 Key<Twox64Concat, T::BlockNumber>,109 ),110 Value = BalanceOf<T>,111 QueryKind = ValueQuery,112 >;113114 #[pallet::storage]115 pub type PendingUnstake<T: Config> = StorageNMap<116 Key = (117 Key<Blake2_128Concat, T::AccountId>,118 Key<Twox64Concat, T::BlockNumber>,119 ),120 Value = BalanceOf<T>,121 QueryKind = ValueQuery,122 >;123124 125 #[pallet::storage]126 pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;127128 129 #[pallet::storage]130 #[pallet::getter(fn get_interest_block)]131 pub type NextInterestBlock<T: Config> =132 StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;133134 #[pallet::hooks]135 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {136 fn on_initialize(current_block: T::BlockNumber) -> Weight137 where138 <T as frame_system::Config>::BlockNumber: From<u32>,139 {140 PendingUnstake::<T>::iter()141 .filter_map(|((staker, block), amount)| {142 if block <= current_block {143 Some((staker, block, amount))144 } else {145 None146 }147 })148 .for_each(|(staker, block, amount)| {149 Self::unlock_balance_unchecked(&staker, amount); 150 <PendingUnstake<T>>::remove((staker, block));151 });152153 let next_interest_block = Self::get_interest_block();154155 if next_interest_block != 0.into() && current_block >= next_interest_block {156 let mut acc = <BalanceOf<T>>::default();157 let mut weight: Weight = 0;158 NextInterestBlock::<T>::set(current_block + DAY.into());159 Staked::<T>::iter()160 .filter(|((_, block), _)| *block + DAY.into() <= current_block)161 .for_each(|((staker, block), amount)| {162 Self::recalculate_stake(&staker, block, amount, &mut acc);163 164 });165 <TotalStaked<T>>::get()166 .checked_add(&acc)167 .map(|res| <TotalStaked<T>>::set(res));168 };169 0170 }171 }172173 #[pallet::call]174 impl<T: Config> Pallet<T> {175 #[pallet::weight(0)]176 pub fn set_admin_address(origin: OriginFor<T>, admin: T::AccountId) -> DispatchResult {177 ensure_root(origin)?;178 <Admin<T>>::set(Some(admin));179180 Ok(())181 }182183 #[pallet::weight(0)]184 pub fn start_app_promotion(185 origin: OriginFor<T>,186 promotion_start_relay_block: T::BlockNumber,187 ) -> DispatchResult188 where189 <T as frame_system::Config>::BlockNumber: From<u32>,190 {191 ensure_root(origin)?;192193 194 if <StartBlock<T>>::get() == 0u32.into() {195 196 <StartBlock<T>>::set(promotion_start_relay_block);197198 <NextInterestBlock<T>>::set(promotion_start_relay_block + DAY.into());199 }200201 Ok(())202 }203204 #[pallet::weight(0)]205 pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {206 let staker_id = ensure_signed(staker)?;207208 let balance =209 <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);210211 ensure!(balance >= amount, ArithmeticError::Underflow);212213 Self::set_lock_unchecked(&staker_id, amount);214215 let block_number =216 <T::BlockNumberProvider as BlockNumberProvider>::current_block_number();217218 <Staked<T>>::insert(219 (&staker_id, block_number),220 <Staked<T>>::get((&staker_id, block_number))221 .checked_add(&amount)222 .ok_or(ArithmeticError::Overflow)?,223 );224225 <TotalStaked<T>>::set(226 <TotalStaked<T>>::get()227 .checked_add(&amount)228 .ok_or(ArithmeticError::Overflow)?,229 );230231 Ok(())232 }233234 #[pallet::weight(0)]235 pub fn unstake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {236 let staker_id = ensure_signed(staker)?;237238 let mut stakes = Staked::<T>::iter_prefix((&staker_id,)).collect::<Vec<_>>();239240 let total_staked = stakes241 .iter()242 .fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);243244 ensure!(total_staked >= amount, ArithmeticError::Underflow);245246 <TotalStaked<T>>::set(247 <TotalStaked<T>>::get()248 .checked_sub(&amount)249 .ok_or(ArithmeticError::Underflow)?,250 );251252 let block = <T::BlockNumberProvider>::current_block_number() + WEEK.into();253 <PendingUnstake<T>>::insert(254 (&staker_id, block),255 <PendingUnstake<T>>::get((&staker_id, block))256 .checked_add(&amount)257 .ok_or(ArithmeticError::Overflow)?,258 );259260 stakes.sort_by_key(|(block, _)| *block);261262 let mut acc_amount = amount;263 let new_state = stakes264 .into_iter()265 .map_while(|(block, balance_per_block)| {266 if acc_amount == <BalanceOf<T>>::default() {267 return None;268 }269 if acc_amount <= balance_per_block {270 let res = (block, balance_per_block - acc_amount, acc_amount);271 acc_amount = <BalanceOf<T>>::default();272 return Some(res);273 } else {274 acc_amount -= balance_per_block;275 return Some((block, <BalanceOf<T>>::default(), acc_amount));276 }277 })278 .collect::<Vec<_>>();279280 new_state281 .into_iter()282 .for_each(|(block, to_staked, _to_pending)| {283 if to_staked == <BalanceOf<T>>::default() {284 <Staked<T>>::remove((&staker_id, block));285 } else {286 <Staked<T>>::insert((&staker_id, block), to_staked);287 }288 });289290 Ok(())291 }292 }293}294295impl<T: Config> Pallet<T> {296 297 298299 300301 302303 304305 306 307 308 309 310 311312 313 314 315 316 317318 319 320321 322 323324 325 326 327328 329330 331 332 333 334 335336 337 338 339 340 341 342 343344 345346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363364 365 366 367 368 369 370 371 372 373374 375 376377 pub fn sponsor_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {378 Ok(())379 }380381 pub fn stop_sponsorign_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {382 Ok(())383 }384385 pub fn sponsor_conract(admin: T::AccountId, app_id: u32) -> DispatchResult {386 Ok(())387 }388389 pub fn stop_sponsorign_contract(admin: T::AccountId, app_id: u32) -> DispatchResult {390 Ok(())391 }392}393394impl<T: Config> Pallet<T> {395 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {396 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();397 locked_balance -= amount;398 Self::set_lock_unchecked(staker, locked_balance);399 }400401 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {402 Self::get_locked_balance(staker)403 .map(|l| l.amount)404 .and_then(|b| b.checked_add(&amount))405 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))406 .ok_or(ArithmeticError::Overflow.into())407 }408409 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {410 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(411 LOCK_IDENTIFIER,412 staker,413 amount,414 WithdrawReasons::all(),415 )416 }417418 pub fn get_locked_balance(419 staker: impl EncodeLike<T::AccountId>,420 ) -> Option<BalanceLock<BalanceOf<T>>> {421 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)422 .into_iter()423 .find(|l| l.id == LOCK_IDENTIFIER)424 }425426 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {427 let staked = Staked::<T>::iter_prefix((staker,))428 .into_iter()429 .fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + amount);430 if staked != <BalanceOf<T>>::default() {431 Some(staked)432 } else {433 None434 }435 }436437 pub fn total_staked_by_id_per_block(438 staker: impl EncodeLike<T::AccountId>,439 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {440 let staked = Staked::<T>::iter_prefix((staker,))441 .into_iter()442 .map(|(block, amount)| (block, amount))443 .collect::<Vec<_>>();444 if !staked.is_empty() {445 Some(staked)446 } else {447 None448 }449 }450451 pub fn cross_id_total_staked(staker: T::CrossAccountId) -> Option<BalanceOf<T>> {452 Self::total_staked_by_id(staker.as_sub())453 }454455 pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {456 Self::get_locked_balance(staker.as_sub())457 .map(|l| l.amount)458 .unwrap_or_default()459 }460461 pub fn cross_id_total_staked_per_block(462 staker: T::CrossAccountId,463 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {464 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()465 }466467 fn recalculate_stake(468 staker: &T::AccountId,469 block: T::BlockNumber,470 base: BalanceOf<T>,471 income_acc: &mut BalanceOf<T>,472 ) {473 let income = Self::calculate_income(base);474 base.checked_add(&income).map(|res| {475 <Staked<T>>::insert((staker, block), res);476 *income_acc += income;477 <T::Currency as Currency<T::AccountId>>::transfer(478 &T::TreasuryAccountId::get(),479 staker,480 income,481 ExistenceRequirement::KeepAlive,482 )483 .and_then(|_| Self::add_lock_balance(staker, income));484 });485 }486487 fn calculate_income<I>(base: I) -> I488 where489 I: EncodeLike<BalanceOf<T>> + Balance,490 {491 let day_rate = Perbill::from_rational(5u32, 1_0000);492 day_rate * base493 }494}