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};45use sp_core::H160;46use codec::EncodeLike;47use pallet_balances::BalanceLock;48pub use types::*;495051use up_data_structs::CollectionId;5253use frame_support::{54 dispatch::{DispatchResult},55 traits::{56 Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement,57 },58 ensure,59};6061use weights::WeightInfo;6263pub use pallet::*;64use pallet_evm::account::CrossAccountId;65use sp_runtime::{66 Perbill,67 traits::{BlockNumberProvider, CheckedAdd, CheckedSub, AccountIdConversion, Zero},68 ArithmeticError,69};7071pub const LOCK_IDENTIFIER: [u8; 8] = *b"appstake";72const PENDING_LIMIT_PER_BLOCK: u32 = 3;7374type BalanceOf<T> =75 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;7677787980818283#[frame_support::pallet]84pub mod pallet {85 use super::*;86 use frame_support::{87 Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId,88 traits::ReservableCurrency,89 };90 use frame_system::pallet_prelude::*;9192 #[pallet::config]93 pub trait Config: frame_system::Config + pallet_evm::account::Config {94 type Currency: ExtendedLockableCurrency<Self::AccountId>95 + ReservableCurrency<Self::AccountId>;9697 type CollectionHandler: CollectionHandler<98 AccountId = Self::AccountId,99 CollectionId = CollectionId,100 >;101102 type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;103104 type TreasuryAccountId: Get<Self::AccountId>;105106 107 #[pallet::constant]108 type PalletId: Get<PalletId>;109110 111 #[pallet::constant]112 type RecalculationInterval: Get<Self::BlockNumber>;113 114 #[pallet::constant]115 type PendingInterval: Get<Self::BlockNumber>;116117 118 #[pallet::constant]119 type Day: Get<Self::BlockNumber>; 120121 #[pallet::constant]122 type Nominal: Get<BalanceOf<Self>>;123124 #[pallet::constant]125 type IntervalIncome: Get<Perbill>;126127 128 type WeightInfo: WeightInfo;129130 131 type RelayBlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;132133 134 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;135 }136137 #[pallet::pallet]138 #[pallet::generate_store(pub(super) trait Store)]139 pub struct Pallet<T>(_);140141 #[pallet::event]142 #[pallet::generate_deposit(fn deposit_event)]143 pub enum Event<T: Config> {144 StakingRecalculation(145 146 T::AccountId,147 148 BalanceOf<T>,149 150 BalanceOf<T>,151 ),152 }153154 #[pallet::error]155 pub enum Error<T> {156 157 AdminNotSet,158 159 NoPermission,160 161 NotSufficientFounds,162 PendingForBlockOverflow,163 164 InvalidArgument,165 }166167 #[pallet::storage]168 pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;169170 #[pallet::storage]171 pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;172173 174 #[pallet::storage]175 pub type Staked<T: Config> = StorageNMap<176 Key = (177 Key<Blake2_128Concat, T::AccountId>,178 Key<Twox64Concat, T::BlockNumber>,179 ),180 Value = (BalanceOf<T>, T::BlockNumber),181 QueryKind = ValueQuery,182 >;183 184 #[pallet::storage]185 pub type StakesPerAccount<T: Config> =186 StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;187188 189 190 191 192 193 194 195 196 197 198 #[pallet::storage]199 pub type PendingUnstake<T: Config> = StorageMap<200 _,201 Twox64Concat,202 T::BlockNumber,203 BoundedVec<(T::AccountId, BalanceOf<T>), ConstU32<PENDING_LIMIT_PER_BLOCK>>,204 ValueQuery,205 >;206207 208 #[pallet::storage]209 pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;210211 212 213 214 215 216217 218 219 #[pallet::storage]220 #[pallet::getter(fn get_next_calculated_record)]221 pub type NextCalculatedRecord<T: Config> =222 StorageValue<Value = (T::AccountId, T::BlockNumber), QueryKind = OptionQuery>;223224 #[pallet::hooks]225 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {226 fn on_initialize(current_block_number: T::BlockNumber) -> Weight227 where228 <T as frame_system::Config>::BlockNumber: From<u32>,229 {230 let mut consumed_weight = 0;231 let mut add_weight = |reads, writes, weight| {232 consumed_weight += T::DbWeight::get().reads_writes(reads, writes);233 consumed_weight += weight;234 };235236 let block_pending = PendingUnstake::<T>::take(current_block_number);237238 add_weight(0, 1, 0);239240 if !block_pending.is_empty() {241 block_pending.into_iter().for_each(|(staker, amount)| {242 <T::Currency as ReservableCurrency<T::AccountId>>::unreserve(&staker, amount);243 });244 }245246 consumed_weight247 }248 }249250 #[pallet::call]251 impl<T: Config> Pallet<T>252 where253 T::BlockNumber: From<u32> + Into<u32>,254 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,255 {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 265 266 267 268 269 270 271 272 273274 275 276 277 278279 280 281282 283 284285 286 287288 289 290 291 292 293 294295 296 297 298 299300 301 302303 #[pallet::weight(T::WeightInfo::stake())]304 pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {305 let staker_id = ensure_signed(staker)?;306307 ensure!(308 StakesPerAccount::<T>::get(&staker_id) < 10,309 Error::<T>::NoPermission310 );311312 ensure!(313 amount >= Into::<BalanceOf<T>>::into(100u128) * T::Nominal::get(),314 ArithmeticError::Underflow315 );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 <TotalStaked<T>>::set(346 <TotalStaked<T>>::get()347 .checked_add(&amount)348 .ok_or(ArithmeticError::Overflow)?,349 );350351 StakesPerAccount::<T>::mutate(&staker_id, |stakes| *stakes += 1);352 Ok(())353 }354355 #[pallet::weight(T::WeightInfo::unstake())]356 pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {357 let staker_id = ensure_signed(staker)?;358 let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();359 let mut pendings = <PendingUnstake<T>>::get(block);360361 ensure!(pendings.is_full(), Error::<T>::PendingForBlockOverflow);362363 let mut total_stakes = 0u64;364365 let total_staked: BalanceOf<T> = Staked::<T>::drain_prefix((&staker_id,))366 .map(|(_, (amount, _))| {367 total_stakes += 1;368 amount369 })370 .sum();371372 if total_staked.is_zero() {373 return Ok(None.into());374 }375376 pendings377 .try_push((staker_id.clone(), total_staked))378 .map_err(|_| Error::<T>::PendingForBlockOverflow)?;379380 <PendingUnstake<T>>::insert(block, pendings);381382 Self::unlock_balance_unchecked(&staker_id, total_staked);383384 <T::Currency as ReservableCurrency<T::AccountId>>::reserve(&staker_id, total_staked)?;385386 TotalStaked::<T>::set(387 TotalStaked::<T>::get()388 .checked_sub(&total_staked)389 .ok_or(ArithmeticError::Underflow)?,390 ); 391392 StakesPerAccount::<T>::remove(&staker_id);393394 Ok(None.into())395 }396397 #[pallet::weight(T::WeightInfo::sponsor_collection())]398 pub fn sponsor_collection(399 admin: OriginFor<T>,400 collection_id: CollectionId,401 ) -> DispatchResult {402 let admin_id = ensure_signed(admin)?;403 ensure!(404 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,405 Error::<T>::NoPermission406 );407408 T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)409 }410 #[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]411 pub fn stop_sponsoring_collection(412 admin: OriginFor<T>,413 collection_id: CollectionId,414 ) -> DispatchResult {415 let admin_id = ensure_signed(admin)?;416417 ensure!(418 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,419 Error::<T>::NoPermission420 );421422 ensure!(423 T::CollectionHandler::get_sponsor(collection_id)?424 .ok_or(<Error<T>>::InvalidArgument)?425 == Self::account_id(),426 <Error<T>>::NoPermission427 );428 T::CollectionHandler::remove_collection_sponsor(collection_id)429 }430431 #[pallet::weight(T::WeightInfo::sponsor_contract())]432 pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {433 let admin_id = ensure_signed(admin)?;434435 ensure!(436 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,437 Error::<T>::NoPermission438 );439440 T::ContractHandler::set_sponsor(441 T::CrossAccountId::from_sub(Self::account_id()),442 contract_id,443 )444 }445446 #[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]447 pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {448 let admin_id = ensure_signed(admin)?;449450 ensure!(451 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,452 Error::<T>::NoPermission453 );454455 ensure!(456 T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?457 == T::CrossAccountId::from_sub(Self::account_id()),458 <Error<T>>::NoPermission459 );460 T::ContractHandler::remove_contract_sponsor(contract_id)461 }462463 #[pallet::weight(0)]464 pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {465 let admin_id = ensure_signed(admin)?;466467 ensure!(468 admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,469 Error::<T>::NoPermission470 );471472 let current_recalc_block =473 Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());474 let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();475476 let mut storage_iterator = Self::get_next_calculated_key()477 .map_or(Staked::<T>::iter().skip(0), |key| {478 Staked::<T>::iter_from(key).skip(1)479 });480481 NextCalculatedRecord::<T>::set(None);482483 {484 let mut stakers_number = stakers_number.unwrap_or(20);485 let mut current_id = admin_id;486 let mut income_acc = BalanceOf::<T>::default();487488 while let Some(((id, staked_block), (amount, next_recalc_block_for_stake))) =489 storage_iterator.next()490 {491 if current_id != id {492 if income_acc != BalanceOf::<T>::default() {493 <T::Currency as Currency<T::AccountId>>::transfer(494 &T::TreasuryAccountId::get(),495 ¤t_id,496 income_acc,497 ExistenceRequirement::KeepAlive,498 )499 .and_then(|_| Self::add_lock_balance(¤t_id, income_acc))?;500501 Self::deposit_event(Event::StakingRecalculation(502 current_id, amount, income_acc,503 ));504 }505506 if stakers_number == 0 {507 NextCalculatedRecord::<T>::set(Some((id, staked_block)));508 break;509 }510 stakers_number -= 1;511 income_acc = BalanceOf::<T>::default();512 current_id = id;513 };514 if current_recalc_block >= next_recalc_block_for_stake {515 Self::recalculate_and_insert_stake(516 ¤t_id,517 staked_block,518 next_recalc_block,519 amount,520 ((current_recalc_block - next_recalc_block_for_stake)521 / T::RecalculationInterval::get())522 .into() + 1,523 &mut income_acc,524 );525 }526 }527 }528529 Ok(())530 }531 }532}533534impl<T: Config> Pallet<T> {535 pub fn account_id() -> T::AccountId {536 T::PalletId::get().into_account_truncating()537 }538539 fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {540 let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();541 locked_balance -= amount;542 Self::set_lock_unchecked(staker, locked_balance);543 }544545 fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {546 Self::get_locked_balance(staker)547 .map_or(<BalanceOf<T>>::default(), |l| l.amount)548 .checked_add(&amount)549 .map(|new_lock| Self::set_lock_unchecked(staker, new_lock))550 .ok_or(ArithmeticError::Overflow.into())551 }552553 fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {554 if amount.is_zero() {555 <T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);556 } else {557 <T::Currency as LockableCurrency<T::AccountId>>::set_lock(558 LOCK_IDENTIFIER,559 staker,560 amount,561 WithdrawReasons::all(),562 )563 }564 }565566 pub fn get_locked_balance(567 staker: impl EncodeLike<T::AccountId>,568 ) -> Option<BalanceLock<BalanceOf<T>>> {569 <T::Currency as ExtendedLockableCurrency<T::AccountId>>::locks(staker)570 .into_iter()571 .find(|l| l.id == LOCK_IDENTIFIER)572 }573574 pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {575 let staked = Staked::<T>::iter_prefix((staker,))576 .into_iter()577 .fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {578 acc + amount579 });580 if staked != <BalanceOf<T>>::default() {581 Some(staked)582 } else {583 None584 }585 }586587 pub fn total_staked_by_id_per_block(588 staker: impl EncodeLike<T::AccountId>,589 ) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {590 let mut staked = Staked::<T>::iter_prefix((staker,))591 .into_iter()592 .map(|(block, (amount, _))| (block, amount))593 .collect::<Vec<_>>();594 staked.sort_by_key(|(block, _)| *block);595 if !staked.is_empty() {596 Some(staked)597 } else {598 None599 }600 }601602 pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {603 staker.map_or(Some(<TotalStaked<T>>::get()), |s| {604 Self::total_staked_by_id(s.as_sub())605 })606 607 }608609 pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {610 Self::get_locked_balance(staker.as_sub())611 .map(|l| l.amount)612 .unwrap_or_default()613 }614615 pub fn cross_id_total_staked_per_block(616 staker: T::CrossAccountId,617 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {618 Self::total_staked_by_id_per_block(staker.as_sub()).unwrap_or_default()619 }620621 fn recalculate_and_insert_stake(622 staker: &T::AccountId,623 staked_block: T::BlockNumber,624 next_recalc_block: T::BlockNumber,625 base: BalanceOf<T>,626 iters: u32,627 income_acc: &mut BalanceOf<T>,628 ) {629 let income = Self::calculate_income(base, iters);630631 base.checked_add(&income).map(|res| {632 <Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));633 *income_acc += income;634 });635 }636637 fn calculate_income<I>(base: I, iters: u32) -> I638 where639 I: EncodeLike<BalanceOf<T>> + Balance,640 {641 let mut income = base;642643 (0..iters).for_each(|_| income += T::IntervalIncome::get() * income);644645 income - base646 }647648 fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {649 (current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()650 }651652 653 654 655656 fn get_next_calculated_key() -> Option<Vec<u8>> {657 Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))658 }659}660661impl<T: Config> Pallet<T>662where663 <<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,664{665 pub fn cross_id_pending_unstake(staker: Option<T::CrossAccountId>) -> BalanceOf<T> {666 staker.map_or(667 PendingUnstake::<T>::iter_values()668 .flat_map(|pendings| pendings.into_iter().map(|(_, amount)| amount))669 .sum(),670 |s| {671 PendingUnstake::<T>::iter_values()672 .flatten()673 .filter_map(|(id, amount)| {674 if id == *s.as_sub() {675 Some(amount)676 } else {677 None678 }679 })680 .sum()681 },682 )683 }684685 pub fn cross_id_pending_unstake_per_block(686 staker: T::CrossAccountId,687 ) -> Vec<(T::BlockNumber, BalanceOf<T>)> {688 let mut unsorted_res = vec![];689 PendingUnstake::<T>::iter().for_each(|(block, pendings)| {690 pendings.into_iter().for_each(|(id, amount)| {691 if id == *staker.as_sub() {692 unsorted_res.push((block, amount));693 };694 })695 });696697 unsorted_res.sort_by_key(|(block, _)| *block);698 unsorted_res699 }700}