From 99353211bdc0cef3b1a1f4af937c0a7068aad348 Mon Sep 17 00:00:00 2001 From: PraetorP Date: Mon, 13 Feb 2023 08:38:30 +0000 Subject: [PATCH] feat: added `unstake_partial` extrinsic --- --- a/pallets/app-promotion/src/lib.rs +++ b/pallets/app-promotion/src/lib.rs @@ -72,7 +72,7 @@ traits::{ Currency, Get, LockableCurrency, WithdrawReasons, tokens::Balance, ExistenceRequirement, }, - ensure, + ensure, BoundedVec, }; use weights::WeightInfo; @@ -494,7 +494,7 @@ /// free for further use. #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::unstake())] - pub fn unstake(staker: OriginFor) -> DispatchResultWithPostInfo { + pub fn unstake_all(staker: OriginFor) -> DispatchResultWithPostInfo { let staker_id = ensure_signed(staker)?; let config = >::get(); @@ -538,6 +538,58 @@ Ok(None::.into()) } + /// Unstakes all stakes. + /// Moves the sum of all stakes to the `reserved` state. + /// After the end of `PendingInterval` this sum becomes completely + /// free for further use. + #[pallet::call_index(8)] + #[pallet::weight(::WeightInfo::unstake())] + pub fn unstake_partial(staker: OriginFor, amount: BalanceOf) -> DispatchResult { + let staker_id = ensure_signed(staker)?; + let config = >::get(); + + // calculate block number where the sum would be free + let block = >::block_number() + config.pending_interval; + + let mut pendings = >::get(block); + + // checks that we can do unreserve stakes in the block + ensure!(!pendings.is_full(), Error::::PendingForBlockOverflow); + + Self::partial_unstake(&staker_id, amount, pendings, block)?; + + let mut total_stakes = 0u64; + + let total_staked: BalanceOf = Staked::::drain_prefix((&staker_id,)) + .map(|(_, (amount, _))| { + total_stakes += 1; + amount + }) + .sum(); + + if total_staked.is_zero() { + return Ok(()); // TO-DO + } + + // pendings + // .try_push((staker_id.clone(), total_staked)) + // .map_err(|_| Error::::PendingForBlockOverflow)?; + + // >::insert(block, pendings); + + // TotalStaked::::set( + // TotalStaked::::get() + // .checked_sub(&total_staked) + // .ok_or(ArithmeticError::Underflow)?, + // ); + + // StakesPerAccount::::remove(&staker_id); + + Self::deposit_event(Event::Unstake(staker_id, total_staked)); + + Ok(()) + } + /// Sets the pallet to be the sponsor for the collection. /// /// # Permissions @@ -809,6 +861,86 @@ T::PalletId::get().into_account_truncating() } + fn partial_unstake( + staker_id: &T::AccountId, + unstaked_balance: BalanceOf, + mut pendings: BoundedVec< + (T::AccountId, BalanceOf), + sp_core::ConstU32, + >, + pending_block: T::BlockNumber, + ) -> DispatchResult { + if unstaked_balance == Default::default() { + return Ok(()); + } + + let mut stakes = Staked::::iter_prefix((staker_id,)).collect::>(); + + let total_staked = stakes + .iter() + .fold(>::default(), |acc, (_, (balance, _))| { + acc + *balance + }); + + ensure!(total_staked >= unstaked_balance, ArithmeticError::Underflow); + + >::set( + >::get() + .checked_sub(&unstaked_balance) + .ok_or(ArithmeticError::Underflow)?, + ); + + stakes.sort_by_key(|(block, _)| *block); + + let mut acc_amount = unstaked_balance; + let mut will_deleted_stakes_count = 0u8; + + let changed_stakes = stakes + .into_iter() + .map_while(|(block, (balance_per_block, recalc_block))| { + if acc_amount == >::default() { + return None; + } + if acc_amount <= balance_per_block { + let res = (block, (balance_per_block - acc_amount, recalc_block)); + acc_amount = >::default(); + return Some(res); + } else { + acc_amount -= balance_per_block; + will_deleted_stakes_count += 1; + return Some((block, (>::default(), recalc_block))); + } + }) + .collect::>(); + + pendings + .try_push((staker_id.clone(), unstaked_balance)) + .map_err(|_| Error::::PendingForBlockOverflow)?; + + StakesPerAccount::::try_mutate(staker_id, |stakes| -> DispatchResult { + *stakes = stakes + .checked_div(will_deleted_stakes_count) + .ok_or(ArithmeticError::Underflow)?; + Ok(()) + })?; + + changed_stakes + .iter() + .for_each(|(staked_block, (current_stake_state, _))| { + if current_stake_state == &Default::default() { + >::remove((staker_id, staked_block)); + } else { + >::mutate((staker_id, staked_block), |(old_stake_state, _)| { + *old_stake_state = *current_stake_state + }); + } + }); + + >::insert(pending_block, pendings); + + Ok(()) + } + /// Adds the balance to locked by the pallet. /// /// - `staker`: staker account. -- gitstuff