diff --git a/designdocs/capacity.md b/designdocs/capacity.md index 160fab6a86..8f0a911ce8 100644 --- a/designdocs/capacity.md +++ b/designdocs/capacity.md @@ -302,7 +302,7 @@ Storage for keeping records of staking accounting. /// Storage for keeping a ledger of staked token amounts for accounts. #[pallet::storage] pub type StakingAccountLedger = - StorageMap<_, Twox64Concat, T::AccountId, StakingAccountDetails>; + StorageMap<_, Twox64Concat, T::AccountId, StakingDetails>; ``` @@ -355,7 +355,7 @@ The type used for storing information about staking details. ```rust -pub struct StakingAccountDetails { +pub struct StakingDetails { /// The amount a Staker has staked, minus the sum of all tokens in `unlocking`. pub active: Balance, /// The total amount of tokens in `active` and `unlocking` diff --git a/designdocs/capacity_staking_rewards_implementation.md b/designdocs/capacity_staking_rewards_implementation.md index 24f8b02af5..0a7f0ac99d 100644 --- a/designdocs/capacity_staking_rewards_implementation.md +++ b/designdocs/capacity_staking_rewards_implementation.md @@ -1,11 +1,13 @@ # Capacity Staking Rewards Implementation ## Overview -Staking Capacity for rewards is a new feature which allows token holders to stake FRQCY and split the staking -rewards with a Provider they choose. The Provider receives a small reward in Capacity -(which is periodically replenished), and the staker receives a periodic return in FRQCY token. -The amount of Capacity that the Provider would receive in such case is a fraction of what they would get from a -`MaximumCapacity` stake. +This document describes a new type of staking which allows token holders to stake FRQCY and split staking rewards with a Provider the staker chooses. + +Currently, when staking token for Capacity, the only choice is to assign all the generated Capacity to the designated target. +The target, who must be a Provider, may then spend this Capacity to pay for specific transactions. This is called **Maximized Capacity** staking. + +In this new type of staking, called **Provider Boosting**, the Provider receives a reward in Capacity and the staker receives a periodic return in FRQCY token. +The amount of Capacity that the Provider would receive in such case is a less than what they would get from a `MaximumCapacity` stake. The period of Capacity replenishment - the `Epoch` - and the period of token reward - the `RewardEra`- are different. Epochs much necessarily be much shorter than rewards because Capacity replenishment needs to be multiple times a day to meet the needs of a high traffic network, and to allow Providers the ability to delay transactions to a time of day with lower network activity if necessary. @@ -13,12 +15,13 @@ Reward eras need to be on a much longer scale, such as every two weeks, because In addition, this lets the chain to store Reward history for much longer rather than forcing people to have to take steps to claim rewards. ### Diagram -This illustrates roughly (and not to scale) how Provider Boost staking works. Just like the current staking behavior, now called Maximized staking, The Capacity generated by staking is added to the Provider's Capacity ledger immediately so it can be used right away. The amount staked is locked in Alice's account, preventing transfer. +This illustrates roughly -- not to scale and **NOT reflecting actual reward amounts** -- how Provider Boost staking is expected to work. Just like the current staking behavior, now called Maximium staking, The Capacity generated by staking is added to the Provider's Capacity ledger immediately so it can be used right away. The amount staked is locked in Alice's account, preventing transfer. Provider Boost token rewards are earned only for token staked for a complete Reward Era. So Alice does not begin earning rewards until Reward Era 5 in the diagram, and this means Alice must wait until Reward Era 6 to claim rewards for Reward Era 5. Unclaimed reward amounts are actually not minted or transferred until they are claimed, and may also not be calculated until then, depending on the economic model. This process will be described in more detail in the Economic Model Design Document. +### NOTE: Actual reward amounts are TBD; amounts are for illustration purposes only ![Provider boosted staking](https://github.com/LibertyDSNP/frequency/assets/502640/ffb632f2-79c2-4a09-a906-e4de02e4f348) The proposed feature is a design for staking FRQCY token in exchange for Capacity and/or FRQCY. @@ -50,23 +53,15 @@ It does not give regard to what the economic model actually is, since that is ye ## Staking Token Rewards -### StakingAccountDetails updates +### StakingAccountDetails --> StakingDetails New fields are added. The field **`last_rewarded_at`** is to keep track of the last time rewards were claimed for this Staking Account. MaximumCapacity staking accounts MUST always have the value `None` for `last_rewarded_at`. -Finally, `stake_change_unlocking`, is added, which stores an `UnlockChunk` when a staking account has changed. -targets for some amount of funds. This is to prevent retarget spamming. -This will be a V2 of this storage and original StakingAccountDetails will need to be migrated. +This is a second version of this storage, to replace StakingAccountDetails, and StakingAccountDetails data will need to be migrated. ```rust -pub struct StakingAccountDetailsV2 { +pub struct StakingDetails { pub active: BalanceOf, - pub total: BalanceOf, - pub unlocking: BoundedVec, T::EpochNumber>, T::MaxUnlockingChunks>, - /// The number of the last StakingEra that this account's rewards were claimed. pub last_rewards_claimed_at: Option, // NEW None means never rewarded, Some(RewardEra) means last rewarded RewardEra. - /// staking amounts that have been retargeted are prevented from being retargeted again for the - /// configured Thawing Period number of blocks. - pub stake_change_unlocking: BoundedVec, T::RewardEra>, T::MaxUnlockingChunks>, // NEW } ``` @@ -150,7 +145,7 @@ pub struct StakingRewardClaim { /// How much is claimed, in token pub claimed_reward: Balance, /// The end state of the staking account if the operations are valid - pub staking_account_end_state: StakingAccountDetails, + pub staking_account_end_state: StakingDetails, /// The starting era for the claimed reward period, inclusive pub from_era: T::RewardEra, /// The ending era for the claimed reward period, inclusive @@ -264,7 +259,7 @@ calculate rewards on chain at all. Regardless, on success, the claimed rewards are minted and transferred as locked token to the origin, with the existing unstaking thaw period for withdrawal (which simply unlocks thawed token amounts as before). -There is no chunk added; instead the existing unstaking thaw period is applied to last_rewards_claimed_at in StakingAccountDetails. +There is no chunk added; instead the existing unstaking thaw period is applied to last_rewards_claimed_at in StakingDetails. Forcing stakers to wait a thaw period for every claim is an incentive to claim rewards sooner than later, leveling out possible inflationary effects and helping prevent unclaimed rewards from expiring. @@ -336,7 +331,7 @@ No more than `T::MaxUnlockingChunks` staking amounts may be retargeted within th Each call creates one chunk. Emits a `StakingTargetChanged` event with the parameters of the extrinsic. ```rust /// Sets the target of the staking capacity to a new target. -/// This adds a chunk to `StakingAccountDetails.stake_change_unlocking chunks`, up to `T::MaxUnlockingChunks`. +/// This adds a chunk to `StakingDetails.stake_change_unlocking chunks`, up to `T::MaxUnlockingChunks`. /// The staked amount and Capacity generated by `amount` originally targeted to the `from` MSA Id is reassigned to the `to` MSA Id. /// Does not affect unstaking process or additional stake amounts. /// Changing a staking target to a Provider when Origin has nothing staked them will retain the staking type. diff --git a/pallets/capacity/src/benchmarking.rs b/pallets/capacity/src/benchmarking.rs index 3f6fe24099..4ca8fc99cb 100644 --- a/pallets/capacity/src/benchmarking.rs +++ b/pallets/capacity/src/benchmarking.rs @@ -2,7 +2,7 @@ use super::*; use crate::Pallet as Capacity; use frame_benchmarking::{account, benchmarks, whitelist_account}; -use frame_support::{assert_ok, traits::Currency}; +use frame_support::{assert_ok, traits::Currency, BoundedVec}; use frame_system::RawOrigin; use parity_scale_codec::alloc::vec::Vec; @@ -56,21 +56,19 @@ benchmarks! { withdraw_unstaked { let caller: T::AccountId = create_funded_account::("account", SEED, 5u32); - let amount: BalanceOf = T::MinimumStakingAmount::get(); - - let mut staking_account = StakingAccountDetails::::default(); - staking_account.deposit(500u32.into()); - - // set new unlock chunks using tuples of (value, thaw_at) - let new_unlocks: Vec<(u32, u32)> = Vec::from([(50u32, 3u32), (50u32, 5u32)]); - assert_eq!(true, staking_account.set_unlock_chunks(&new_unlocks)); + let mut unlocking: UnlockChunkList = BoundedVec::default(); + for _i in 0..T::MaxUnlockingChunks::get() { + let unlock_chunk: UnlockChunk, T::EpochNumber> = UnlockChunk { value: 1u32.into(), thaw_at: 3u32.into() }; + assert_ok!(unlocking.try_push(unlock_chunk)); + } + UnstakeUnlocks::::set(&caller, Some(unlocking)); - Capacity::::set_staking_account(&caller.clone(), &staking_account); CurrentEpoch::::set(T::EpochNumber::from(5u32)); }: _ (RawOrigin::Signed(caller.clone())) verify { - assert_last_event::(Event::::StakeWithdrawn {account: caller, amount: 100u32.into() }.into()); + let total = T::MaxUnlockingChunks::get(); + assert_last_event::(Event::::StakeWithdrawn {account: caller, amount: total.into() }.into()); } on_initialize { @@ -91,7 +89,7 @@ benchmarks! { let target = 1; let block_number = 4u32; - let mut staking_account = StakingAccountDetails::::default(); + let mut staking_account = StakingDetails::::default(); let mut target_details = StakingTargetDetails::>::default(); let mut capacity_details = CapacityDetails::, ::EpochNumber>::default(); @@ -99,10 +97,20 @@ benchmarks! { target_details.deposit(staking_amount, capacity_amount); capacity_details.deposit(&staking_amount, &capacity_amount); - Capacity::::set_staking_account(&caller.clone(), &staking_account); + let _ = Capacity::::set_staking_account_and_lock(&caller.clone(), &staking_account); Capacity::::set_target_details_for(&caller.clone(), target, target_details); Capacity::::set_capacity_for(target, capacity_details); + // fill up unlock chunks to max bound - 1 + let count = T::MaxUnlockingChunks::get()-1; + let mut unlocking: UnlockChunkList = BoundedVec::default(); + for _i in 0..count { + let unlock_chunk: UnlockChunk, T::EpochNumber> = UnlockChunk { value: 1u32.into(), thaw_at: 3u32.into() }; + assert_ok!(unlocking.try_push(unlock_chunk)); + } + UnstakeUnlocks::::set(&caller, Some(unlocking)); + + }: _ (RawOrigin::Signed(caller.clone()), target, unstaking_amount.into()) verify { assert_last_event::(Event::::UnStaked {account: caller, target: target, amount: unstaking_amount.into(), capacity: Capacity::::calculate_capacity_reduction(unstaking_amount.into(), staking_amount, capacity_amount) }.into()); diff --git a/pallets/capacity/src/lib.rs b/pallets/capacity/src/lib.rs index 61640341a5..8444e6da46 100644 --- a/pallets/capacity/src/lib.rs +++ b/pallets/capacity/src/lib.rs @@ -49,7 +49,6 @@ )] use frame_support::{ - dispatch::DispatchResult, ensure, traits::{Currency, Get, Hooks, LockIdentifier, LockableCurrency, WithdrawReasons}, weights::{constants::RocksDbWeight, Weight}, @@ -81,6 +80,8 @@ mod benchmarking; #[cfg(test)] mod tests; +/// storage migrations +pub mod migration; pub mod weights; type BalanceOf = @@ -88,13 +89,20 @@ type BalanceOf = const STAKING_ID: LockIdentifier = *b"netstkng"; use frame_system::pallet_prelude::*; + #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{pallet_prelude::*, Twox64Concat}; + use frame_support::{ + pallet_prelude::{StorageVersion, *}, + Twox64Concat, + }; use sp_runtime::traits::{AtLeast32BitUnsigned, MaybeDisplay}; + /// the storage version for this pallet + pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); + #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. @@ -154,11 +162,11 @@ pub mod pallet { /// Storage for keeping a ledger of staked token amounts for accounts. /// - Keys: AccountId - /// - Value: [`StakingAccountDetails`](types::StakingAccountDetails) + /// - Value: [`StakingDetails`](types::StakingDetails) #[pallet::storage] #[pallet::getter(fn get_staking_account_for)] pub type StakingAccountLedger = - StorageMap<_, Twox64Concat, T::AccountId, StakingAccountDetails>; + StorageMap<_, Twox64Concat, T::AccountId, StakingDetails>; /// Storage to record how many tokens were targeted to an MSA. /// - Keys: AccountId, MSA Id @@ -206,9 +214,15 @@ pub mod pallet { pub type EpochLength = StorageValue<_, BlockNumberFor, ValueQuery, EpochLengthDefault>; + #[pallet::storage] + #[pallet::getter(fn get_unstake_unlocking_for)] + pub type UnstakeUnlocks = + StorageMap<_, Twox64Concat, T::AccountId, UnlockChunkList>; + // Simple declaration of the `Pallet` type. It is placeholder we use to implement traits and // method. #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); #[pallet::event] @@ -288,6 +302,8 @@ pub mod pallet { MaxEpochLengthExceeded, /// Staker is attempting to stake an amount that leaves a token balance below the minimum amount. BalanceTooLowtoStake, + /// None of the token amounts in UnlockChunks has thawed yet. + NoThawedTokenAvailable, } #[pallet::hooks] @@ -335,25 +351,17 @@ pub mod pallet { Ok(()) } - /// removes all thawed UnlockChunks from caller's StakingAccount and unlocks the sum of the thawed values + /// Removes all thawed UnlockChunks from caller's UnstakeUnlocks and unlocks the sum of the thawed values /// in the caller's token account. /// /// ### Errors - /// - Returns `Error::NotAStakingAccount` if no StakingAccountDetails are found for `origin`. - /// - Returns `Error::NoUnstakedTokensAvailable` if the account has no unstaking chunks or none are thawed. + /// - Returns `Error::NoUnstakedTokensAvailable` if the account has no unstaking chunks. + /// - Returns `Error::NoThawedTokenAvailable` if there are unstaking chunks, but none are thawed. #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::withdraw_unstaked())] pub fn withdraw_unstaked(origin: OriginFor) -> DispatchResult { let staker = ensure_signed(origin)?; - - let mut staking_account = - Self::get_staking_account_for(&staker).ok_or(Error::::NotAStakingAccount)?; - - let current_epoch = Self::get_current_epoch(); - let amount_withdrawn = staking_account.reap_thawed(current_epoch); - ensure!(!amount_withdrawn.is_zero(), Error::::NoUnstakedTokensAvailable); - - Self::update_or_delete_staking_account(&staker, &mut staking_account); + let amount_withdrawn = Self::do_withdraw_unstaked(&staker)?; Self::deposit_event(Event::::StakeWithdrawn { account: staker, amount: amount_withdrawn, @@ -367,8 +375,9 @@ pub mod pallet { /// - Returns `Error::UnstakedAmountIsZero` if `amount` is not greater than zero. /// - Returns `Error::MaxUnlockingChunksExceeded` if attempting to unlock more times than config::MaxUnlockingChunks. /// - Returns `Error::AmountToUnstakeExceedsAmountStaked` if `amount` exceeds the amount currently staked. - /// - Returns `Error::InvalidTarget` if `target` is not a valid staking target - /// - Returns `Error:: NotAStakingAccount` if `origin` has nothing staked + /// - Returns `Error::InvalidTarget` if `target` is not a valid staking target (not a Provider) + /// - Returns `Error:: NotAStakingAccount` if `origin` has nothing staked at all + /// - Returns `Error::StakerTargetRelationshipNotFound` if `origin` has nothing staked to `target` #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::unstake())] pub fn unstake( @@ -381,6 +390,8 @@ pub mod pallet { ensure!(requested_amount > Zero::zero(), Error::::UnstakedAmountIsZero); let actual_amount = Self::decrease_active_staking_balance(&unstaker, requested_amount)?; + Self::add_unlock_chunk(&unstaker, actual_amount)?; + let capacity_reduction = Self::reduce_capacity(&unstaker, target, actual_amount)?; Self::deposit_event(Event::UnStaked { @@ -426,12 +437,12 @@ impl Pallet { staker: &T::AccountId, target: MessageSourceId, amount: BalanceOf, - ) -> Result<(StakingAccountDetails, BalanceOf), DispatchError> { + ) -> Result<(StakingDetails, BalanceOf), DispatchError> { ensure!(amount > Zero::zero(), Error::::ZeroAmountNotAllowed); ensure!(T::TargetValidator::validate(target), Error::::InvalidTarget); let staking_account = Self::get_staking_account_for(&staker).unwrap_or_default(); - let stakable_amount = staking_account.get_stakable_amount_for(&staker, amount); + let stakable_amount = Self::get_stakable_amount_for(&staker, amount); ensure!(stakable_amount > Zero::zero(), Error::::BalanceTooLowtoStake); @@ -452,7 +463,7 @@ impl Pallet { /// Additionally, it issues Capacity to the MSA target. fn increase_stake_and_issue_capacity( staker: &T::AccountId, - staking_account: &mut StakingAccountDetails, + staking_account: &mut StakingDetails, target: MessageSourceId, amount: BalanceOf, ) -> Result, DispatchError> { @@ -465,34 +476,34 @@ impl Pallet { let mut capacity_details = Self::get_capacity_for(target).unwrap_or_default(); capacity_details.deposit(&amount, &capacity).ok_or(ArithmeticError::Overflow)?; - Self::set_staking_account(&staker, staking_account); + Self::set_staking_account_and_lock(&staker, staking_account)?; + Self::set_target_details_for(&staker, target, target_details); Self::set_capacity_for(target, capacity_details); Ok(capacity) } - /// Sets staking account details. - fn set_staking_account(staker: &T::AccountId, staking_account: &StakingAccountDetails) { - T::Currency::set_lock(STAKING_ID, &staker, staking_account.total, WithdrawReasons::all()); - StakingAccountLedger::::insert(staker, staking_account); - } - - /// Deletes staking account details - fn delete_staking_account(staker: &T::AccountId) { - T::Currency::remove_lock(STAKING_ID, &staker); - StakingAccountLedger::::remove(&staker); + /// Sets staking account details after a deposit + fn set_staking_account_and_lock( + staker: &T::AccountId, + staking_account: &StakingDetails, + ) -> Result<(), DispatchError> { + let unlocks = Self::get_unstake_unlocking_for(staker).unwrap_or_default(); + let total_to_lock: BalanceOf = staking_account + .active + .checked_add(&unlock_chunks_total::(&unlocks)) + .ok_or(ArithmeticError::Overflow)?; + T::Currency::set_lock(STAKING_ID, &staker, total_to_lock, WithdrawReasons::all()); + Self::set_staking_account(staker, staking_account); + Ok(()) } - /// If the staking account total is zero we reap storage, otherwise set the account to the new details. - fn update_or_delete_staking_account( - staker: &T::AccountId, - staking_account: &StakingAccountDetails, - ) { - if staking_account.total.is_zero() { - Self::delete_staking_account(&staker); + fn set_staking_account(staker: &T::AccountId, staking_account: &StakingDetails) { + if staking_account.active.is_zero() { + StakingAccountLedger::::set(staker, None); } else { - Self::set_staking_account(&staker, &staking_account) + StakingAccountLedger::::insert(staker, staking_account); } } @@ -513,7 +524,7 @@ impl Pallet { CapacityLedger::::insert(target, capacity_details); } - /// Decrease a staking account's active token and create an unlocking chunk to be thawed at some future block. + /// Decrease a staking account's active token. fn decrease_active_staking_balance( unstaker: &T::AccountId, amount: BalanceOf, @@ -522,15 +533,67 @@ impl Pallet { Self::get_staking_account_for(unstaker).ok_or(Error::::NotAStakingAccount)?; ensure!(amount <= staking_account.active, Error::::AmountToUnstakeExceedsAmountStaked); + let actual_unstaked_amount = staking_account.withdraw(amount)?; + Self::set_staking_account(unstaker, &staking_account); + Ok(actual_unstaked_amount) + } + + fn add_unlock_chunk( + unstaker: &T::AccountId, + actual_unstaked_amount: BalanceOf, + ) -> Result<(), DispatchError> { let current_epoch: T::EpochNumber = Self::get_current_epoch(); let thaw_at = current_epoch.saturating_add(T::EpochNumber::from(T::UnstakingThawPeriod::get())); + let mut unlocks = Self::get_unstake_unlocking_for(unstaker).unwrap_or_default(); + + let unlock_chunk: UnlockChunk, T::EpochNumber> = + UnlockChunk { value: actual_unstaked_amount, thaw_at }; + unlocks + .try_push(unlock_chunk) + .map_err(|_| Error::::MaxUnlockingChunksExceeded)?; - let unstake_result = staking_account.withdraw(amount, thaw_at)?; + UnstakeUnlocks::::set(unstaker, Some(unlocks)); + Ok(()) + } - Self::set_staking_account(&unstaker, &staking_account); + // Calculates a stakable amount from a proposed amount. + pub(crate) fn get_stakable_amount_for( + staker: &T::AccountId, + proposed_amount: BalanceOf, + ) -> BalanceOf { + let account_balance = T::Currency::free_balance(&staker); + account_balance + .saturating_sub(T::MinimumTokenBalance::get()) + .min(proposed_amount) + } - Ok(unstake_result) + pub(crate) fn do_withdraw_unstaked( + staker: &T::AccountId, + ) -> Result, DispatchError> { + let current_epoch = Self::get_current_epoch(); + let mut total_unlocking: BalanceOf = Zero::zero(); + + let mut unlocks = + Self::get_unstake_unlocking_for(staker).ok_or(Error::::NoUnstakedTokensAvailable)?; + let amount_withdrawn = unlock_chunks_reap_thawed::(&mut unlocks, current_epoch); + ensure!(!amount_withdrawn.is_zero(), Error::::NoThawedTokenAvailable); + + if unlocks.is_empty() { + UnstakeUnlocks::::set(staker, None); + } else { + total_unlocking = unlock_chunks_total::(&unlocks); + UnstakeUnlocks::::set(staker, Some(unlocks)); + } + + let staking_account = Self::get_staking_account_for(staker).unwrap_or_default(); + let total_locked = staking_account.active.saturating_add(total_unlocking); + if total_locked.is_zero() { + T::Currency::remove_lock(STAKING_ID, &staker); + } else { + T::Currency::set_lock(STAKING_ID, &staker, total_locked, WithdrawReasons::all()); + } + Ok(amount_withdrawn) } /// Reduce available capacity of target and return the amount of capacity reduction. diff --git a/pallets/capacity/src/migration/mod.rs b/pallets/capacity/src/migration/mod.rs new file mode 100644 index 0000000000..c34354a101 --- /dev/null +++ b/pallets/capacity/src/migration/mod.rs @@ -0,0 +1,2 @@ +/// migrations to v2 +pub mod v2; diff --git a/pallets/capacity/src/migration/v2.rs b/pallets/capacity/src/migration/v2.rs new file mode 100644 index 0000000000..943bd6fa5b --- /dev/null +++ b/pallets/capacity/src/migration/v2.rs @@ -0,0 +1,115 @@ +use crate::{ + types::{StakingDetails, UnlockChunk}, + BalanceOf, Config, Pallet, StakingAccountLedger, StakingType, UnlockChunkList, UnstakeUnlocks, +}; +use frame_support::{ + pallet_prelude::{GetStorageVersion, Weight}, + traits::{Get, OnRuntimeUpgrade, StorageVersion}, +}; + +const LOG_TARGET: &str = "runtime::capacity"; + +#[cfg(feature = "try-runtime")] +use sp_std::{fmt::Debug, vec::Vec}; + +/// Only contains V1 storage format +pub mod v1 { + use super::*; + use frame_support::{storage_alias, BoundedVec, Twox64Concat}; + use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; + use scale_info::TypeInfo; + + #[derive(Default, Encode, Decode, PartialEq, Debug, TypeInfo, Eq, MaxEncodedLen)] + /// Old StakingAccountDetails struct + pub struct StakingAccountDetails { + /// The amount a Staker has staked, minus the sum of all tokens in `unlocking`. + pub active: BalanceOf, + /// The total amount of tokens in `active` and `unlocking` + pub total: BalanceOf, + /// Unstaked balances that are thawing or awaiting withdrawal. + pub unlocking: BoundedVec< + UnlockChunk, T::EpochNumber>, + ::MaxUnlockingChunks, + >, + } + + #[storage_alias] + /// alias to StakingAccountLedger storage + pub(crate) type StakingAccountLedger = StorageMap< + Pallet, + Twox64Concat, + ::AccountId, + StakingAccountDetails, + >; +} + +/// migrate StakingAccountLedger to use new StakingDetails +pub fn migrate_to_v2() -> Weight { + let on_chain_version = Pallet::::on_chain_storage_version(); // 1r + + if on_chain_version.lt(&2) { + log::info!(target: LOG_TARGET, "🔄 StakingAccountLedger migration started"); + let mut maybe_count = 0u32; + StakingAccountLedger::::translate( + |key: ::AccountId, + old_details: v1::StakingAccountDetails| { + let new_account: StakingDetails = StakingDetails { + active: old_details.active, + staking_type: StakingType::MaximumCapacity, + }; + let new_unlocks: UnlockChunkList = old_details.unlocking; + UnstakeUnlocks::::insert(key, new_unlocks); + maybe_count += 1; + log::info!(target: LOG_TARGET,"migrated {:?}", maybe_count); + Some(new_account) + }, + ); + StorageVersion::new(2).put::>(); // 1 w + let reads = (maybe_count + 1) as u64; + let writes = (maybe_count * 2 + 1) as u64; + log::info!(target: LOG_TARGET, "🔄 migration finished"); + let weight = T::DbWeight::get().reads_writes(reads, writes); + log::info!(target: LOG_TARGET, "Migration calculated weight = {:?}", weight); + weight + } else { + // storage was already migrated. + log::info!(target: LOG_TARGET, "Old StorageAccountLedger migration attempted to run. Please remove"); + T::DbWeight::get().reads(1) + } +} +/// The OnRuntimeUpgrade implementation for this storage migration +pub struct MigrateToV2(sp_std::marker::PhantomData); +impl OnRuntimeUpgrade for MigrateToV2 { + fn on_runtime_upgrade() -> Weight { + migrate_to_v2::() + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + use frame_support::storage::generator::StorageMap; + use parity_scale_codec::Encode; + let pallet_prefix = v1::StakingAccountLedger::::module_prefix(); + let storage_prefix = v1::StakingAccountLedger::::storage_prefix(); + assert_eq!(&b"Capacity"[..], pallet_prefix); + assert_eq!(&b"StakingAccountLedger"[..], storage_prefix); + log::info!(target: LOG_TARGET, "Running pre_upgrade..."); + + let count = v1::StakingAccountLedger::::iter().count() as u32; + log::info!(target: LOG_TARGET, "Finish pre_upgrade for {:?} records", count); + Ok(count.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + use parity_scale_codec::Decode; + let pre_upgrade_count: u32 = Decode::decode(&mut state.as_slice()).unwrap_or_default(); + let on_chain_version = Pallet::::on_chain_storage_version(); + + assert_eq!(on_chain_version, crate::pallet::STORAGE_VERSION); + assert_eq!(pre_upgrade_count as usize, StakingAccountLedger::::iter().count()); + assert_eq!(pre_upgrade_count as usize, UnstakeUnlocks::::iter().count()); + + log::info!(target: LOG_TARGET, "✅ migration post_upgrade checks passed"); + Ok(()) + } +} diff --git a/pallets/capacity/src/tests/migrate_v2_tests.rs b/pallets/capacity/src/tests/migrate_v2_tests.rs new file mode 100644 index 0000000000..ce90dc83c3 --- /dev/null +++ b/pallets/capacity/src/tests/migrate_v2_tests.rs @@ -0,0 +1,56 @@ +#[cfg(test)] +mod test { + use crate::{ + migration::{ + v2::v1::{ + StakingAccountDetails as OldStakingAccountDetails, + StakingAccountLedger as OldStakingAccountLedger, + }, + *, + }, + tests::mock::*, + types::*, + BalanceOf, Config, StakingAccountLedger, + StakingType::MaximumCapacity, + UnstakeUnlocks, + }; + use frame_support::{traits::StorageVersion, BoundedVec}; + + #[test] + #[allow(deprecated)] + fn test_v1_to_v2_works() { + new_test_ext().execute_with(|| { + StorageVersion::new(1).put::(); + for i in 0..3u32 { + let storage_key: ::AccountId = i.into(); + let unlocks: BoundedVec< + UnlockChunk, ::EpochNumber>, + ::MaxUnlockingChunks, + > = BoundedVec::try_from(vec![ + UnlockChunk { value: i as u64, thaw_at: i + 10 }, + UnlockChunk { value: (i + 1) as u64, thaw_at: i + 20 }, + UnlockChunk { value: (i + 2) as u64, thaw_at: i + 30 }, + ]) + .unwrap_or_default(); + + let old_record = + OldStakingAccountDetails:: { active: 3, total: 5, unlocking: unlocks }; + OldStakingAccountLedger::::insert(storage_key, old_record); + } + + assert_eq!(OldStakingAccountLedger::::iter().count(), 3); + + let _w = v2::migrate_to_v2::(); + + assert_eq!(StakingAccountLedger::::iter().count(), 3); + assert_eq!(UnstakeUnlocks::::iter().count(), 3); + + // check that this is really the new type + let last_account: StakingDetails = Capacity::get_staking_account_for(2).unwrap(); + assert_eq!(last_account.staking_type, MaximumCapacity); + + let last_unlocks = Capacity::get_unstake_unlocking_for(2).unwrap(); + assert_eq!(9u64, unlock_chunks_total::(&last_unlocks)); + }) + } +} diff --git a/pallets/capacity/src/tests/mod.rs b/pallets/capacity/src/tests/mod.rs index afef352dc8..871e227ae0 100644 --- a/pallets/capacity/src/tests/mod.rs +++ b/pallets/capacity/src/tests/mod.rs @@ -1,5 +1,6 @@ pub mod capacity_details_tests; pub mod epochs_tests; +mod migrate_v2_tests; pub mod mock; pub mod other_tests; pub mod replenishment_tests; @@ -7,6 +8,7 @@ pub mod stake_and_deposit_tests; pub mod staking_account_details_tests; pub mod staking_target_details_tests; pub mod testing_utils; +mod unlock_chunks_tests; pub mod unstaking_tests; pub mod withdraw_unstaked_tests; pub mod withdrawal_tests; diff --git a/pallets/capacity/src/tests/other_tests.rs b/pallets/capacity/src/tests/other_tests.rs index 7a32c1a87c..9c559f8aef 100644 --- a/pallets/capacity/src/tests/other_tests.rs +++ b/pallets/capacity/src/tests/other_tests.rs @@ -1,12 +1,15 @@ -use frame_support::traits::{Currency, Get}; +use frame_support::{ + assert_ok, + traits::{Currency, Get}, +}; use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::traits::Zero; use common_primitives::{capacity::Nontransferable, msa::MessageSourceId}; use crate::{ - BalanceOf, CapacityDetails, Config, CurrentEpoch, CurrentEpochInfo, EpochInfo, - StakingAccountDetails, StakingTargetDetails, + BalanceOf, CapacityDetails, Config, CurrentEpoch, CurrentEpochInfo, EpochInfo, StakingDetails, + StakingTargetDetails, }; use super::{mock::*, testing_utils::*}; @@ -64,13 +67,13 @@ fn start_new_epoch_works() { } #[test] -fn set_staking_account_is_succesful() { +fn set_staking_account_is_successful() { new_test_ext().execute_with(|| { let staker = 100; - let mut staking_account = StakingAccountDetails::::default(); + let mut staking_account = StakingDetails::::default(); staking_account.deposit(55); - Capacity::set_staking_account(&staker, &staking_account); + assert_ok!(Capacity::set_staking_account_and_lock(&staker, &staking_account)); assert_eq!(Balances::locks(&staker)[0].amount, 55); }); diff --git a/pallets/capacity/src/tests/stake_and_deposit_tests.rs b/pallets/capacity/src/tests/stake_and_deposit_tests.rs index 95e5b94c8d..b91039730e 100644 --- a/pallets/capacity/src/tests/stake_and_deposit_tests.rs +++ b/pallets/capacity/src/tests/stake_and_deposit_tests.rs @@ -1,5 +1,5 @@ use super::{mock::*, testing_utils::*}; -use crate::{BalanceOf, CapacityDetails, Error, Event, StakingAccountDetails}; +use crate::{BalanceOf, CapacityDetails, Error, Event, StakingDetails}; use common_primitives::{capacity::Nontransferable, msa::MessageSourceId}; use frame_support::{assert_noop, assert_ok, traits::WithdrawReasons}; use sp_runtime::ArithmeticError; @@ -15,9 +15,7 @@ fn stake_works() { assert_ok!(Capacity::stake(RuntimeOrigin::signed(account), target, amount)); // Check that StakingAccountLedger is updated. - assert_eq!(Capacity::get_staking_account_for(account).unwrap().total, 50); assert_eq!(Capacity::get_staking_account_for(account).unwrap().active, 50); - assert_eq!(Capacity::get_staking_account_for(account).unwrap().unlocking.len(), 0); // Check that StakingTargetLedger is updated. assert_eq!(Capacity::get_target_for(account, target).unwrap().amount, 50); @@ -115,9 +113,7 @@ fn stake_increase_stake_amount_works() { assert_ok!(Capacity::stake(RuntimeOrigin::signed(account), target, additional_amount)); // Check that StakingAccountLedger is updated. - assert_eq!(Capacity::get_staking_account_for(account).unwrap().total, 150); assert_eq!(Capacity::get_staking_account_for(account).unwrap().active, 150); - assert_eq!(Capacity::get_staking_account_for(account).unwrap().unlocking.len(), 0); // Check that StakingTargetLedger is updated. assert_eq!(Capacity::get_target_for(account, target).unwrap().amount, 150); @@ -151,9 +147,7 @@ fn stake_multiple_accounts_can_stake_to_the_same_target() { assert_ok!(Capacity::stake(RuntimeOrigin::signed(account_1), target, stake_amount_1)); // Check that StakingAccountLedger is updated. - assert_eq!(Capacity::get_staking_account_for(account_1).unwrap().total, 50); assert_eq!(Capacity::get_staking_account_for(account_1).unwrap().active, 50); - assert_eq!(Capacity::get_staking_account_for(account_1).unwrap().unlocking.len(), 0); // Check that StakingTargetLedger is updated. assert_eq!(Capacity::get_target_for(account_1, target).unwrap().amount, 50); @@ -175,9 +169,7 @@ fn stake_multiple_accounts_can_stake_to_the_same_target() { assert_ok!(Capacity::stake(RuntimeOrigin::signed(account_2), target, stake_amount_2)); // Check that StakingAccountLedger is updated. - assert_eq!(Capacity::get_staking_account_for(account_2).unwrap().total, 100); assert_eq!(Capacity::get_staking_account_for(account_2).unwrap().active, 100); - assert_eq!(Capacity::get_staking_account_for(account_2).unwrap().unlocking.len(), 0); // Check that StakingTargetLedger is updated. assert_eq!(Capacity::get_target_for(account_2, target).unwrap().amount, 100); @@ -204,7 +196,6 @@ fn stake_an_account_can_stake_to_multiple_targets() { let amount_2 = 200; assert_ok!(Capacity::stake(RuntimeOrigin::signed(account), target_1, amount_1)); - assert_eq!(Capacity::get_staking_account_for(account).unwrap().total, amount_1); assert_ok!(Capacity::set_epoch_length(RuntimeOrigin::root(), 10)); @@ -213,9 +204,7 @@ fn stake_an_account_can_stake_to_multiple_targets() { assert_ok!(Capacity::stake(RuntimeOrigin::signed(account), target_2, amount_2)); // Check that StakingAccountLedger is updated. - assert_eq!(Capacity::get_staking_account_for(account).unwrap().total, 300); assert_eq!(Capacity::get_staking_account_for(account).unwrap().active, 300); - assert_eq!(Capacity::get_staking_account_for(account).unwrap().unlocking.len(), 0); // Check that StakingTargetLedger is updated for target 1. assert_eq!(Capacity::get_target_for(account, target_1).unwrap().amount, 100); @@ -249,9 +238,7 @@ fn stake_when_staking_amount_is_greater_than_free_balance_it_stakes_maximum() { assert_ok!(Capacity::stake(RuntimeOrigin::signed(account), target, amount)); // Check that StakingAccountLedger is updated. - assert_eq!(Capacity::get_staking_account_for(account).unwrap().total, 190); assert_eq!(Capacity::get_staking_account_for(account).unwrap().active, 190); - assert_eq!(Capacity::get_staking_account_for(account).unwrap().unlocking.len(), 0); // Check that StakingTargetLedger is updated. assert_eq!(Capacity::get_target_for(account, target).unwrap().amount, 190); @@ -264,6 +251,17 @@ fn stake_when_staking_amount_is_greater_than_free_balance_it_stakes_maximum() { }); } +#[test] +fn get_stakable_amount_for_works() { + new_test_ext().execute_with(|| { + let account = 200; + // An amount greater than the free balance + let amount = 230; + let res: u64 = Capacity::get_stakable_amount_for(&account, amount); + assert_eq!(res, 190); + }) +} + #[test] fn stake_when_staking_amount_is_less_than_min_token_balance_it_errors() { new_test_ext().execute_with(|| { @@ -354,7 +352,7 @@ fn ensure_can_stake_is_successful() { let amount = 10; register_provider(target, String::from("Foo")); - let staking_details = StakingAccountDetails::::default(); + let staking_details = StakingDetails::::default(); assert_ok!( Capacity::ensure_can_stake(&account, target, amount), (staking_details, BalanceOf::::from(10u64)) @@ -368,7 +366,7 @@ fn increase_stake_and_issue_capacity_is_successful() { let staker = 10_000; // has 10_000 token let target: MessageSourceId = 1; let amount = 550; - let mut staking_account = StakingAccountDetails::::default(); + let mut staking_account = StakingDetails::::default(); assert_ok!(Capacity::increase_stake_and_issue_capacity( &staker, @@ -377,9 +375,7 @@ fn increase_stake_and_issue_capacity_is_successful() { amount )); - assert_eq!(staking_account.total, amount); assert_eq!(staking_account.active, amount); - assert_eq!(staking_account.unlocking.len(), 0); let capacity_details = Capacity::get_capacity_for(&target).unwrap(); @@ -394,6 +390,26 @@ fn increase_stake_and_issue_capacity_is_successful() { }); } +#[test] +fn stake_when_there_are_unlocks_sets_lock_correctly() { + new_test_ext().execute_with(|| { + let staker = 600; + let target1 = 2; + let target2 = 3; + register_provider(target1, String::from("target1")); + register_provider(target2, String::from("target2")); + assert_ok!(Capacity::stake(RuntimeOrigin::signed(staker), target1, 20)); + + assert_ok!(Capacity::unstake(RuntimeOrigin::signed(staker), target1, 5)); + + assert_ok!(Capacity::stake(RuntimeOrigin::signed(staker), target2, 20)); + + // should all still be locked. + assert_eq!(Balances::locks(&staker)[0].amount, 40); + assert_eq!(Balances::locks(&staker)[0].reasons, WithdrawReasons::all().into()); + }) +} + #[test] fn impl_deposit_is_successful() { new_test_ext().execute_with(|| { diff --git a/pallets/capacity/src/tests/staking_account_details_tests.rs b/pallets/capacity/src/tests/staking_account_details_tests.rs index e49ed9082b..eed5e1f158 100644 --- a/pallets/capacity/src/tests/staking_account_details_tests.rs +++ b/pallets/capacity/src/tests/staking_account_details_tests.rs @@ -1,110 +1,50 @@ use super::mock::*; use crate::*; -use frame_support::assert_err; -use sp_core::bounded::BoundedVec; - -type UnlockBVec = BoundedVec< - UnlockChunk, ::EpochNumber>, - ::MaxUnlockingChunks, ->; - #[test] -fn staking_account_details_withdraw_reduces_active_staking_balance_and_creates_unlock_chunk() { - let mut staking_account_details = StakingAccountDetails:: { +fn staking_account_details_withdraw_reduces_active_staking_balance() { + let mut staking_account_details = StakingDetails:: { active: BalanceOf::::from(15u64), - total: BalanceOf::::from(15u64), - unlocking: BoundedVec::default(), + staking_type: StakingType::MaximumCapacity, }; - assert_eq!(Ok(3u64), staking_account_details.withdraw(3, 3)); - let expected_chunks: UnlockBVec = - BoundedVec::try_from(vec![UnlockChunk { value: 3u64, thaw_at: 3u32 }]).unwrap(); + assert_eq!(Ok(3u64), staking_account_details.withdraw(3)); assert_eq!( staking_account_details, - StakingAccountDetails:: { + StakingDetails:: { active: BalanceOf::::from(12u64), - total: BalanceOf::::from(15u64), - unlocking: expected_chunks, + staking_type: StakingType::MaximumCapacity, } ) } #[test] fn staking_account_details_withdraw_goes_to_zero_when_result_below_minimum() { - let mut staking_account_details = StakingAccountDetails:: { + let mut staking_account_details = StakingDetails:: { active: BalanceOf::::from(10u64), - total: BalanceOf::::from(10u64), - unlocking: BoundedVec::default(), + staking_type: StakingType::MaximumCapacity, }; - assert_eq!(Ok(10u64), staking_account_details.withdraw(6, 3)); + assert_eq!(Ok(10u64), staking_account_details.withdraw(6)); assert_eq!(0u64, staking_account_details.active); - assert_eq!(10u64, staking_account_details.total); staking_account_details.deposit(10); - assert_eq!(Ok(10u64), staking_account_details.withdraw(9, 3)); + assert_eq!(Ok(10u64), staking_account_details.withdraw(9)); assert_eq!(0u64, staking_account_details.active); staking_account_details.deposit(10); - assert_eq!(Ok(10u64), staking_account_details.withdraw(11, 3)); + assert_eq!(Ok(10u64), staking_account_details.withdraw(11)); assert_eq!(0u64, staking_account_details.active); } -#[test] -fn staking_account_details_withdraw_returns_err_when_too_many_chunks() { - let maximum_chunks: UnlockBVec = BoundedVec::try_from(vec![ - UnlockChunk { value: 1u64, thaw_at: 3u32 }, - UnlockChunk { value: 1u64, thaw_at: 3u32 }, - UnlockChunk { value: 1u64, thaw_at: 3u32 }, - UnlockChunk { value: 1u64, thaw_at: 3u32 }, - ]) - .unwrap(); - - let mut staking_account_details = StakingAccountDetails:: { - active: BalanceOf::::from(10u64), - total: BalanceOf::::from(10u64), - unlocking: maximum_chunks, - }; - - assert_err!(staking_account_details.withdraw(6, 3), Error::::MaxUnlockingChunksExceeded); - assert_eq!(10u64, staking_account_details.active); - assert_eq!(10u64, staking_account_details.total); -} - -#[test] -fn staking_account_details_reap_thawed_happy_path() { - let mut staking_account = StakingAccountDetails::::default(); - staking_account.deposit(10); - - // 10 token total, 6 token unstaked - let new_unlocks: Vec<(u32, u32)> = vec![(1u32, 2u32), (2u32, 3u32), (3u32, 4u32)]; - assert_eq!(true, staking_account.set_unlock_chunks(&new_unlocks)); - assert_eq!(10, staking_account.total); - assert_eq!(3, staking_account.unlocking.len()); - - // At epoch 3, the first two chunks should be thawed. - assert_eq!(3u64, staking_account.reap_thawed(3u32)); - assert_eq!(1, staking_account.unlocking.len()); - // ...leaving 10-3 = 7 total in staking - assert_eq!(7, staking_account.total); - - // At epoch 5, all unstaking is done. - assert_eq!(3u64, staking_account.reap_thawed(5u32)); - assert_eq!(0, staking_account.unlocking.len()); - // ...leaving 7-3 = 4 total - assert_eq!(4, staking_account.total); -} - #[test] fn impl_staking_account_details_increase_by() { - let mut staking_account = StakingAccountDetails::::default(); + let mut staking_account = StakingDetails::::default(); assert_eq!(staking_account.deposit(10), Some(())); assert_eq!( staking_account, - StakingAccountDetails:: { + StakingDetails:: { active: BalanceOf::::from(10u64), - total: BalanceOf::::from(10u64), - unlocking: BoundedVec::default(), + staking_type: StakingType::MaximumCapacity, } ) } @@ -112,28 +52,10 @@ fn impl_staking_account_details_increase_by() { #[test] fn impl_staking_account_details_default() { assert_eq!( - StakingAccountDetails::::default(), - StakingAccountDetails:: { + StakingDetails::::default(), + StakingDetails:: { active: BalanceOf::::zero(), - total: BalanceOf::::zero(), - unlocking: BoundedVec::default(), + staking_type: StakingType::MaximumCapacity, }, ); } - -#[test] -fn impl_staking_account_details_get_stakable_amount_for() { - new_test_ext().execute_with(|| { - let account = 200; - let staking_account = StakingAccountDetails::::default(); - - // When staking all of free balance. - assert_eq!(staking_account.get_stakable_amount_for(&account, 10), 10); - - // When staking an amount below free balance. - assert_eq!(staking_account.get_stakable_amount_for(&account, 5), 5); - - // When staking an amount above account free balance. It stakes all of the free balance. - assert_eq!(staking_account.get_stakable_amount_for(&account, 200), 190); - }); -} diff --git a/pallets/capacity/src/tests/unlock_chunks_tests.rs b/pallets/capacity/src/tests/unlock_chunks_tests.rs new file mode 100644 index 0000000000..423e0db3de --- /dev/null +++ b/pallets/capacity/src/tests/unlock_chunks_tests.rs @@ -0,0 +1,38 @@ +use crate::{ + tests::mock::{new_test_ext, Test}, + unlock_chunks_from_vec, unlock_chunks_reap_thawed, unlock_chunks_total, UnlockChunkList, +}; +use sp_runtime::BoundedVec; + +#[test] +fn unlock_chunks_reap_thawed_happy_path() { + new_test_ext().execute_with(|| { + // 10 token total, 6 token unstaked + let new_unlocks: Vec<(u32, u32)> = vec![(1u32, 2u32), (2u32, 3u32), (3u32, 4u32)]; + let mut chunks = unlock_chunks_from_vec::(&new_unlocks); + assert_eq!(3, chunks.len()); + + // At epoch 3, the first two chunks should be thawed. + let reaped = unlock_chunks_reap_thawed::(&mut chunks, 3); + assert_eq!(3, reaped); + assert_eq!(1, chunks.len()); + assert_eq!(3, unlock_chunks_total::(&chunks)); + + // At epoch 5, all unstaking is done. + assert_eq!(3u64, unlock_chunks_reap_thawed::(&mut chunks, 5u32)); + assert_eq!(0, chunks.len()); + + assert_eq!(0u64, unlock_chunks_reap_thawed::(&mut chunks, 5u32)); + }) +} + +#[test] +fn unlock_chunks_total_works() { + new_test_ext().execute_with(|| { + let mut chunks: UnlockChunkList = BoundedVec::default(); + assert_eq!(0u64, unlock_chunks_total::(&chunks)); + let new_unlocks: Vec<(u32, u32)> = vec![(1u32, 2u32), (2u32, 3u32), (3u32, 4u32)]; + chunks = unlock_chunks_from_vec::(&new_unlocks); + assert_eq!(6u64, unlock_chunks_total::(&chunks)); + }) +} diff --git a/pallets/capacity/src/tests/unstaking_tests.rs b/pallets/capacity/src/tests/unstaking_tests.rs index 916e354040..8e930cc141 100644 --- a/pallets/capacity/src/tests/unstaking_tests.rs +++ b/pallets/capacity/src/tests/unstaking_tests.rs @@ -1,6 +1,6 @@ use super::{mock::*, testing_utils::*}; use crate as pallet_capacity; -use crate::{CapacityDetails, StakingAccountDetails, StakingTargetDetails, UnlockChunk}; +use crate::{CapacityDetails, StakingDetails, StakingTargetDetails, StakingType, UnlockChunk}; use common_primitives::msa::MessageSourceId; use frame_support::{assert_noop, assert_ok, traits::Get}; use pallet_capacity::{BalanceOf, Config, Error, Event}; @@ -26,18 +26,19 @@ fn unstake_happy_path() { // Assert that staking account detail values are decremented correctly after unstaking let staking_account_details = Capacity::get_staking_account_for(token_account).unwrap(); - assert_eq!(staking_account_details.unlocking.len(), 1); let expected_unlocking_chunks: BoundedVec< UnlockChunk, ::EpochNumber>, ::MaxUnlockingChunks, > = BoundedVec::try_from(vec![UnlockChunk { value: unstaking_amount, thaw_at: 2u32 }]) .unwrap(); + let unlocking = Capacity::get_unstake_unlocking_for(token_account).unwrap(); + assert_eq!(unlocking, expected_unlocking_chunks); + assert_eq!( - StakingAccountDetails:: { + StakingDetails:: { active: BalanceOf::::from(60u64), - total: BalanceOf::::from(staking_amount), - unlocking: expected_unlocking_chunks, + staking_type: StakingType::MaximumCapacity, }, staking_account_details, ); @@ -109,7 +110,7 @@ fn unstake_errors_max_unlocking_chunks_exceeded() { assert_ok!(Capacity::stake(RuntimeOrigin::signed(token_account), target, staking_amount)); - for _n in 0..::MaxUnlockingChunks::get() { + for _n in 0..::MaxUnlockingChunks::get() { assert_ok!(Capacity::unstake( RuntimeOrigin::signed(token_account), target, @@ -158,3 +159,41 @@ fn unstake_errors_not_a_staking_account() { ); }); } + +#[test] +fn unstaking_everything_reaps_staking_account() { + new_test_ext().execute_with(|| { + let staker = 500; + let target = 1; + let amount = 20; + assert_ok!(Capacity::set_epoch_length(RuntimeOrigin::root(), 10)); + + register_provider(target, String::from("WithdrawUnst")); + assert_ok!(Capacity::stake(RuntimeOrigin::signed(staker), target, amount)); + + run_to_block(1); + // unstake everything + assert_ok!(Capacity::unstake(RuntimeOrigin::signed(staker), target, 20)); + assert_eq!(1, Balances::locks(&staker).len()); + assert_eq!(20u64, Balances::locks(&staker)[0].amount); + + // it should reap the staking account right away + assert!(Capacity::get_staking_account_for(&staker).is_none()); + }) +} + +#[test] +fn unstake_when_not_staking_to_target_errors() { + new_test_ext().execute_with(|| { + let staker = 500; + let target = 1; + let amount = 20; + register_provider(target, String::from("WithdrawUnst")); + + assert_ok!(Capacity::stake(RuntimeOrigin::signed(staker), target, amount)); + assert_noop!( + Capacity::unstake(RuntimeOrigin::signed(staker), 2, 20), + Error::::StakerTargetRelationshipNotFound + ); + }) +} diff --git a/pallets/capacity/src/tests/withdraw_unstaked_tests.rs b/pallets/capacity/src/tests/withdraw_unstaked_tests.rs index c8356b86e7..5e37d86e34 100644 --- a/pallets/capacity/src/tests/withdraw_unstaked_tests.rs +++ b/pallets/capacity/src/tests/withdraw_unstaked_tests.rs @@ -1,39 +1,31 @@ -use super::{mock::*, testing_utils::run_to_block}; -use crate as pallet_capacity; -use crate::StakingAccountDetails; +use super::{ + mock::*, + testing_utils::{register_provider, run_to_block}, +}; +use crate::{ + unlock_chunks_from_vec, CurrentEpoch, CurrentEpochInfo, EpochInfo, Error, Event, UnstakeUnlocks, +}; use frame_support::{assert_noop, assert_ok}; -use pallet_capacity::{BalanceOf, Error, Event}; #[test] fn withdraw_unstaked_happy_path() { new_test_ext().execute_with(|| { - // set up staker and staking account let staker = 500; - // set new unlock chunks using tuples of (value, thaw_at in number of Epochs) - let unlocks: Vec<(u32, u32)> = vec![(1u32, 2u32), (2u32, 3u32), (3u32, 4u32)]; - - // setup_staking_account_for::(staker, staking_amount, &unlocks); - let mut staking_account = StakingAccountDetails::::default(); - - // we have 10 total staked, and 6 of those are unstaking. - staking_account.deposit(10); - assert_eq!(true, staking_account.set_unlock_chunks(&unlocks)); - assert_eq!(10u64, staking_account.total); - Capacity::set_staking_account(&staker, &staking_account.into()); - - let starting_account = Capacity::get_staking_account_for(&staker).unwrap(); - + CurrentEpoch::::set(0); + CurrentEpochInfo::::set(EpochInfo { epoch_start: 0 }); assert_ok!(Capacity::set_epoch_length(RuntimeOrigin::root(), 10)); + // set new unlock chunks using tuples of (value, thaw_at in number of Epochs) + let new_unlocks: Vec<(u32, u32)> = vec![(1u32, 2u32), (2u32, 3u32), (3u32, 4u32)]; + let unlocking = unlock_chunks_from_vec::(&new_unlocks); + UnstakeUnlocks::::set(&staker, Some(unlocking)); + // We want to advance to epoch 3 to unlock the first two sets. run_to_block(31); assert_eq!(3u32, Capacity::get_current_epoch()); assert_ok!(Capacity::withdraw_unstaked(RuntimeOrigin::signed(staker))); - let current_account: StakingAccountDetails = - Capacity::get_staking_account_for(&staker).unwrap(); let expected_reaped_value = 3u64; - assert_eq!(starting_account.total - expected_reaped_value, current_account.total); System::assert_last_event( Event::StakeWithdrawn { account: staker, amount: expected_reaped_value }.into(), ); @@ -44,59 +36,63 @@ fn withdraw_unstaked_happy_path() { fn withdraw_unstaked_correctly_sets_new_lock_state() { new_test_ext().execute_with(|| { let staker = 500; - let mut staking_account = StakingAccountDetails::::default(); - staking_account.deposit(10); + let target = 1; + let amount = 20; + assert_ok!(Capacity::set_epoch_length(RuntimeOrigin::root(), 10)); - // set new unlock chunks using tuples of (value, thaw_at) - let new_unlocks: Vec<(u32, u32)> = vec![(1u32, 2u32), (2u32, 3u32), (3u32, 4u32)]; - assert_eq!(true, staking_account.set_unlock_chunks(&new_unlocks)); + register_provider(target, String::from("WithdrawUnst")); + assert_ok!(Capacity::stake(RuntimeOrigin::signed(staker), target, amount)); - Capacity::set_staking_account(&staker, &staking_account); + run_to_block(1); + assert_ok!(Capacity::unstake(RuntimeOrigin::signed(staker), target, 1)); assert_eq!(1, Balances::locks(&staker).len()); - assert_eq!(10u64, Balances::locks(&staker)[0].amount); + assert_eq!(20u64, Balances::locks(&staker)[0].amount); - assert_ok!(Capacity::set_epoch_length(RuntimeOrigin::root(), 10)); + // thaw period in mock is 2 Epochs * 10 blocks = 20 blocks. + run_to_block(21); + assert_ok!(Capacity::unstake(RuntimeOrigin::signed(staker), target, 2)); + assert_ok!(Capacity::withdraw_unstaked(RuntimeOrigin::signed(staker))); + assert_eq!(1, Balances::locks(&staker).len()); + assert_eq!(19u64, Balances::locks(&staker)[0].amount); - // Epoch length = 10, we want to run to epoch 3 - run_to_block(31); + run_to_block(41); + assert_ok!(Capacity::unstake(RuntimeOrigin::signed(staker), target, 3)); assert_ok!(Capacity::withdraw_unstaked(RuntimeOrigin::signed(staker))); + assert_eq!(1, Balances::locks(&staker).len()); + assert_eq!(17u64, Balances::locks(&staker)[0].amount); + run_to_block(61); + assert_ok!(Capacity::withdraw_unstaked(RuntimeOrigin::signed(staker))); assert_eq!(1, Balances::locks(&staker).len()); - assert_eq!(7u64, Balances::locks(&staker)[0].amount); + assert_eq!(14u64, Balances::locks(&staker)[0].amount); }) } #[test] fn withdraw_unstaked_cleans_up_storage_and_removes_all_locks_if_no_stake_left() { new_test_ext().execute_with(|| { - let mut staking_account = StakingAccountDetails::::default(); - let staking_amount: BalanceOf = 10; - staking_account.deposit(staking_amount); - - // set new unlock chunks using tuples of (value, thaw_at) - let new_unlocks: Vec<(u32, u32)> = vec![(10u32, 2u32)]; - assert_eq!(true, staking_account.set_unlock_chunks(&new_unlocks)); - let staker = 500; - Capacity::set_staking_account(&staker, &staking_account); + let target = 1; + let amount = 20; assert_ok!(Capacity::set_epoch_length(RuntimeOrigin::root(), 10)); - // Epoch Length = 10 and UnstakingThawPeriod = 2 (epochs) - run_to_block(30); - assert_ok!(Capacity::withdraw_unstaked(RuntimeOrigin::signed(staker))); - assert!(Capacity::get_staking_account_for(&staker).is_none()); + register_provider(target, String::from("WithdrawUnst")); + assert_ok!(Capacity::stake(RuntimeOrigin::signed(staker), target, amount)); + run_to_block(1); + // unstake everything + assert_ok!(Capacity::unstake(RuntimeOrigin::signed(staker), target, 20)); + // wait for thaw + run_to_block(21); + assert_ok!(Capacity::withdraw_unstaked(RuntimeOrigin::signed(staker))); assert_eq!(0, Balances::locks(&staker).len()); + assert!(Capacity::get_unstake_unlocking_for(&staker).is_none()); }) } #[test] fn withdraw_unstaked_cannot_withdraw_if_no_unstaking_chunks() { new_test_ext().execute_with(|| { - let staker = 500; - let mut staking_account = StakingAccountDetails::::default(); - staking_account.deposit(10); - Capacity::set_staking_account(&staker, &staking_account); assert_noop!( Capacity::withdraw_unstaked(RuntimeOrigin::signed(500)), Error::::NoUnstakedTokensAvailable @@ -107,29 +103,16 @@ fn withdraw_unstaked_cannot_withdraw_if_no_unstaking_chunks() { fn withdraw_unstaked_cannot_withdraw_if_unstaking_chunks_not_thawed() { new_test_ext().execute_with(|| { let staker = 500; - let mut staking_account = StakingAccountDetails::::default(); - staking_account.deposit(10); - - // set new unlock chunks using tuples of (value, thaw_at) - let new_unlocks: Vec<(u32, u32)> = vec![(1u32, 3u32), (2u32, 40u32), (3u32, 9u32)]; - assert_eq!(true, staking_account.set_unlock_chunks(&new_unlocks)); - - Capacity::set_staking_account(&staker, &staking_account); + let target = 1; + let amount = 10; + assert_ok!(Capacity::set_epoch_length(RuntimeOrigin::root(), 10)); + register_provider(target, String::from("WithdrawUnst")); + assert_ok!(Capacity::stake(RuntimeOrigin::signed(staker), target, amount)); - run_to_block(2); + run_to_block(11); assert_noop!( Capacity::withdraw_unstaked(RuntimeOrigin::signed(500)), Error::::NoUnstakedTokensAvailable ); }) } - -#[test] -fn withdraw_unstaked_error_if_not_a_staking_account() { - new_test_ext().execute_with(|| { - assert_noop!( - Capacity::withdraw_unstaked(RuntimeOrigin::signed(999)), - Error::::NotAStakingAccount - ); - }) -} diff --git a/pallets/capacity/src/types.rs b/pallets/capacity/src/types.rs index 72f5168cb8..80dc35c396 100644 --- a/pallets/capacity/src/types.rs +++ b/pallets/capacity/src/types.rs @@ -1,30 +1,39 @@ //! Types for the Capacity Pallet use super::*; use frame_support::{BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound}; -use log::warn; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ traits::{CheckedAdd, CheckedSub, Saturating, Zero}, RuntimeDebug, }; - #[cfg(any(feature = "runtime-benchmarks", test))] use sp_std::vec::Vec; +#[derive( + Clone, Copy, Debug, Decode, Encode, TypeInfo, Eq, MaxEncodedLen, PartialEq, PartialOrd, +)] +/// The type of staking a given Staking Account is doing. +pub enum StakingType { + /// Staking account targets Providers for capacity only, no token reward + MaximumCapacity, + /// Staking account targets Providers and splits reward between capacity to the Provider + /// and token for the account holder + ProviderBoost, +} + /// The type used for storing information about staking details. #[derive( TypeInfo, RuntimeDebugNoBound, PartialEqNoBound, EqNoBound, Clone, Decode, Encode, MaxEncodedLen, )] #[scale_info(skip_type_params(T))] -pub struct StakingAccountDetails { +pub struct StakingDetails { /// The amount a Staker has staked, minus the sum of all tokens in `unlocking`. pub active: BalanceOf, - /// The total amount of tokens in `active` and `unlocking` - pub total: BalanceOf, - /// Unstaked balances that are thawing or awaiting withdrawal. - pub unlocking: BoundedVec, T::EpochNumber>, T::MaxUnlockingChunks>, + /// The type of staking for this staking account + pub staking_type: StakingType, } + /// The type that is used to record a single request for a number of tokens to be unlocked. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct UnlockChunk { @@ -34,79 +43,17 @@ pub struct UnlockChunk { pub thaw_at: EpochNumber, } -impl StakingAccountDetails { +impl StakingDetails { /// Increases total and active balances by an amount. pub fn deposit(&mut self, amount: BalanceOf) -> Option<()> { - self.total = amount.checked_add(&self.total)?; self.active = amount.checked_add(&self.active)?; - Some(()) } - /// Calculates a stakable amount from a proposed amount. - pub fn get_stakable_amount_for( - &self, - staker: &T::AccountId, - proposed_amount: BalanceOf, - ) -> BalanceOf { - let account_balance = T::Currency::free_balance(&staker); - let available_staking_balance = account_balance.saturating_sub(self.total); - available_staking_balance - .saturating_sub(T::MinimumTokenBalance::get()) - .min(proposed_amount) - } - - #[cfg(any(feature = "runtime-benchmarks", test))] - #[allow(clippy::unwrap_used)] - /// tmp fn for testing only - /// set unlock chunks with (balance, thaw_at). does not check that the unlock chunks - /// don't exceed total. - /// returns true on success, false on failure (?) - pub fn set_unlock_chunks(&mut self, chunks: &Vec<(u32, u32)>) -> bool { - let result: Vec, ::EpochNumber>> = chunks - .into_iter() - .map(|chunk| UnlockChunk { value: chunk.0.into(), thaw_at: chunk.1.into() }) - .collect(); - self.unlocking = BoundedVec::try_from(result).unwrap(); - self.unlocking.len() == chunks.len() - } - - /// deletes thawed chunks, updates `total`, Caller is responsible for updating free/locked - /// balance on the token account. - /// Returns: the total amount reaped from `unlocking` - pub fn reap_thawed(&mut self, current_epoch: ::EpochNumber) -> BalanceOf { - let mut total_reaped: BalanceOf = 0u32.into(); - self.unlocking.retain(|chunk| { - if current_epoch.ge(&chunk.thaw_at) { - total_reaped = total_reaped.saturating_add(chunk.value); - match self.total.checked_sub(&chunk.value) { - Some(new_total) => self.total = new_total, - None => warn!( - "Underflow when subtracting {:?} from staking total {:?}", - chunk.value, self.total - ), - } - false - } else { - true - } - }); - total_reaped - } - /// Decrease the amount of active stake by an amount and create an UnlockChunk. - pub fn withdraw( - &mut self, - amount: BalanceOf, - thaw_at: T::EpochNumber, - ) -> Result, DispatchError> { - // let's check for an early exit before doing all these calcs - ensure!( - self.unlocking.len() < T::MaxUnlockingChunks::get() as usize, - Error::::MaxUnlockingChunksExceeded - ); - + pub fn withdraw(&mut self, amount: BalanceOf) -> Result, DispatchError> { let current_active = self.active; + let mut new_active = self.active.saturating_sub(amount); let mut actual_unstaked: BalanceOf = amount; @@ -114,21 +61,15 @@ impl StakingAccountDetails { actual_unstaked = current_active; new_active = Zero::zero(); } - let unlock_chunk = UnlockChunk { value: actual_unstaked, thaw_at }; - - // we've already done the check but it's fine, we need to handle possible errors. - self.unlocking - .try_push(unlock_chunk) - .map_err(|_| Error::::MaxUnlockingChunksExceeded)?; self.active = new_active; Ok(actual_unstaked) } } -impl Default for StakingAccountDetails { +impl Default for StakingDetails { fn default() -> Self { - Self { active: Zero::zero(), total: Zero::zero(), unlocking: Default::default() } + Self { active: Zero::zero(), staking_type: StakingType::MaximumCapacity } } } @@ -237,3 +178,48 @@ pub struct EpochInfo { /// The block number when this epoch started. pub epoch_start: BlockNumber, } + +/// A BoundedVec containing UnlockChunks +pub type UnlockChunkList = BoundedVec< + UnlockChunk, ::EpochNumber>, + ::MaxUnlockingChunks, +>; + +/// Computes and returns the total token held in an UnlockChunkList. +pub fn unlock_chunks_total(unlock_chunks: &UnlockChunkList) -> BalanceOf { + unlock_chunks + .iter() + .fold(Zero::zero(), |acc: BalanceOf, chunk| acc.saturating_add(chunk.value)) +} + +/// Deletes thawed chunks +/// Caller is responsible for updating free/locked balance on the token account. +/// Returns: the total amount reaped from `unlocking` +pub fn unlock_chunks_reap_thawed( + unlock_chunks: &mut UnlockChunkList, + current_epoch: ::EpochNumber, +) -> BalanceOf { + let mut total_reaped: BalanceOf = 0u32.into(); + unlock_chunks.retain(|chunk| { + if current_epoch.ge(&chunk.thaw_at) { + total_reaped = total_reaped.saturating_add(chunk.value); + false + } else { + true + } + }); + total_reaped +} +#[cfg(any(feature = "runtime-benchmarks", test))] +#[allow(clippy::unwrap_used)] +/// set unlock chunks with (balance, thaw_at). Does not check BoundedVec limit. +/// returns true on success, false on failure (?) +/// For testing and benchmarks ONLY, note possible panic via BoundedVec::try_from + unwrap +pub fn unlock_chunks_from_vec(chunks: &Vec<(u32, u32)>) -> UnlockChunkList { + let result: Vec, ::EpochNumber>> = chunks + .into_iter() + .map(|chunk| UnlockChunk { value: chunk.0.into(), thaw_at: chunk.1.into() }) + .collect(); + // CAUTION + BoundedVec::try_from(result).unwrap() +} diff --git a/pallets/capacity/src/weights.rs b/pallets/capacity/src/weights.rs index fb7f024db5..c5d7ed84e5 100644 --- a/pallets/capacity/src/weights.rs +++ b/pallets/capacity/src/weights.rs @@ -18,9 +18,9 @@ //! Autogenerated weights for pallet_capacity //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-14, STEPS: `20`, REPEAT: `10`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-11-30, STEPS: `20`, REPEAT: `10`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `benchmark-runner-44wtw-sz2gt`, CPU: `Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz` +//! HOSTNAME: `benchmark-runner-44wtw-5slfv`, CPU: `Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("frequency-bench"), DB CACHE: 1024 // Executed Command: @@ -62,11 +62,13 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Msa::ProviderToRegistryEntry` (r:1 w:0) /// Proof: `Msa::ProviderToRegistryEntry` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) /// Storage: `Capacity::StakingAccountLedger` (r:1 w:1) - /// Proof: `Capacity::StakingAccountLedger` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + /// Proof: `Capacity::StakingAccountLedger` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// Storage: `Capacity::StakingTargetLedger` (r:1 w:1) /// Proof: `Capacity::StakingTargetLedger` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Capacity::CapacityLedger` (r:1 w:1) /// Proof: `Capacity::CapacityLedger` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Capacity::UnstakeUnlocks` (r:1 w:0) + /// Proof: `Capacity::UnstakeUnlocks` (`max_values`: None, `max_size`: Some(121), added: 2596, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) @@ -75,24 +77,26 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `223` // Estimated: `6249` - // Minimum execution time: 43_154_000 picoseconds. - Weight::from_parts(44_525_000, 6249) - .saturating_add(T::DbWeight::get().reads(6_u64)) + // Minimum execution time: 44_240_000 picoseconds. + Weight::from_parts(45_555_000, 6249) + .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } - /// Storage: `Capacity::StakingAccountLedger` (r:1 w:1) - /// Proof: `Capacity::StakingAccountLedger` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + /// Storage: `Capacity::UnstakeUnlocks` (r:1 w:1) + /// Proof: `Capacity::UnstakeUnlocks` (`max_values`: None, `max_size`: Some(121), added: 2596, mode: `MaxEncodedLen`) + /// Storage: `Capacity::StakingAccountLedger` (r:1 w:0) + /// Proof: `Capacity::StakingAccountLedger` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) fn withdraw_unstaked() -> Weight { // Proof Size summary in bytes: - // Measured: `339` + // Measured: `285` // Estimated: `6249` - // Minimum execution time: 33_031_000 picoseconds. - Weight::from_parts(34_284_000, 6249) - .saturating_add(T::DbWeight::get().reads(3_u64)) + // Minimum execution time: 28_294_000 picoseconds. + Weight::from_parts(29_114_000, 6249) + .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `Capacity::CurrentEpochInfo` (r:1 w:1) @@ -103,28 +107,26 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `2974` - // Minimum execution time: 3_876_000 picoseconds. - Weight::from_parts(4_036_000, 2974) + // Minimum execution time: 3_912_000 picoseconds. + Weight::from_parts(4_081_000, 2974) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Capacity::StakingAccountLedger` (r:1 w:1) - /// Proof: `Capacity::StakingAccountLedger` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) + /// Proof: `Capacity::StakingAccountLedger` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `Capacity::UnstakeUnlocks` (r:1 w:1) + /// Proof: `Capacity::UnstakeUnlocks` (`max_values`: None, `max_size`: Some(121), added: 2596, mode: `MaxEncodedLen`) /// Storage: `Capacity::StakingTargetLedger` (r:1 w:1) /// Proof: `Capacity::StakingTargetLedger` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Capacity::CapacityLedger` (r:1 w:1) /// Proof: `Capacity::CapacityLedger` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn unstake() -> Weight { // Proof Size summary in bytes: - // Measured: `433` - // Estimated: `6249` - // Minimum execution time: 38_465_000 picoseconds. - Weight::from_parts(39_656_000, 6249) - .saturating_add(T::DbWeight::get().reads(5_u64)) + // Measured: `271` + // Estimated: `5071` + // Minimum execution time: 25_110_000 picoseconds. + Weight::from_parts(25_886_000, 5071) + .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Capacity::EpochLength` (r:0 w:1) @@ -133,8 +135,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_805_000 picoseconds. - Weight::from_parts(7_569_000, 0) + // Minimum execution time: 6_335_000 picoseconds. + Weight::from_parts(6_662_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } } @@ -144,11 +146,13 @@ impl WeightInfo for () { /// Storage: `Msa::ProviderToRegistryEntry` (r:1 w:0) /// Proof: `Msa::ProviderToRegistryEntry` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) /// Storage: `Capacity::StakingAccountLedger` (r:1 w:1) - /// Proof: `Capacity::StakingAccountLedger` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + /// Proof: `Capacity::StakingAccountLedger` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// Storage: `Capacity::StakingTargetLedger` (r:1 w:1) /// Proof: `Capacity::StakingTargetLedger` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Capacity::CapacityLedger` (r:1 w:1) /// Proof: `Capacity::CapacityLedger` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Capacity::UnstakeUnlocks` (r:1 w:0) + /// Proof: `Capacity::UnstakeUnlocks` (`max_values`: None, `max_size`: Some(121), added: 2596, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) @@ -157,24 +161,26 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `223` // Estimated: `6249` - // Minimum execution time: 43_154_000 picoseconds. - Weight::from_parts(44_525_000, 6249) - .saturating_add(RocksDbWeight::get().reads(6_u64)) + // Minimum execution time: 44_240_000 picoseconds. + Weight::from_parts(45_555_000, 6249) + .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } - /// Storage: `Capacity::StakingAccountLedger` (r:1 w:1) - /// Proof: `Capacity::StakingAccountLedger` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) + /// Storage: `Capacity::UnstakeUnlocks` (r:1 w:1) + /// Proof: `Capacity::UnstakeUnlocks` (`max_values`: None, `max_size`: Some(121), added: 2596, mode: `MaxEncodedLen`) + /// Storage: `Capacity::StakingAccountLedger` (r:1 w:0) + /// Proof: `Capacity::StakingAccountLedger` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) fn withdraw_unstaked() -> Weight { // Proof Size summary in bytes: - // Measured: `339` + // Measured: `285` // Estimated: `6249` - // Minimum execution time: 33_031_000 picoseconds. - Weight::from_parts(34_284_000, 6249) - .saturating_add(RocksDbWeight::get().reads(3_u64)) + // Minimum execution time: 28_294_000 picoseconds. + Weight::from_parts(29_114_000, 6249) + .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `Capacity::CurrentEpochInfo` (r:1 w:1) @@ -185,28 +191,26 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `2974` - // Minimum execution time: 3_876_000 picoseconds. - Weight::from_parts(4_036_000, 2974) + // Minimum execution time: 3_912_000 picoseconds. + Weight::from_parts(4_081_000, 2974) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Capacity::StakingAccountLedger` (r:1 w:1) - /// Proof: `Capacity::StakingAccountLedger` (`max_values`: None, `max_size`: Some(153), added: 2628, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) + /// Proof: `Capacity::StakingAccountLedger` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `Capacity::UnstakeUnlocks` (r:1 w:1) + /// Proof: `Capacity::UnstakeUnlocks` (`max_values`: None, `max_size`: Some(121), added: 2596, mode: `MaxEncodedLen`) /// Storage: `Capacity::StakingTargetLedger` (r:1 w:1) /// Proof: `Capacity::StakingTargetLedger` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Capacity::CapacityLedger` (r:1 w:1) /// Proof: `Capacity::CapacityLedger` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn unstake() -> Weight { // Proof Size summary in bytes: - // Measured: `433` - // Estimated: `6249` - // Minimum execution time: 38_465_000 picoseconds. - Weight::from_parts(39_656_000, 6249) - .saturating_add(RocksDbWeight::get().reads(5_u64)) + // Measured: `271` + // Estimated: `5071` + // Minimum execution time: 25_110_000 picoseconds. + Weight::from_parts(25_886_000, 5071) + .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Capacity::EpochLength` (r:0 w:1) @@ -215,8 +219,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_805_000 picoseconds. - Weight::from_parts(7_569_000, 0) + // Minimum execution time: 6_335_000 picoseconds. + Weight::from_parts(6_662_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } } diff --git a/runtime/frequency/src/lib.rs b/runtime/frequency/src/lib.rs index 1af8603e70..f553726c91 100644 --- a/runtime/frequency/src/lib.rs +++ b/runtime/frequency/src/lib.rs @@ -223,6 +223,7 @@ pub type Executive = frame_executive::Executive< ( pallet_messages::migration::v2::MigrateToV2, pallet_schemas::migration::v2::MigrateToV2, + pallet_capacity::migration::v2::MigrateToV2, ), >; @@ -261,7 +262,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("frequency"), impl_name: create_runtime_str!("frequency"), authoring_version: 1, - spec_version: 64, + spec_version: 65, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -275,7 +276,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("frequency-rococo"), impl_name: create_runtime_str!("frequency"), authoring_version: 1, - spec_version: 64, + spec_version: 65, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1,