From da133e17611444f510982aa4073232b5f902caee Mon Sep 17 00:00:00 2001 From: PraetorP Date: Tue, 14 Feb 2023 12:41:00 +0000 Subject: [PATCH] refac(app-promo): impl for `unstake_all` & `unstake_partial` extrinsics Added benchmark for `unstake_partial` extrinsic --- --- a/Cargo.lock +++ b/Cargo.lock @@ -5782,7 +5782,7 @@ [[package]] name = "pallet-app-promotion" -version = "0.1.4" +version = "0.1.5" dependencies = [ "frame-benchmarking", "frame-support", --- a/pallets/app-promotion/CHANGELOG.md +++ b/pallets/app-promotion/CHANGELOG.md @@ -4,6 +4,12 @@ +## [0.1.5] - 2023-02-14 + +### Added + +- `unstake_partial` extrinsic. + ## [0.1.4] - 2023-01-31 ### Changed --- a/pallets/app-promotion/Cargo.toml +++ b/pallets/app-promotion/Cargo.toml @@ -9,7 +9,7 @@ license = 'GPLv3' name = 'pallet-app-promotion' repository = 'https://github.com/UniqueNetwork/unique-chain' -version = '0.1.4' +version = '0.1.5' [package.metadata.docs.rs] targets = ['x86_64-unknown-linux-gnu'] --- a/pallets/app-promotion/src/benchmarking.rs +++ b/pallets/app-promotion/src/benchmarking.rs @@ -65,7 +65,7 @@ let staker = account::("staker", index, SEED); ::Currency::make_free_balance_be(&staker, Perbill::from_rational(1u32, 2) * BalanceOf::::max_value()); PromototionPallet::::stake(RawOrigin::Signed(staker.clone()).into(), Into::>::into(100u128) * T::Nominal::get())?; - PromototionPallet::::unstake(RawOrigin::Signed(staker.clone()).into()).map_err(|e| e.error)?; + PromototionPallet::::unstake_all(RawOrigin::Signed(staker.clone()).into()).map_err(|e| e.error)?; Result::<(), sp_runtime::DispatchError>::Ok(()) })?; let block_number = >::current_block_number() + T::PendingInterval::get(); @@ -115,7 +115,7 @@ let _ = ::Currency::make_free_balance_be(&caller, Perbill::from_rational(1u32, 2) * BalanceOf::::max_value()); } : _(RawOrigin::Signed(caller.clone()), share * ::Currency::total_balance(&caller)) - unstake { + unstake_all { let caller = account::("caller", 0, SEED); let share = Perbill::from_rational(1u32, 20); let _ = ::Currency::make_free_balance_be(&caller, Perbill::from_rational(1u32, 2) * BalanceOf::::max_value()); @@ -130,6 +130,21 @@ } : _(RawOrigin::Signed(caller.clone())) + unstake_partial { + let caller = account::("caller", 0, SEED); + let share = Perbill::from_rational(1u32, 20); + let _ = ::Currency::make_free_balance_be(&caller, Perbill::from_rational(1u32, 2) * BalanceOf::::max_value()); + (1..11).map(|i| { + // used to change block number + >::set_block_number(i.into()); + T::RelayBlockNumberProvider::set_block_number((2*i).into()); + assert_eq!(>::block_number(), i.into()); + assert_eq!(T::RelayBlockNumberProvider::current_block_number(), (2*i).into()); + PromototionPallet::::stake(RawOrigin::Signed(caller.clone()).into(), Into::>::into(100u128) * T::Nominal::get()) + }).collect::, _>>()?; + + } : _(RawOrigin::Signed(caller.clone()), Into::>::into(1000u128) * T::Nominal::get()) + sponsor_collection { let pallet_admin = account::("admin", 0, SEED); PromototionPallet::::set_admin_address(RawOrigin::Root.into(), T::CrossAccountId::from_sub(pallet_admin.clone()))?; --- a/pallets/app-promotion/src/lib.rs +++ b/pallets/app-promotion/src/lib.rs @@ -208,6 +208,8 @@ SponsorNotSet, /// Errors caused by incorrect actions with a locked balance. IncorrectLockedBalanceOperation, + /// Errors caused by insufficient staked balance. + InsufficientStakedBalance, } /// Stores the total staked amount. @@ -489,65 +491,30 @@ } /// 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(2)] - #[pallet::weight(::WeightInfo::unstake())] - pub fn unstake_all(staker: OriginFor) -> DispatchResultWithPostInfo { + #[pallet::weight(::WeightInfo::unstake_all())] + pub fn unstake_all(staker: OriginFor) -> 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); - - 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(None::.into()); // 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(None::.into()) + Self::unstake_all_internal(staker_id) } - /// Unstakes all stakes. - /// Moves the sum of all stakes to the `reserved` state. + /// Unstakes the amount of balance for the staker. /// After the end of `PendingInterval` this sum becomes completely /// free for further use. + /// + /// # Arguments + /// + /// * `staker`: staker account. + /// * `amount`: amount of unstaked funds. #[pallet::call_index(8)] - #[pallet::weight(::WeightInfo::unstake())] + #[pallet::weight(::WeightInfo::unstake_partial())] pub fn unstake_partial(staker: OriginFor, amount: BalanceOf) -> DispatchResult { let staker_id = ensure_signed(staker)?; - Self::partial_unstake(&staker_id, amount) + Self::unstake_partial_internal(staker_id, amount) } /// Sets the pallet to be the sponsor for the collection. @@ -821,12 +788,18 @@ T::PalletId::get().into_account_truncating() } - fn partial_unstake(staker_id: &T::AccountId, unstaked_balance: BalanceOf) -> DispatchResult { - + /// Unstakes the balance for the staker. + /// + /// - `staker`: staker account. + /// - `amount`: amount of unstaked funds. + fn unstake_partial_internal( + staker_id: T::AccountId, + unstaked_balance: BalanceOf, + ) -> DispatchResult { if unstaked_balance == Default::default() { return Ok(()); } - + let config = >::get(); // calculate block number where the sum would be free @@ -837,7 +810,7 @@ // checks that we can do unreserve stakes in the block ensure!(!pendings.is_full(), Error::::PendingForBlockOverflow); - let mut stakes = Staked::::iter_prefix((staker_id,)).collect::>(); + let mut stakes = Staked::::iter_prefix((&staker_id,)).collect::>(); let total_staked = stakes .iter() @@ -845,7 +818,10 @@ acc + *balance }); - ensure!(total_staked >= unstaked_balance, ArithmeticError::Underflow); + ensure!( + total_staked >= unstaked_balance, + >::InsufficientStakedBalance + ); >::set( >::get() @@ -880,7 +856,7 @@ .try_push((staker_id.clone(), unstaked_balance)) .map_err(|_| Error::::PendingForBlockOverflow)?; - StakesPerAccount::::try_mutate(staker_id, |stakes| -> DispatchResult { + StakesPerAccount::::try_mutate(&staker_id, |stakes| -> DispatchResult { *stakes = stakes .checked_sub(will_deleted_stakes_count) .ok_or(ArithmeticError::Underflow)?; @@ -891,9 +867,9 @@ .iter() .for_each(|(staked_block, (current_stake_state, _))| { if current_stake_state == &Default::default() { - >::remove((staker_id, staked_block)); + >::remove((&staker_id, staked_block)); } else { - >::mutate((staker_id, staked_block), |(old_stake_state, _)| { + >::mutate((&staker_id, staked_block), |(old_stake_state, _)| { *old_stake_state = *current_stake_state }); } @@ -901,7 +877,7 @@ >::insert(unpending_block, pendings); - Self::deposit_event(Event::Unstake(staker_id.clone(), total_staked)); + Self::deposit_event(Event::Unstake(staker_id, total_staked)); Ok(()) } @@ -1102,4 +1078,47 @@ unsorted_res.sort_by_key(|(block, _)| *block); unsorted_res } + + fn unstake_all_internal(staker_id: T::AccountId) -> DispatchResult { + 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); + + 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(()); + } + + 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(()) + } } --- a/pallets/app-promotion/src/weights.rs +++ b/pallets/app-promotion/src/weights.rs @@ -3,7 +3,7 @@ //! Autogenerated weights for pallet_app_promotion //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-12-25, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-02-14, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 // Executed Command: @@ -38,7 +38,8 @@ fn set_admin_address() -> Weight; fn payout_stakers(b: u32, ) -> Weight; fn stake() -> Weight; - fn unstake() -> Weight; + fn unstake_all() -> Weight; + fn unstake_partial() -> Weight; fn sponsor_collection() -> Weight; fn stop_sponsoring_collection() -> Weight; fn sponsor_contract() -> Weight; @@ -49,18 +50,19 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: AppPromotion PendingUnstake (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn on_initialize(b: u32, ) -> Weight { - Weight::from_ref_time(3_079_948 as u64) - // Standard Error: 30_376 - .saturating_add(Weight::from_ref_time(6_343_630 as u64).saturating_mul(b as u64)) + Weight::from_ref_time(2_562_275 as u64) + // Standard Error: 21_950 + .saturating_add(Weight::from_ref_time(7_177_129 as u64).saturating_mul(b as u64)) .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(b as u64))) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(b as u64))) + .saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(b as u64))) + .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(b as u64))) } // Storage: AppPromotion Admin (r:0 w:1) fn set_admin_address() -> Weight { - Weight::from_ref_time(6_653_000 as u64) + Weight::from_ref_time(6_146_000 as u64) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: AppPromotion Admin (r:1 w:0) @@ -72,9 +74,9 @@ // Storage: Balances Locks (r:1 w:1) // Storage: AppPromotion TotalStaked (r:1 w:1) fn payout_stakers(b: u32, ) -> Weight { - Weight::from_ref_time(74_048_000 as u64) - // Standard Error: 33_223 - .saturating_add(Weight::from_ref_time(57_702_092 as u64).saturating_mul(b as u64)) + Weight::from_ref_time(64_035_000 as u64) + // Standard Error: 19_434 + .saturating_add(Weight::from_ref_time(47_251_111 as u64).saturating_mul(b as u64)) .saturating_add(T::DbWeight::get().reads(7 as u64)) .saturating_add(T::DbWeight::get().reads((12 as u64).saturating_mul(b as u64))) .saturating_add(T::DbWeight::get().writes(3 as u64)) @@ -88,47 +90,55 @@ // Storage: AppPromotion Staked (r:1 w:1) // Storage: AppPromotion TotalStaked (r:1 w:1) fn stake() -> Weight { - Weight::from_ref_time(20_314_000 as u64) + Weight::from_ref_time(18_078_000 as u64) .saturating_add(T::DbWeight::get().reads(7 as u64)) .saturating_add(T::DbWeight::get().writes(5 as u64)) } // Storage: Configuration AppPromomotionConfigurationOverride (r:1 w:0) // Storage: AppPromotion PendingUnstake (r:1 w:1) // Storage: AppPromotion Staked (r:11 w:10) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) // Storage: AppPromotion TotalStaked (r:1 w:1) // Storage: AppPromotion StakesPerAccount (r:0 w:1) - fn unstake() -> Weight { - Weight::from_ref_time(64_582_000 as u64) - .saturating_add(T::DbWeight::get().reads(16 as u64)) - .saturating_add(T::DbWeight::get().writes(15 as u64)) + fn unstake_all() -> Weight { + Weight::from_ref_time(45_038_000 as u64) + .saturating_add(T::DbWeight::get().reads(14 as u64)) + .saturating_add(T::DbWeight::get().writes(13 as u64)) + } + // Storage: Configuration AppPromomotionConfigurationOverride (r:1 w:0) + // Storage: AppPromotion PendingUnstake (r:1 w:1) + // Storage: AppPromotion Staked (r:11 w:10) + // Storage: AppPromotion TotalStaked (r:1 w:1) + // Storage: AppPromotion StakesPerAccount (r:1 w:1) + fn unstake_partial() -> Weight { + Weight::from_ref_time(48_863_000 as u64) + .saturating_add(T::DbWeight::get().reads(15 as u64)) + .saturating_add(T::DbWeight::get().writes(13 as u64)) } // Storage: AppPromotion Admin (r:1 w:0) // Storage: Common CollectionById (r:1 w:1) fn sponsor_collection() -> Weight { - Weight::from_ref_time(16_364_000 as u64) + Weight::from_ref_time(14_808_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: AppPromotion Admin (r:1 w:0) // Storage: Common CollectionById (r:1 w:1) fn stop_sponsoring_collection() -> Weight { - Weight::from_ref_time(15_710_000 as u64) + Weight::from_ref_time(14_587_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: AppPromotion Admin (r:1 w:0) // Storage: EvmContractHelpers Sponsoring (r:0 w:1) fn sponsor_contract() -> Weight { - Weight::from_ref_time(12_669_000 as u64) + Weight::from_ref_time(11_791_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: AppPromotion Admin (r:1 w:0) // Storage: EvmContractHelpers Sponsoring (r:1 w:1) fn stop_sponsoring_contract() -> Weight { - Weight::from_ref_time(14_406_000 as u64) + Weight::from_ref_time(13_576_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } @@ -137,18 +147,19 @@ // For backwards compatibility and tests impl WeightInfo for () { // Storage: AppPromotion PendingUnstake (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn on_initialize(b: u32, ) -> Weight { - Weight::from_ref_time(3_079_948 as u64) - // Standard Error: 30_376 - .saturating_add(Weight::from_ref_time(6_343_630 as u64).saturating_mul(b as u64)) + Weight::from_ref_time(2_562_275 as u64) + // Standard Error: 21_950 + .saturating_add(Weight::from_ref_time(7_177_129 as u64).saturating_mul(b as u64)) .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(b as u64))) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(b as u64))) + .saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(b as u64))) + .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(b as u64))) } // Storage: AppPromotion Admin (r:0 w:1) fn set_admin_address() -> Weight { - Weight::from_ref_time(6_653_000 as u64) + Weight::from_ref_time(6_146_000 as u64) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: AppPromotion Admin (r:1 w:0) @@ -160,9 +171,9 @@ // Storage: Balances Locks (r:1 w:1) // Storage: AppPromotion TotalStaked (r:1 w:1) fn payout_stakers(b: u32, ) -> Weight { - Weight::from_ref_time(74_048_000 as u64) - // Standard Error: 33_223 - .saturating_add(Weight::from_ref_time(57_702_092 as u64).saturating_mul(b as u64)) + Weight::from_ref_time(64_035_000 as u64) + // Standard Error: 19_434 + .saturating_add(Weight::from_ref_time(47_251_111 as u64).saturating_mul(b as u64)) .saturating_add(RocksDbWeight::get().reads(7 as u64)) .saturating_add(RocksDbWeight::get().reads((12 as u64).saturating_mul(b as u64))) .saturating_add(RocksDbWeight::get().writes(3 as u64)) @@ -176,47 +187,55 @@ // Storage: AppPromotion Staked (r:1 w:1) // Storage: AppPromotion TotalStaked (r:1 w:1) fn stake() -> Weight { - Weight::from_ref_time(20_314_000 as u64) + Weight::from_ref_time(18_078_000 as u64) .saturating_add(RocksDbWeight::get().reads(7 as u64)) .saturating_add(RocksDbWeight::get().writes(5 as u64)) } // Storage: Configuration AppPromomotionConfigurationOverride (r:1 w:0) // Storage: AppPromotion PendingUnstake (r:1 w:1) // Storage: AppPromotion Staked (r:11 w:10) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) // Storage: AppPromotion TotalStaked (r:1 w:1) // Storage: AppPromotion StakesPerAccount (r:0 w:1) - fn unstake() -> Weight { - Weight::from_ref_time(64_582_000 as u64) - .saturating_add(RocksDbWeight::get().reads(16 as u64)) - .saturating_add(RocksDbWeight::get().writes(15 as u64)) + fn unstake_all() -> Weight { + Weight::from_ref_time(45_038_000 as u64) + .saturating_add(RocksDbWeight::get().reads(14 as u64)) + .saturating_add(RocksDbWeight::get().writes(13 as u64)) } + // Storage: Configuration AppPromomotionConfigurationOverride (r:1 w:0) + // Storage: AppPromotion PendingUnstake (r:1 w:1) + // Storage: AppPromotion Staked (r:11 w:10) + // Storage: AppPromotion TotalStaked (r:1 w:1) + // Storage: AppPromotion StakesPerAccount (r:1 w:1) + fn unstake_partial() -> Weight { + Weight::from_ref_time(48_863_000 as u64) + .saturating_add(RocksDbWeight::get().reads(15 as u64)) + .saturating_add(RocksDbWeight::get().writes(13 as u64)) + } // Storage: AppPromotion Admin (r:1 w:0) // Storage: Common CollectionById (r:1 w:1) fn sponsor_collection() -> Weight { - Weight::from_ref_time(16_364_000 as u64) + Weight::from_ref_time(14_808_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: AppPromotion Admin (r:1 w:0) // Storage: Common CollectionById (r:1 w:1) fn stop_sponsoring_collection() -> Weight { - Weight::from_ref_time(15_710_000 as u64) + Weight::from_ref_time(14_587_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: AppPromotion Admin (r:1 w:0) // Storage: EvmContractHelpers Sponsoring (r:0 w:1) fn sponsor_contract() -> Weight { - Weight::from_ref_time(12_669_000 as u64) + Weight::from_ref_time(11_791_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: AppPromotion Admin (r:1 w:0) // Storage: EvmContractHelpers Sponsoring (r:1 w:1) fn stop_sponsoring_contract() -> Weight { - Weight::from_ref_time(14_406_000 as u64) + Weight::from_ref_time(13_576_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } --- a/tests/src/sub/appPromotion/appPromotion.test.ts +++ b/tests/src/sub/appPromotion/appPromotion.test.ts @@ -247,7 +247,7 @@ // unstake has no effect if no stakes at all testCase.method === 'unstakeAll' ? await helper.staking.unstakeAll(staker) - : await expect(helper.staking.unstakePartial(staker, 100n * nominal)).to.be.rejectedWith('Arithmetic: Underflow'); + : await expect(helper.staking.unstakePartial(staker, 100n * nominal)).to.be.rejectedWith('appPromotion.InsufficientStakedBalance'); expect(await helper.staking.getPendingUnstake({Substrate: staker.address})).to.be.equal(0n); expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n); // TODO bigint closeTo helper @@ -262,7 +262,7 @@ await helper.staking.unstakeAll(staker); } else { await helper.staking.unstakePartial(staker, 100n * nominal); - await expect(helper.staking.unstakePartial(staker, 100n * nominal)).to.be.rejectedWith('Arithmetic: Underflow'); + await expect(helper.staking.unstakePartial(staker, 100n * nominal)).to.be.rejectedWith('appPromotion.InsufficientStakedBalance'); } expect(await helper.staking.getStakesNumber({Substrate: staker.address})).to.eq(0); -- gitstuff