mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 14:31:02 +00:00
Tvl pool staking (#1322)
What does this PR do? - Introduced the TotalValueLocked storage for nomination-pools. - introduced a slashing api in mock.rs - additional test for tracking a slashing event towards a pool without sub-pools - migration for the nomination-pools (V6 to V7) with `VersionedMigration` Why are these changes needed? this is the continuation of the work by @kianenigma in this [PR](https://github.com/paritytech/substrate/pull/13319) How were these changes implemented and what do they affect? - It's an extra StorageValue that's modified whenever funds flow in or out of staking for any of the `bonded_account` of `BondedPools` - The `PoolSlashed`event is now emitted even when no `SubPools` are found Closes https://github.com/paritytech/polkadot-sdk/issues/155 KSM: HHEEgVzcqL3kCXgsxSfJMbsTy8dxoTctuXtpY94n4s8F4pS --------- Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> Co-authored-by: Ankan <ankan.anurag@gmail.com> Co-authored-by: command-bot <>
This commit is contained in:
@@ -1508,6 +1508,7 @@ pub mod migrations {
|
|||||||
paras_registrar::migration::VersionCheckedMigrateToV1<Runtime, ()>,
|
paras_registrar::migration::VersionCheckedMigrateToV1<Runtime, ()>,
|
||||||
pallet_nomination_pools::migration::versioned_migrations::V5toV6<Runtime>,
|
pallet_nomination_pools::migration::versioned_migrations::V5toV6<Runtime>,
|
||||||
pallet_referenda::migration::v1::MigrateV0ToV1<Runtime, ()>,
|
pallet_referenda::migration::v1::MigrateV0ToV1<Runtime, ()>,
|
||||||
|
pallet_nomination_pools::migration::versioned_migrations::V6ToV7<Runtime>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -538,6 +538,31 @@ impl<T: Config> PoolMember<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Total balance of the member, both active and unbonding.
|
||||||
|
/// Doesn't mutate state.
|
||||||
|
#[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
|
||||||
|
fn total_balance(&self) -> BalanceOf<T> {
|
||||||
|
let pool = BondedPool::<T>::get(self.pool_id).unwrap();
|
||||||
|
let active_balance = pool.points_to_balance(self.active_points());
|
||||||
|
|
||||||
|
let sub_pools = match SubPoolsStorage::<T>::get(self.pool_id) {
|
||||||
|
Some(sub_pools) => sub_pools,
|
||||||
|
None => return active_balance,
|
||||||
|
};
|
||||||
|
|
||||||
|
let unbonding_balance = self.unbonding_eras.iter().fold(
|
||||||
|
BalanceOf::<T>::zero(),
|
||||||
|
|accumulator, (era, unlocked_points)| {
|
||||||
|
// if the `SubPools::with_era` has already been merged into the
|
||||||
|
// `SubPools::no_era` use this pool instead.
|
||||||
|
let era_pool = sub_pools.with_era.get(era).unwrap_or(&sub_pools.no_era);
|
||||||
|
accumulator + (era_pool.point_to_balance(*unlocked_points))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
active_balance + unbonding_balance
|
||||||
|
}
|
||||||
|
|
||||||
/// Total points of this member, both active and unbonding.
|
/// Total points of this member, both active and unbonding.
|
||||||
fn total_points(&self) -> BalanceOf<T> {
|
fn total_points(&self) -> BalanceOf<T> {
|
||||||
self.active_points().saturating_add(self.unbonding_points())
|
self.active_points().saturating_add(self.unbonding_points())
|
||||||
@@ -1189,11 +1214,11 @@ impl<T: Config> BondedPool<T> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bond exactly `amount` from `who`'s funds into this pool.
|
/// Bond exactly `amount` from `who`'s funds into this pool. Increases the [`TotalValueLocked`]
|
||||||
|
/// by `amount`.
|
||||||
///
|
///
|
||||||
/// If the bond type is `Create`, `Staking::bond` is called, and `who`
|
/// If the bond is [`BondType::Create`], [`Staking::bond`] is called, and `who` is allowed to be
|
||||||
/// is allowed to be killed. Otherwise, `Staking::bond_extra` is called and `who`
|
/// killed. Otherwise, [`Staking::bond_extra`] is called and `who` cannot be killed.
|
||||||
/// cannot be killed.
|
|
||||||
///
|
///
|
||||||
/// Returns `Ok(points_issues)`, `Err` otherwise.
|
/// Returns `Ok(points_issues)`, `Err` otherwise.
|
||||||
fn try_bond_funds(
|
fn try_bond_funds(
|
||||||
@@ -1224,6 +1249,9 @@ impl<T: Config> BondedPool<T> {
|
|||||||
// found, we exit early.
|
// found, we exit early.
|
||||||
BondType::Later => T::Staking::bond_extra(&bonded_account, amount)?,
|
BondType::Later => T::Staking::bond_extra(&bonded_account, amount)?,
|
||||||
}
|
}
|
||||||
|
TotalValueLocked::<T>::mutate(|tvl| {
|
||||||
|
tvl.saturating_accrue(amount);
|
||||||
|
});
|
||||||
|
|
||||||
Ok(points_issued)
|
Ok(points_issued)
|
||||||
}
|
}
|
||||||
@@ -1239,6 +1267,27 @@ impl<T: Config> BondedPool<T> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Withdraw all the funds that are already unlocked from staking for the
|
||||||
|
/// [`BondedPool::bonded_account`].
|
||||||
|
///
|
||||||
|
/// Also reduces the [`TotalValueLocked`] by the difference of the
|
||||||
|
/// [`T::Staking::total_stake`] of the [`BondedPool::bonded_account`] that might occur by
|
||||||
|
/// [`T::Staking::withdraw_unbonded`].
|
||||||
|
///
|
||||||
|
/// Returns the result of [`T::Staking::withdraw_unbonded`]
|
||||||
|
fn withdraw_from_staking(&self, num_slashing_spans: u32) -> Result<bool, DispatchError> {
|
||||||
|
let bonded_account = self.bonded_account();
|
||||||
|
|
||||||
|
let prev_total = T::Staking::total_stake(&bonded_account.clone()).unwrap_or_default();
|
||||||
|
let outcome = T::Staking::withdraw_unbonded(bonded_account.clone(), num_slashing_spans);
|
||||||
|
let diff = prev_total
|
||||||
|
.defensive_saturating_sub(T::Staking::total_stake(&bonded_account).unwrap_or_default());
|
||||||
|
TotalValueLocked::<T>::mutate(|tvl| {
|
||||||
|
tvl.saturating_reduce(diff);
|
||||||
|
});
|
||||||
|
outcome
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A reward pool.
|
/// A reward pool.
|
||||||
@@ -1437,9 +1486,7 @@ impl<T: Config> UnbondPool<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Dissolve some points from the unbonding pool, reducing the balance of the pool
|
/// Dissolve some points from the unbonding pool, reducing the balance of the pool
|
||||||
/// proportionally.
|
/// proportionally. This is the opposite of `issue`.
|
||||||
///
|
|
||||||
/// This is the opposite of `issue`.
|
|
||||||
///
|
///
|
||||||
/// Returns the actual amount of `Balance` that was removed from the pool.
|
/// Returns the actual amount of `Balance` that was removed from the pool.
|
||||||
fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
|
fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
|
||||||
@@ -1525,7 +1572,7 @@ pub mod pallet {
|
|||||||
use sp_runtime::Perbill;
|
use sp_runtime::Perbill;
|
||||||
|
|
||||||
/// The current storage version.
|
/// The current storage version.
|
||||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(6);
|
const STORAGE_VERSION: StorageVersion = StorageVersion::new(7);
|
||||||
|
|
||||||
#[pallet::pallet]
|
#[pallet::pallet]
|
||||||
#[pallet::storage_version(STORAGE_VERSION)]
|
#[pallet::storage_version(STORAGE_VERSION)]
|
||||||
@@ -1602,6 +1649,14 @@ pub mod pallet {
|
|||||||
type MaxUnbonding: Get<u32>;
|
type MaxUnbonding: Get<u32>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The sum of funds across all pools.
|
||||||
|
///
|
||||||
|
/// This might be lower but never higher than the sum of `total_balance` of all [`PoolMembers`]
|
||||||
|
/// because calling `pool_withdraw_unbonded` might decrease the total stake of the pool's
|
||||||
|
/// `bonded_account` without adjusting the pallet-internal `UnbondingPool`'s.
|
||||||
|
#[pallet::storage]
|
||||||
|
pub type TotalValueLocked<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
|
||||||
|
|
||||||
/// Minimum amount to bond to join a pool.
|
/// Minimum amount to bond to join a pool.
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
pub type MinJoinBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
|
pub type MinJoinBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
|
||||||
@@ -1825,9 +1880,9 @@ pub mod pallet {
|
|||||||
CannotWithdrawAny,
|
CannotWithdrawAny,
|
||||||
/// The amount does not meet the minimum bond to either join or create a pool.
|
/// The amount does not meet the minimum bond to either join or create a pool.
|
||||||
///
|
///
|
||||||
/// The depositor can never unbond to a value less than
|
/// The depositor can never unbond to a value less than `Pallet::depositor_min_bond`. The
|
||||||
/// `Pallet::depositor_min_bond`. The caller does not have nominating
|
/// caller does not have nominating permissions for the pool. Members can never unbond to a
|
||||||
/// permissions for the pool. Members can never unbond to a value below `MinJoinBond`.
|
/// value below `MinJoinBond`.
|
||||||
MinimumBondNotMet,
|
MinimumBondNotMet,
|
||||||
/// The transaction could not be executed due to overflow risk for the pool.
|
/// The transaction could not be executed due to overflow risk for the pool.
|
||||||
OverflowRisk,
|
OverflowRisk,
|
||||||
@@ -2114,7 +2169,7 @@ pub mod pallet {
|
|||||||
|
|
||||||
/// Call `withdraw_unbonded` for the pools account. This call can be made by any account.
|
/// Call `withdraw_unbonded` for the pools account. This call can be made by any account.
|
||||||
///
|
///
|
||||||
/// This is useful if their are too many unlocking chunks to call `unbond`, and some
|
/// This is useful if there are too many unlocking chunks to call `unbond`, and some
|
||||||
/// can be cleared by withdrawing. In the case there are too many unlocking chunks, the user
|
/// can be cleared by withdrawing. In the case there are too many unlocking chunks, the user
|
||||||
/// would probably see an error like `NoMoreChunks` emitted from the staking system when
|
/// would probably see an error like `NoMoreChunks` emitted from the staking system when
|
||||||
/// they attempt to unbond.
|
/// they attempt to unbond.
|
||||||
@@ -2127,10 +2182,12 @@ pub mod pallet {
|
|||||||
) -> DispatchResult {
|
) -> DispatchResult {
|
||||||
let _ = ensure_signed(origin)?;
|
let _ = ensure_signed(origin)?;
|
||||||
let pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
|
let pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
|
||||||
|
|
||||||
// For now we only allow a pool to withdraw unbonded if its not destroying. If the pool
|
// For now we only allow a pool to withdraw unbonded if its not destroying. If the pool
|
||||||
// is destroying then `withdraw_unbonded` can be used.
|
// is destroying then `withdraw_unbonded` can be used.
|
||||||
ensure!(pool.state != PoolState::Destroying, Error::<T>::NotDestroying);
|
ensure!(pool.state != PoolState::Destroying, Error::<T>::NotDestroying);
|
||||||
T::Staking::withdraw_unbonded(pool.bonded_account(), num_slashing_spans)?;
|
pool.withdraw_from_staking(num_slashing_spans)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2180,9 +2237,8 @@ pub mod pallet {
|
|||||||
ensure!(!withdrawn_points.is_empty(), Error::<T>::CannotWithdrawAny);
|
ensure!(!withdrawn_points.is_empty(), Error::<T>::CannotWithdrawAny);
|
||||||
|
|
||||||
// Before calculating the `balance_to_unbond`, we call withdraw unbonded to ensure the
|
// Before calculating the `balance_to_unbond`, we call withdraw unbonded to ensure the
|
||||||
// `transferable_balance` is correct.
|
// `transferrable_balance` is correct.
|
||||||
let stash_killed =
|
let stash_killed = bonded_pool.withdraw_from_staking(num_slashing_spans)?;
|
||||||
T::Staking::withdraw_unbonded(bonded_pool.bonded_account(), num_slashing_spans)?;
|
|
||||||
|
|
||||||
// defensive-only: the depositor puts enough funds into the stash so that it will only
|
// defensive-only: the depositor puts enough funds into the stash so that it will only
|
||||||
// be destroyed when they are leaving.
|
// be destroyed when they are leaving.
|
||||||
@@ -2846,12 +2902,9 @@ impl<T: Config> Pallet<T> {
|
|||||||
},
|
},
|
||||||
(false, false) => {
|
(false, false) => {
|
||||||
// Equivalent to (current_points / current_balance) * new_funds
|
// Equivalent to (current_points / current_balance) * new_funds
|
||||||
balance(
|
balance(u256(current_points).saturating_mul(u256(new_funds)))
|
||||||
u256(current_points)
|
// We check for zero above
|
||||||
.saturating_mul(u256(new_funds))
|
.div(current_balance)
|
||||||
// We check for zero above
|
|
||||||
.div(u256(current_balance)),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2871,9 +2924,12 @@ impl<T: Config> Pallet<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Equivalent of (current_balance / current_points) * points
|
// Equivalent of (current_balance / current_points) * points
|
||||||
balance(u256(current_balance).saturating_mul(u256(points)))
|
balance(
|
||||||
// We check for zero above
|
u256(current_balance)
|
||||||
.div(current_points)
|
.saturating_mul(u256(points))
|
||||||
|
// We check for zero above
|
||||||
|
.div(u256(current_points)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the member has some rewards, transfer a payout from the reward pool to the member.
|
/// If the member has some rewards, transfer a payout from the reward pool to the member.
|
||||||
@@ -3242,6 +3298,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
let mut pools_members = BTreeMap::<PoolId, u32>::new();
|
let mut pools_members = BTreeMap::<PoolId, u32>::new();
|
||||||
let mut pools_members_pending_rewards = BTreeMap::<PoolId, BalanceOf<T>>::new();
|
let mut pools_members_pending_rewards = BTreeMap::<PoolId, BalanceOf<T>>::new();
|
||||||
let mut all_members = 0u32;
|
let mut all_members = 0u32;
|
||||||
|
let mut total_balance_members = Default::default();
|
||||||
PoolMembers::<T>::iter().try_for_each(|(_, d)| -> Result<(), TryRuntimeError> {
|
PoolMembers::<T>::iter().try_for_each(|(_, d)| -> Result<(), TryRuntimeError> {
|
||||||
let bonded_pool = BondedPools::<T>::get(d.pool_id).unwrap();
|
let bonded_pool = BondedPools::<T>::get(d.pool_id).unwrap();
|
||||||
ensure!(!d.total_points().is_zero(), "No member should have zero points");
|
ensure!(!d.total_points().is_zero(), "No member should have zero points");
|
||||||
@@ -3257,6 +3314,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
let pending_rewards = d.pending_rewards(current_rc).unwrap();
|
let pending_rewards = d.pending_rewards(current_rc).unwrap();
|
||||||
*pools_members_pending_rewards.entry(d.pool_id).or_default() += pending_rewards;
|
*pools_members_pending_rewards.entry(d.pool_id).or_default() += pending_rewards;
|
||||||
} // else this pool has been heavily slashed and cannot have any rewards anymore.
|
} // else this pool has been heavily slashed and cannot have any rewards anymore.
|
||||||
|
total_balance_members += d.total_balance();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
@@ -3280,6 +3338,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let mut expected_tvl: BalanceOf<T> = Default::default();
|
||||||
BondedPools::<T>::iter().try_for_each(|(id, inner)| -> Result<(), TryRuntimeError> {
|
BondedPools::<T>::iter().try_for_each(|(id, inner)| -> Result<(), TryRuntimeError> {
|
||||||
let bonded_pool = BondedPool { id, inner };
|
let bonded_pool = BondedPool { id, inner };
|
||||||
ensure!(
|
ensure!(
|
||||||
@@ -3300,13 +3359,28 @@ impl<T: Config> Pallet<T> {
|
|||||||
"depositor must always have MinCreateBond stake in the pool, except for when the \
|
"depositor must always have MinCreateBond stake in the pool, except for when the \
|
||||||
pool is being destroyed and the depositor is the last member",
|
pool is being destroyed and the depositor is the last member",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expected_tvl +=
|
||||||
|
T::Staking::total_stake(&bonded_pool.bonded_account()).unwrap_or_default();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
ensure!(
|
ensure!(
|
||||||
MaxPoolMembers::<T>::get().map_or(true, |max| all_members <= max),
|
MaxPoolMembers::<T>::get().map_or(true, |max| all_members <= max),
|
||||||
Error::<T>::MaxPoolMembers
|
Error::<T>::MaxPoolMembers
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ensure!(
|
||||||
|
TotalValueLocked::<T>::get() == expected_tvl,
|
||||||
|
"TVL deviates from the actual sum of funds of all Pools."
|
||||||
|
);
|
||||||
|
|
||||||
|
ensure!(
|
||||||
|
TotalValueLocked::<T>::get() <= total_balance_members,
|
||||||
|
"TVL must be equal to or less than the total balance of all PoolMembers."
|
||||||
|
);
|
||||||
|
|
||||||
if level <= 1 {
|
if level <= 1 {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
@@ -3424,20 +3498,30 @@ impl<T: Config> Pallet<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> sp_staking::OnStakingUpdate<T::AccountId, BalanceOf<T>> for Pallet<T> {
|
impl<T: Config> sp_staking::OnStakingUpdate<T::AccountId, BalanceOf<T>> for Pallet<T> {
|
||||||
|
/// Reduces the balances of the [`SubPools`], that belong to the pool involved in the
|
||||||
|
/// slash, to the amount that is defined in the `slashed_unlocking` field of
|
||||||
|
/// [`sp_staking::OnStakingUpdate::on_slash`]
|
||||||
|
///
|
||||||
|
/// Emits the `PoolsSlashed` event.
|
||||||
fn on_slash(
|
fn on_slash(
|
||||||
pool_account: &T::AccountId,
|
pool_account: &T::AccountId,
|
||||||
// Bonded balance is always read directly from staking, therefore we don't need to update
|
// Bonded balance is always read directly from staking, therefore we don't need to update
|
||||||
// anything here.
|
// anything here.
|
||||||
slashed_bonded: BalanceOf<T>,
|
slashed_bonded: BalanceOf<T>,
|
||||||
slashed_unlocking: &BTreeMap<EraIndex, BalanceOf<T>>,
|
slashed_unlocking: &BTreeMap<EraIndex, BalanceOf<T>>,
|
||||||
|
total_slashed: BalanceOf<T>,
|
||||||
) {
|
) {
|
||||||
if let Some(pool_id) = ReversePoolIdLookup::<T>::get(pool_account) {
|
let Some(pool_id) = ReversePoolIdLookup::<T>::get(pool_account) else { return };
|
||||||
let mut sub_pools = match SubPoolsStorage::<T>::get(pool_id).defensive() {
|
// As the slashed account belongs to a `BondedPool` the `TotalValueLocked` decreases and
|
||||||
Some(sub_pools) => sub_pools,
|
// an event is emitted.
|
||||||
None => return,
|
TotalValueLocked::<T>::mutate(|tvl| {
|
||||||
};
|
tvl.defensive_saturating_reduce(total_slashed);
|
||||||
for (era, slashed_balance) in slashed_unlocking.iter() {
|
});
|
||||||
if let Some(pool) = sub_pools.with_era.get_mut(era) {
|
|
||||||
|
if let Some(mut sub_pools) = SubPoolsStorage::<T>::get(pool_id) {
|
||||||
|
// set the reduced balance for each of the `SubPools`
|
||||||
|
slashed_unlocking.iter().for_each(|(era, slashed_balance)| {
|
||||||
|
if let Some(pool) = sub_pools.with_era.get_mut(era).defensive() {
|
||||||
pool.balance = *slashed_balance;
|
pool.balance = *slashed_balance;
|
||||||
Self::deposit_event(Event::<T>::UnbondingPoolSlashed {
|
Self::deposit_event(Event::<T>::UnbondingPoolSlashed {
|
||||||
era: *era,
|
era: *era,
|
||||||
@@ -3445,10 +3529,11 @@ impl<T: Config> sp_staking::OnStakingUpdate<T::AccountId, BalanceOf<T>> for Pall
|
|||||||
balance: *slashed_balance,
|
balance: *slashed_balance,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
Self::deposit_event(Event::<T>::PoolSlashed { pool_id, balance: slashed_bonded });
|
|
||||||
SubPoolsStorage::<T>::insert(pool_id, sub_pools);
|
SubPoolsStorage::<T>::insert(pool_id, sub_pools);
|
||||||
|
} else if !slashed_unlocking.is_empty() {
|
||||||
|
defensive!("Expected SubPools were not found");
|
||||||
}
|
}
|
||||||
|
Self::deposit_event(Event::<T>::PoolSlashed { pool_id, balance: slashed_bonded });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,16 @@ use sp_runtime::TryRuntimeError;
|
|||||||
pub mod versioned_migrations {
|
pub mod versioned_migrations {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
/// Migration V6 to V7 wrapped in a [`frame_support::migrations::VersionedMigration`], ensuring
|
||||||
|
/// the migration is only performed when on-chain version is 6.
|
||||||
|
pub type V6ToV7<T> = frame_support::migrations::VersionedMigration<
|
||||||
|
6,
|
||||||
|
7,
|
||||||
|
v7::VersionUncheckedMigrateV6ToV7<T>,
|
||||||
|
crate::pallet::Pallet<T>,
|
||||||
|
<T as frame_system::Config>::DbWeight,
|
||||||
|
>;
|
||||||
|
|
||||||
/// Wrapper over `MigrateToV6` with convenience version checks.
|
/// Wrapper over `MigrateToV6` with convenience version checks.
|
||||||
pub type V5toV6<T> = frame_support::migrations::VersionedMigration<
|
pub type V5toV6<T> = frame_support::migrations::VersionedMigration<
|
||||||
5,
|
5,
|
||||||
@@ -37,6 +47,83 @@ pub mod versioned_migrations {
|
|||||||
>;
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This migration accumulates and initializes the [`TotalValueLocked`] for all pools.
|
||||||
|
///
|
||||||
|
/// WARNING: This migration works under the assumption that the [`BondedPools`] cannot be inflated
|
||||||
|
/// arbitrarily. Otherwise this migration could fail due to too high weight.
|
||||||
|
mod v7 {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub struct VersionUncheckedMigrateV6ToV7<T>(sp_std::marker::PhantomData<T>);
|
||||||
|
impl<T: Config> VersionUncheckedMigrateV6ToV7<T> {
|
||||||
|
fn calculate_tvl_by_total_stake() -> BalanceOf<T> {
|
||||||
|
BondedPools::<T>::iter()
|
||||||
|
.map(|(id, inner)| {
|
||||||
|
T::Staking::total_stake(
|
||||||
|
&BondedPool { id, inner: inner.clone() }.bonded_account(),
|
||||||
|
)
|
||||||
|
.unwrap_or_default()
|
||||||
|
})
|
||||||
|
.reduce(|acc, total_balance| acc + total_balance)
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Config> OnRuntimeUpgrade for VersionUncheckedMigrateV6ToV7<T> {
|
||||||
|
fn on_runtime_upgrade() -> Weight {
|
||||||
|
let migrated = BondedPools::<T>::count();
|
||||||
|
// The TVL should be the sum of all the funds that are actively staked and in the
|
||||||
|
// unbonding process of the account of each pool.
|
||||||
|
let tvl: BalanceOf<T> = Self::calculate_tvl_by_total_stake();
|
||||||
|
|
||||||
|
TotalValueLocked::<T>::set(tvl);
|
||||||
|
|
||||||
|
log!(info, "Upgraded {} pools with a TVL of {:?}", migrated, tvl);
|
||||||
|
|
||||||
|
// reads: migrated * (BondedPools + Staking::total_stake) + count + onchain
|
||||||
|
// version
|
||||||
|
//
|
||||||
|
// writes: current version + TVL
|
||||||
|
T::DbWeight::get().reads_writes(migrated.saturating_mul(2).saturating_add(2).into(), 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "try-runtime")]
|
||||||
|
fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "try-runtime")]
|
||||||
|
fn post_upgrade(_data: Vec<u8>) -> Result<(), TryRuntimeError> {
|
||||||
|
// check that the `TotalValueLocked` written is actually the sum of `total_stake` of the
|
||||||
|
// `BondedPools``
|
||||||
|
let tvl: BalanceOf<T> = Self::calculate_tvl_by_total_stake();
|
||||||
|
ensure!(
|
||||||
|
TotalValueLocked::<T>::get() == tvl,
|
||||||
|
"TVL written is not equal to `Staking::total_stake` of all `BondedPools`."
|
||||||
|
);
|
||||||
|
|
||||||
|
// calculate the sum of `total_balance` of all `PoolMember` as the upper bound for the
|
||||||
|
// `TotalValueLocked`.
|
||||||
|
let total_balance_members: BalanceOf<T> = PoolMembers::<T>::iter()
|
||||||
|
.map(|(_, member)| member.total_balance())
|
||||||
|
.reduce(|acc, total_balance| acc + total_balance)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
ensure!(
|
||||||
|
TotalValueLocked::<T>::get() <= total_balance_members,
|
||||||
|
"TVL is greater than the balance of all PoolMembers."
|
||||||
|
);
|
||||||
|
|
||||||
|
ensure!(
|
||||||
|
Pallet::<T>::on_chain_storage_version() >= 7,
|
||||||
|
"nomination-pools::migration::v7: wrong storage version"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod v6 {
|
mod v6 {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use crate::{self as pools};
|
|||||||
use frame_support::{assert_ok, parameter_types, traits::fungible::Mutate, PalletId};
|
use frame_support::{assert_ok, parameter_types, traits::fungible::Mutate, PalletId};
|
||||||
use frame_system::RawOrigin;
|
use frame_system::RawOrigin;
|
||||||
use sp_runtime::{BuildStorage, FixedU128};
|
use sp_runtime::{BuildStorage, FixedU128};
|
||||||
use sp_staking::Stake;
|
use sp_staking::{OnStakingUpdate, Stake};
|
||||||
|
|
||||||
pub type BlockNumber = u64;
|
pub type BlockNumber = u64;
|
||||||
pub type AccountId = u128;
|
pub type AccountId = u128;
|
||||||
@@ -46,7 +46,8 @@ parameter_types! {
|
|||||||
pub static CurrentEra: EraIndex = 0;
|
pub static CurrentEra: EraIndex = 0;
|
||||||
pub static BondingDuration: EraIndex = 3;
|
pub static BondingDuration: EraIndex = 3;
|
||||||
pub storage BondedBalanceMap: BTreeMap<AccountId, Balance> = Default::default();
|
pub storage BondedBalanceMap: BTreeMap<AccountId, Balance> = Default::default();
|
||||||
pub storage UnbondingBalanceMap: BTreeMap<AccountId, Balance> = Default::default();
|
// map from a user to a vec of eras and amounts being unlocked in each era.
|
||||||
|
pub storage UnbondingBalanceMap: BTreeMap<AccountId, Vec<(EraIndex, Balance)>> = Default::default();
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub static MaxUnbonding: u32 = 8;
|
pub static MaxUnbonding: u32 = 8;
|
||||||
pub static StakingMinBond: Balance = 10;
|
pub static StakingMinBond: Balance = 10;
|
||||||
@@ -60,6 +61,19 @@ impl StakingMock {
|
|||||||
x.insert(who, bonded);
|
x.insert(who, bonded);
|
||||||
BondedBalanceMap::set(&x)
|
BondedBalanceMap::set(&x)
|
||||||
}
|
}
|
||||||
|
/// Mimics a slash towards a pool specified by `pool_id`.
|
||||||
|
/// This reduces the bonded balance of a pool by `amount` and calls [`Pools::on_slash`] to
|
||||||
|
/// enact changes in the nomination-pool pallet.
|
||||||
|
///
|
||||||
|
/// Does not modify any [`SubPools`] of the pool as [`Default::default`] is passed for
|
||||||
|
/// `slashed_unlocking`.
|
||||||
|
pub fn slash_by(pool_id: PoolId, amount: Balance) {
|
||||||
|
let acc = Pools::create_bonded_account(pool_id);
|
||||||
|
let bonded = BondedBalanceMap::get();
|
||||||
|
let pre_total = bonded.get(&acc).unwrap();
|
||||||
|
Self::set_bonded_balance(acc, pre_total - amount);
|
||||||
|
Pools::on_slash(&acc, pre_total - amount, &Default::default(), amount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl sp_staking::StakingInterface for StakingMock {
|
impl sp_staking::StakingInterface for StakingMock {
|
||||||
@@ -105,8 +119,11 @@ impl sp_staking::StakingInterface for StakingMock {
|
|||||||
let mut x = BondedBalanceMap::get();
|
let mut x = BondedBalanceMap::get();
|
||||||
*x.get_mut(who).unwrap() = x.get_mut(who).unwrap().saturating_sub(amount);
|
*x.get_mut(who).unwrap() = x.get_mut(who).unwrap().saturating_sub(amount);
|
||||||
BondedBalanceMap::set(&x);
|
BondedBalanceMap::set(&x);
|
||||||
|
|
||||||
|
let era = Self::current_era();
|
||||||
|
let unlocking_at = era + Self::bonding_duration();
|
||||||
let mut y = UnbondingBalanceMap::get();
|
let mut y = UnbondingBalanceMap::get();
|
||||||
*y.entry(*who).or_insert(Self::Balance::zero()) += amount;
|
y.entry(*who).or_insert(Default::default()).push((unlocking_at, amount));
|
||||||
UnbondingBalanceMap::set(&y);
|
UnbondingBalanceMap::set(&y);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -116,11 +133,13 @@ impl sp_staking::StakingInterface for StakingMock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result<bool, DispatchError> {
|
fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result<bool, DispatchError> {
|
||||||
// Simulates removing unlocking chunks and only having the bonded balance locked
|
let mut unbonding_map = UnbondingBalanceMap::get();
|
||||||
let mut x = UnbondingBalanceMap::get();
|
let staker_map = unbonding_map.get_mut(&who).ok_or("Nothing to unbond")?;
|
||||||
x.remove(&who);
|
|
||||||
UnbondingBalanceMap::set(&x);
|
|
||||||
|
|
||||||
|
let current_era = Self::current_era();
|
||||||
|
staker_map.retain(|(unlocking_at, _amount)| *unlocking_at > current_era);
|
||||||
|
|
||||||
|
UnbondingBalanceMap::set(&unbonding_map);
|
||||||
Ok(UnbondingBalanceMap::get().is_empty() && BondedBalanceMap::get().is_empty())
|
Ok(UnbondingBalanceMap::get().is_empty() && BondedBalanceMap::get().is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,14 +163,17 @@ impl sp_staking::StakingInterface for StakingMock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn stake(who: &Self::AccountId) -> Result<Stake<Balance>, DispatchError> {
|
fn stake(who: &Self::AccountId) -> Result<Stake<Balance>, DispatchError> {
|
||||||
match (
|
match (UnbondingBalanceMap::get().get(who), BondedBalanceMap::get().get(who).copied()) {
|
||||||
UnbondingBalanceMap::get().get(who).copied(),
|
|
||||||
BondedBalanceMap::get().get(who).copied(),
|
|
||||||
) {
|
|
||||||
(None, None) => Err(DispatchError::Other("balance not found")),
|
(None, None) => Err(DispatchError::Other("balance not found")),
|
||||||
(Some(v), None) => Ok(Stake { total: v, active: 0 }),
|
(Some(v), None) => Ok(Stake {
|
||||||
|
total: v.into_iter().fold(0u128, |acc, &x| acc.saturating_add(x.1)),
|
||||||
|
active: 0,
|
||||||
|
}),
|
||||||
(None, Some(v)) => Ok(Stake { total: v, active: v }),
|
(None, Some(v)) => Ok(Stake { total: v, active: v }),
|
||||||
(Some(a), Some(b)) => Ok(Stake { total: a + b, active: b }),
|
(Some(a), Some(b)) => Ok(Stake {
|
||||||
|
total: a.into_iter().fold(0u128, |acc, &x| acc.saturating_add(x.1)) + b,
|
||||||
|
active: b,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ fn test_setup_works() {
|
|||||||
assert_eq!(StakingMock::bonding_duration(), 3);
|
assert_eq!(StakingMock::bonding_duration(), 3);
|
||||||
assert!(Metadata::<T>::contains_key(1));
|
assert!(Metadata::<T>::contains_key(1));
|
||||||
|
|
||||||
|
// initial member.
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 10);
|
||||||
|
|
||||||
let last_pool = LastPoolId::<Runtime>::get();
|
let last_pool = LastPoolId::<Runtime>::get();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
BondedPool::<Runtime>::get(last_pool).unwrap(),
|
BondedPool::<Runtime>::get(last_pool).unwrap(),
|
||||||
@@ -218,10 +221,7 @@ mod bonded_pool {
|
|||||||
|
|
||||||
// slash half of the pool's balance. expected result of `fn api_points_to_balance`
|
// slash half of the pool's balance. expected result of `fn api_points_to_balance`
|
||||||
// to be 1/2 of the pool's balance.
|
// to be 1/2 of the pool's balance.
|
||||||
StakingMock::set_bonded_balance(
|
StakingMock::slash_by(1, Pools::depositor_min_bond() / 2);
|
||||||
default_bonded_account(),
|
|
||||||
Pools::depositor_min_bond() / 2,
|
|
||||||
);
|
|
||||||
assert_eq!(Pallet::<Runtime>::api_points_to_balance(1, 10), 5);
|
assert_eq!(Pallet::<Runtime>::api_points_to_balance(1, 10), 5);
|
||||||
|
|
||||||
// if pool does not exist, points to balance ratio is 0.
|
// if pool does not exist, points to balance ratio is 0.
|
||||||
@@ -238,10 +238,7 @@ mod bonded_pool {
|
|||||||
|
|
||||||
// slash half of the pool's balance. expect result of `fn api_balance_to_points`
|
// slash half of the pool's balance. expect result of `fn api_balance_to_points`
|
||||||
// to be 2 * of the balance to add to the pool.
|
// to be 2 * of the balance to add to the pool.
|
||||||
StakingMock::set_bonded_balance(
|
StakingMock::slash_by(1, Pools::depositor_min_bond() / 2);
|
||||||
default_bonded_account(),
|
|
||||||
Pools::depositor_min_bond() / 2,
|
|
||||||
);
|
|
||||||
assert_eq!(Pallet::<Runtime>::api_balance_to_points(1, 10), 20);
|
assert_eq!(Pallet::<Runtime>::api_balance_to_points(1, 10), 20);
|
||||||
|
|
||||||
// if pool does not exist, balance to points ratio is 0.
|
// if pool does not exist, balance to points ratio is 0.
|
||||||
@@ -637,12 +634,12 @@ mod join {
|
|||||||
// Given
|
// Given
|
||||||
Currency::set_balance(&11, ExistentialDeposit::get() + 2);
|
Currency::set_balance(&11, ExistentialDeposit::get() + 2);
|
||||||
assert!(!PoolMembers::<Runtime>::contains_key(11));
|
assert!(!PoolMembers::<Runtime>::contains_key(11));
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 10);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
assert_ok!(Pools::join(RuntimeOrigin::signed(11), 2, 1));
|
assert_ok!(Pools::join(RuntimeOrigin::signed(11), 2, 1));
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pool_events_since_last_call(),
|
pool_events_since_last_call(),
|
||||||
vec![
|
vec![
|
||||||
@@ -651,6 +648,7 @@ mod join {
|
|||||||
Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true },
|
Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 12);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PoolMembers::<Runtime>::get(11).unwrap(),
|
PoolMembers::<Runtime>::get(11).unwrap(),
|
||||||
@@ -660,7 +658,7 @@ mod join {
|
|||||||
|
|
||||||
// Given
|
// Given
|
||||||
// The bonded balance is slashed in half
|
// The bonded balance is slashed in half
|
||||||
StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 6);
|
StakingMock::slash_by(1, 6);
|
||||||
|
|
||||||
// And
|
// And
|
||||||
Currency::set_balance(&12, ExistentialDeposit::get() + 12);
|
Currency::set_balance(&12, ExistentialDeposit::get() + 12);
|
||||||
@@ -672,8 +670,12 @@ mod join {
|
|||||||
// Then
|
// Then
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pool_events_since_last_call(),
|
pool_events_since_last_call(),
|
||||||
vec![Event::Bonded { member: 12, pool_id: 1, bonded: 12, joined: true }]
|
vec![
|
||||||
|
Event::PoolSlashed { pool_id: 1, balance: 6 },
|
||||||
|
Event::Bonded { member: 12, pool_id: 1, bonded: 12, joined: true }
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 18);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PoolMembers::<Runtime>::get(12).unwrap(),
|
PoolMembers::<Runtime>::get(12).unwrap(),
|
||||||
@@ -2359,11 +2361,15 @@ mod unbond {
|
|||||||
.min_join_bond(10)
|
.min_join_bond(10)
|
||||||
.add_members(vec![(20, 20)])
|
.add_members(vec![(20, 20)])
|
||||||
.build_and_execute(|| {
|
.build_and_execute(|| {
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 30);
|
||||||
// can unbond to above limit
|
// can unbond to above limit
|
||||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
|
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
|
||||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 15);
|
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 15);
|
||||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 5);
|
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 5);
|
||||||
|
|
||||||
|
// tvl remains unchanged.
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 30);
|
||||||
|
|
||||||
// cannot go to below 10:
|
// cannot go to below 10:
|
||||||
assert_noop!(
|
assert_noop!(
|
||||||
Pools::unbond(RuntimeOrigin::signed(20), 20, 10),
|
Pools::unbond(RuntimeOrigin::signed(20), 20, 10),
|
||||||
@@ -2669,8 +2675,9 @@ mod unbond {
|
|||||||
.add_members(vec![(40, 40), (550, 550)])
|
.add_members(vec![(40, 40), (550, 550)])
|
||||||
.build_and_execute(|| {
|
.build_and_execute(|| {
|
||||||
let ed = Currency::minimum_balance();
|
let ed = Currency::minimum_balance();
|
||||||
// Given a slash from 600 -> 100
|
// Given a slash from 600 -> 500
|
||||||
StakingMock::set_bonded_balance(default_bonded_account(), 100);
|
StakingMock::slash_by(1, 500);
|
||||||
|
|
||||||
// and unclaimed rewards of 600.
|
// and unclaimed rewards of 600.
|
||||||
Currency::set_balance(&default_reward_account(), ed + 600);
|
Currency::set_balance(&default_reward_account(), ed + 600);
|
||||||
|
|
||||||
@@ -2702,8 +2709,9 @@ mod unbond {
|
|||||||
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
|
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
|
||||||
Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true },
|
Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true },
|
||||||
Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true },
|
Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true },
|
||||||
|
Event::PoolSlashed { pool_id: 1, balance: 100 },
|
||||||
Event::PaidOut { member: 40, pool_id: 1, payout: 40 },
|
Event::PaidOut { member: 40, pool_id: 1, payout: 40 },
|
||||||
Event::Unbonded { member: 40, pool_id: 1, points: 6, balance: 6, era: 3 }
|
Event::Unbonded { member: 40, pool_id: 1, balance: 6, points: 6, era: 3 }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2863,6 +2871,7 @@ mod unbond {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// When the root kicks then its ok
|
// When the root kicks then its ok
|
||||||
|
// Account with ID 100 is kicked.
|
||||||
assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(900), 100));
|
assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(900), 100));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -2883,6 +2892,7 @@ mod unbond {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// When the bouncer kicks then its ok
|
// When the bouncer kicks then its ok
|
||||||
|
// Account with ID 200 is kicked.
|
||||||
assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(902), 200));
|
assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(902), 200));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -2921,7 +2931,7 @@ mod unbond {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*UnbondingBalanceMap::get().get(&default_bonded_account()).unwrap(),
|
*UnbondingBalanceMap::get().get(&default_bonded_account()).unwrap(),
|
||||||
100 + 200
|
vec![(3, 100), (3, 200)],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -3020,7 +3030,10 @@ mod unbond {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0);
|
assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0);
|
||||||
assert_eq!(*UnbondingBalanceMap::get().get(&default_bonded_account()).unwrap(), 10);
|
assert_eq!(
|
||||||
|
*UnbondingBalanceMap::get().get(&default_bonded_account()).unwrap(),
|
||||||
|
vec![(6, 10)]
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3298,7 +3311,7 @@ mod unbond {
|
|||||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 0);
|
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 0);
|
||||||
|
|
||||||
// slash the default pool
|
// slash the default pool
|
||||||
StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 5);
|
StakingMock::slash_by(1, 5);
|
||||||
|
|
||||||
// cannot unbond even 7, because the value of shares is now less.
|
// cannot unbond even 7, because the value of shares is now less.
|
||||||
assert_noop!(
|
assert_noop!(
|
||||||
@@ -3368,21 +3381,58 @@ mod pool_withdraw_unbonded {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pool_withdraw_unbonded_works() {
|
fn pool_withdraw_unbonded_works() {
|
||||||
ExtBuilder::default().build_and_execute(|| {
|
ExtBuilder::default().add_members(vec![(20, 10)]).build_and_execute(|| {
|
||||||
// Given 10 unbonded directly against the pool account
|
// Given 10 unbond'ed directly against the pool account
|
||||||
assert_ok!(StakingMock::unbond(&default_bonded_account(), 5));
|
|
||||||
// and the pool account only has 10 balance
|
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
|
||||||
assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(5));
|
|
||||||
assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(10));
|
assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15));
|
||||||
assert_eq!(Currency::free_balance(&default_bonded_account()), 10);
|
assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(20));
|
||||||
|
assert_eq!(Balances::free_balance(&default_bonded_account()), 20);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
|
CurrentEra::set(StakingMock::current_era() + StakingMock::bonding_duration() + 1);
|
||||||
assert_ok!(Pools::pool_withdraw_unbonded(RuntimeOrigin::signed(10), 1, 0));
|
assert_ok!(Pools::pool_withdraw_unbonded(RuntimeOrigin::signed(10), 1, 0));
|
||||||
|
|
||||||
// Then there unbonding balance is no longer locked
|
// Then their unbonding balance is no longer locked
|
||||||
assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(5));
|
assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15));
|
||||||
assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(5));
|
assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(15));
|
||||||
assert_eq!(Currency::free_balance(&default_bonded_account()), 10);
|
assert_eq!(Balances::free_balance(&default_bonded_account()), 20);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn pool_withdraw_unbonded_creates_tvl_diff() {
|
||||||
|
ExtBuilder::default().add_members(vec![(20, 10)]).build_and_execute(|| {
|
||||||
|
// Given 10 unbond'ed directly against the pool account
|
||||||
|
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
|
||||||
|
|
||||||
|
assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15));
|
||||||
|
assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(20));
|
||||||
|
assert_eq!(Balances::free_balance(&default_bonded_account()), 20);
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 20);
|
||||||
|
|
||||||
|
// When
|
||||||
|
CurrentEra::set(StakingMock::current_era() + StakingMock::bonding_duration() + 1);
|
||||||
|
assert_ok!(Pools::pool_withdraw_unbonded(RuntimeOrigin::signed(10), 1, 0));
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 15);
|
||||||
|
|
||||||
|
let member_balance = PoolMembers::<T>::iter()
|
||||||
|
.map(|(_, member)| member.total_balance())
|
||||||
|
.reduce(|acc, total_balance| acc + total_balance)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// Then their unbonding balance is no longer locked
|
||||||
|
assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15));
|
||||||
|
assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(15));
|
||||||
|
assert_eq!(Currency::free_balance(&default_bonded_account()), 20);
|
||||||
|
|
||||||
|
// The difference between TVL and member_balance is exactly the difference between
|
||||||
|
// `total_stake` and the `free_balance`.
|
||||||
|
// This relation is not guaranteed in the wild as arbitrary transfers towards
|
||||||
|
// `free_balance` can be made to the pool that are not accounted for.
|
||||||
|
let non_locked_balance = Balances::free_balance(&default_bonded_account()) -
|
||||||
|
StakingMock::total_stake(&default_bonded_account()).unwrap();
|
||||||
|
assert_eq!(member_balance, TotalValueLocked::<T>::get() + non_locked_balance);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3412,24 +3462,33 @@ mod withdraw_unbonded {
|
|||||||
let unbond_pool = sub_pools.with_era.get_mut(&3).unwrap();
|
let unbond_pool = sub_pools.with_era.get_mut(&3).unwrap();
|
||||||
// Sanity check
|
// Sanity check
|
||||||
assert_eq!(*unbond_pool, UnbondPool { points: 550 + 40, balance: 550 + 40 });
|
assert_eq!(*unbond_pool, UnbondPool { points: 550 + 40, balance: 550 + 40 });
|
||||||
|
assert_eq!(TotalValueLocked::<Runtime>::get(), 600);
|
||||||
|
|
||||||
// Simulate a slash to the pool with_era(current_era), decreasing the balance by
|
// Simulate a slash to the pool with_era(current_era), decreasing the balance by
|
||||||
// half
|
// half
|
||||||
{
|
{
|
||||||
unbond_pool.balance /= 2; // 295
|
unbond_pool.balance /= 2; // 295
|
||||||
SubPoolsStorage::<Runtime>::insert(1, sub_pools);
|
SubPoolsStorage::<Runtime>::insert(1, sub_pools);
|
||||||
|
|
||||||
|
// Adjust the TVL for this non-api usage (direct sub-pool modification)
|
||||||
|
TotalValueLocked::<Runtime>::mutate(|x| *x -= 295);
|
||||||
|
|
||||||
// Update the equivalent of the unbonding chunks for the `StakingMock`
|
// Update the equivalent of the unbonding chunks for the `StakingMock`
|
||||||
let mut x = UnbondingBalanceMap::get();
|
let mut x = UnbondingBalanceMap::get();
|
||||||
*x.get_mut(&default_bonded_account()).unwrap() /= 5;
|
x.get_mut(&default_bonded_account())
|
||||||
|
.unwrap()
|
||||||
|
.get_mut(current_era as usize)
|
||||||
|
.unwrap()
|
||||||
|
.1 /= 2;
|
||||||
UnbondingBalanceMap::set(&x);
|
UnbondingBalanceMap::set(&x);
|
||||||
|
|
||||||
Currency::set_balance(
|
Currency::set_balance(
|
||||||
&default_bonded_account(),
|
&default_bonded_account(),
|
||||||
Currency::free_balance(&default_bonded_account()) / 2, // 300
|
Currency::free_balance(&default_bonded_account()) / 2, // 300
|
||||||
);
|
);
|
||||||
StakingMock::set_bonded_balance(
|
assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 10);
|
||||||
default_bonded_account(),
|
StakingMock::slash_by(1, 5);
|
||||||
StakingMock::active_stake(&default_bonded_account()).unwrap() / 2,
|
assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 5);
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Advance the current_era to ensure all `with_era` pools will be merged into
|
// Advance the current_era to ensure all `with_era` pools will be merged into
|
||||||
@@ -3465,6 +3524,7 @@ mod withdraw_unbonded {
|
|||||||
era: 3
|
era: 3
|
||||||
},
|
},
|
||||||
Event::Unbonded { member: 40, pool_id: 1, points: 40, balance: 40, era: 3 },
|
Event::Unbonded { member: 40, pool_id: 1, points: 40, balance: 40, era: 3 },
|
||||||
|
Event::PoolSlashed { pool_id: 1, balance: 5 }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -3552,7 +3612,7 @@ mod withdraw_unbonded {
|
|||||||
|
|
||||||
// Given
|
// Given
|
||||||
// current bond is 600, we slash it all to 300.
|
// current bond is 600, we slash it all to 300.
|
||||||
StakingMock::set_bonded_balance(default_bonded_account(), 300);
|
StakingMock::slash_by(1, 300);
|
||||||
Currency::set_balance(&default_bonded_account(), 300);
|
Currency::set_balance(&default_bonded_account(), 300);
|
||||||
assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(300));
|
assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(300));
|
||||||
|
|
||||||
@@ -3572,6 +3632,7 @@ mod withdraw_unbonded {
|
|||||||
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
|
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
|
||||||
Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true },
|
Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true },
|
||||||
Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true },
|
Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true },
|
||||||
|
Event::PoolSlashed { pool_id: 1, balance: 300 },
|
||||||
Event::Unbonded { member: 40, pool_id: 1, balance: 20, points: 20, era: 3 },
|
Event::Unbonded { member: 40, pool_id: 1, balance: 20, points: 20, era: 3 },
|
||||||
Event::Unbonded {
|
Event::Unbonded {
|
||||||
member: 550,
|
member: 550,
|
||||||
@@ -4051,6 +4112,7 @@ mod withdraw_unbonded {
|
|||||||
#[test]
|
#[test]
|
||||||
fn full_multi_step_withdrawing_non_depositor() {
|
fn full_multi_step_withdrawing_non_depositor() {
|
||||||
ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| {
|
ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| {
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 110);
|
||||||
// given
|
// given
|
||||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(100), 100, 75));
|
assert_ok!(Pools::unbond(RuntimeOrigin::signed(100), 100, 75));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -4058,6 +4120,9 @@ mod withdraw_unbonded {
|
|||||||
member_unbonding_eras!(3 => 75)
|
member_unbonding_eras!(3 => 75)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// tvl unchanged.
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 110);
|
||||||
|
|
||||||
// progress one era and unbond the leftover.
|
// progress one era and unbond the leftover.
|
||||||
CurrentEra::set(1);
|
CurrentEra::set(1);
|
||||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(100), 100, 25));
|
assert_ok!(Pools::unbond(RuntimeOrigin::signed(100), 100, 25));
|
||||||
@@ -4070,6 +4135,8 @@ mod withdraw_unbonded {
|
|||||||
Pools::withdraw_unbonded(RuntimeOrigin::signed(100), 100, 0),
|
Pools::withdraw_unbonded(RuntimeOrigin::signed(100), 100, 0),
|
||||||
Error::<Runtime>::CannotWithdrawAny
|
Error::<Runtime>::CannotWithdrawAny
|
||||||
);
|
);
|
||||||
|
// tvl unchanged.
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 110);
|
||||||
|
|
||||||
// now the 75 should be free.
|
// now the 75 should be free.
|
||||||
CurrentEra::set(3);
|
CurrentEra::set(3);
|
||||||
@@ -4089,6 +4156,8 @@ mod withdraw_unbonded {
|
|||||||
PoolMembers::<Runtime>::get(100).unwrap().unbonding_eras,
|
PoolMembers::<Runtime>::get(100).unwrap().unbonding_eras,
|
||||||
member_unbonding_eras!(4 => 25)
|
member_unbonding_eras!(4 => 25)
|
||||||
);
|
);
|
||||||
|
// tvl updated
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 35);
|
||||||
|
|
||||||
// the 25 should be free now, and the member removed.
|
// the 25 should be free now, and the member removed.
|
||||||
CurrentEra::set(4);
|
CurrentEra::set(4);
|
||||||
@@ -4398,6 +4467,7 @@ mod create {
|
|||||||
let next_pool_stash = Pools::create_bonded_account(2);
|
let next_pool_stash = Pools::create_bonded_account(2);
|
||||||
let ed = Currency::minimum_balance();
|
let ed = Currency::minimum_balance();
|
||||||
|
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 10);
|
||||||
assert!(!BondedPools::<Runtime>::contains_key(2));
|
assert!(!BondedPools::<Runtime>::contains_key(2));
|
||||||
assert!(!RewardPools::<Runtime>::contains_key(2));
|
assert!(!RewardPools::<Runtime>::contains_key(2));
|
||||||
assert!(!PoolMembers::<Runtime>::contains_key(11));
|
assert!(!PoolMembers::<Runtime>::contains_key(11));
|
||||||
@@ -4411,6 +4481,7 @@ mod create {
|
|||||||
456,
|
456,
|
||||||
789
|
789
|
||||||
));
|
));
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 10 + StakingMock::minimum_nominator_bond());
|
||||||
|
|
||||||
assert_eq!(Currency::free_balance(&11), 0);
|
assert_eq!(Currency::free_balance(&11), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -4701,9 +4772,10 @@ mod set_state {
|
|||||||
|
|
||||||
// Given
|
// Given
|
||||||
unsafe_set_state(1, PoolState::Open);
|
unsafe_set_state(1, PoolState::Open);
|
||||||
let mut bonded_pool = BondedPool::<Runtime>::get(1).unwrap();
|
// slash the pool to the point that `max_points_to_balance` ratio is
|
||||||
bonded_pool.points = 100;
|
// surpassed. Making this pool destroyable by anyone.
|
||||||
bonded_pool.put();
|
StakingMock::slash_by(1, 10);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
assert_ok!(Pools::set_state(RuntimeOrigin::signed(11), 1, PoolState::Destroying));
|
assert_ok!(Pools::set_state(RuntimeOrigin::signed(11), 1, PoolState::Destroying));
|
||||||
// Then
|
// Then
|
||||||
@@ -4729,6 +4801,7 @@ mod set_state {
|
|||||||
pool_events_since_last_call(),
|
pool_events_since_last_call(),
|
||||||
vec![
|
vec![
|
||||||
Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
|
Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
|
||||||
|
Event::PoolSlashed { pool_id: 1, balance: 0 },
|
||||||
Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
|
Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
|
||||||
Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying }
|
Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying }
|
||||||
]
|
]
|
||||||
@@ -4927,8 +5000,10 @@ mod bond_extra {
|
|||||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10);
|
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10);
|
||||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 20);
|
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 20);
|
||||||
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30);
|
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30);
|
||||||
|
|
||||||
assert_eq!(Currency::free_balance(&10), 35);
|
assert_eq!(Currency::free_balance(&10), 35);
|
||||||
assert_eq!(Currency::free_balance(&20), 20);
|
assert_eq!(Currency::free_balance(&20), 20);
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 30);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::Rewards));
|
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::Rewards));
|
||||||
@@ -4936,6 +5011,8 @@ mod bond_extra {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assert_eq!(Currency::free_balance(&10), 35);
|
assert_eq!(Currency::free_balance(&10), 35);
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 31);
|
||||||
|
|
||||||
// 10's share of the reward is 1/3, since they gave 10/30 of the total shares.
|
// 10's share of the reward is 1/3, since they gave 10/30 of the total shares.
|
||||||
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10 + 1);
|
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10 + 1);
|
||||||
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30 + 1);
|
assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30 + 1);
|
||||||
@@ -4945,6 +5022,8 @@ mod bond_extra {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assert_eq!(Currency::free_balance(&20), 20);
|
assert_eq!(Currency::free_balance(&20), 20);
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 33);
|
||||||
|
|
||||||
// 20's share of the rewards is the other 2/3 of the rewards, since they have 20/30 of
|
// 20's share of the rewards is the other 2/3 of the rewards, since they have 20/30 of
|
||||||
// the shares
|
// the shares
|
||||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 20 + 2);
|
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 20 + 2);
|
||||||
@@ -5354,7 +5433,7 @@ mod reward_counter_precision {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// slash this pool by 99% of that.
|
// slash this pool by 99% of that.
|
||||||
StakingMock::set_bonded_balance(default_bonded_account(), DOT + pool_bond / 100);
|
StakingMock::slash_by(1, pool_bond * 99 / 100);
|
||||||
|
|
||||||
// some whale now joins with the other half ot the total issuance. This will trigger an
|
// some whale now joins with the other half ot the total issuance. This will trigger an
|
||||||
// overflow. This test is actually a bit too lenient because all the reward counters are
|
// overflow. This test is actually a bit too lenient because all the reward counters are
|
||||||
@@ -6868,3 +6947,73 @@ mod commission {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mod slash {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn slash_no_subpool_is_tracked() {
|
||||||
|
let bonded = |points, member_counter| BondedPool::<Runtime> {
|
||||||
|
id: 1,
|
||||||
|
inner: BondedPoolInner {
|
||||||
|
commission: Commission::default(),
|
||||||
|
member_counter,
|
||||||
|
points,
|
||||||
|
roles: DEFAULT_ROLES,
|
||||||
|
state: PoolState::Open,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ExtBuilder::default().with_check(0).build_and_execute(|| {
|
||||||
|
// Given
|
||||||
|
Currency::set_balance(&11, ExistentialDeposit::get() + 2);
|
||||||
|
assert!(!PoolMembers::<Runtime>::contains_key(11));
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 10);
|
||||||
|
|
||||||
|
// When
|
||||||
|
assert_ok!(Pools::join(RuntimeOrigin::signed(11), 2, 1));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert_eq!(
|
||||||
|
pool_events_since_last_call(),
|
||||||
|
vec![
|
||||||
|
Event::Created { depositor: 10, pool_id: 1 },
|
||||||
|
Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
|
||||||
|
Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true },
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 12);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
PoolMembers::<Runtime>::get(11).unwrap(),
|
||||||
|
PoolMember::<Runtime> { pool_id: 1, points: 2, ..Default::default() }
|
||||||
|
);
|
||||||
|
assert_eq!(BondedPool::<Runtime>::get(1).unwrap(), bonded(12, 2));
|
||||||
|
|
||||||
|
// Given
|
||||||
|
// The bonded balance is slashed in half
|
||||||
|
StakingMock::slash_by(1, 6);
|
||||||
|
|
||||||
|
// And
|
||||||
|
Currency::set_balance(&12, ExistentialDeposit::get() + 12);
|
||||||
|
assert!(!PoolMembers::<Runtime>::contains_key(12));
|
||||||
|
|
||||||
|
// When
|
||||||
|
assert_ok!(Pools::join(RuntimeOrigin::signed(12), 12, 1));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert_eq!(
|
||||||
|
pool_events_since_last_call(),
|
||||||
|
vec![
|
||||||
|
Event::PoolSlashed { pool_id: 1, balance: 6 },
|
||||||
|
Event::Bonded { member: 12, pool_id: 1, bonded: 12, joined: true }
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(TotalValueLocked::<T>::get(), 18);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
PoolMembers::<Runtime>::get(12).unwrap(),
|
||||||
|
PoolMember::<Runtime> { pool_id: 1, points: 24, ..Default::default() }
|
||||||
|
);
|
||||||
|
assert_eq!(BondedPool::<Runtime>::get(1).unwrap(), bonded(12 + 24, 3));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -671,8 +671,14 @@ impl<T: Config> StakingLedger<T> {
|
|||||||
// clean unlocking chunks that are set to zero.
|
// clean unlocking chunks that are set to zero.
|
||||||
self.unlocking.retain(|c| !c.value.is_zero());
|
self.unlocking.retain(|c| !c.value.is_zero());
|
||||||
|
|
||||||
T::EventListeners::on_slash(&self.stash, self.active, &slashed_unlocking);
|
let final_slashed_amount = pre_slash_total.saturating_sub(self.total);
|
||||||
pre_slash_total.saturating_sub(self.total)
|
T::EventListeners::on_slash(
|
||||||
|
&self.stash,
|
||||||
|
self.active,
|
||||||
|
&slashed_unlocking,
|
||||||
|
final_slashed_amount,
|
||||||
|
);
|
||||||
|
final_slashed_amount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -278,6 +278,7 @@ impl OnStakingUpdate<AccountId, Balance> for EventListenerMock {
|
|||||||
_pool_account: &AccountId,
|
_pool_account: &AccountId,
|
||||||
slashed_bonded: Balance,
|
slashed_bonded: Balance,
|
||||||
slashed_chunks: &BTreeMap<EraIndex, Balance>,
|
slashed_chunks: &BTreeMap<EraIndex, Balance>,
|
||||||
|
_total_slashed: Balance,
|
||||||
) {
|
) {
|
||||||
LedgerSlashPerEra::set((slashed_bonded, slashed_chunks.clone()));
|
LedgerSlashPerEra::set((slashed_bonded, slashed_chunks.clone()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,10 +121,12 @@ pub trait OnStakingUpdate<AccountId, Balance> {
|
|||||||
/// * `slashed_active` - The new bonded balance of the staker after the slash was applied.
|
/// * `slashed_active` - The new bonded balance of the staker after the slash was applied.
|
||||||
/// * `slashed_unlocking` - A map of slashed eras, and the balance of that unlocking chunk after
|
/// * `slashed_unlocking` - A map of slashed eras, and the balance of that unlocking chunk after
|
||||||
/// the slash is applied. Any era not present in the map is not affected at all.
|
/// the slash is applied. Any era not present in the map is not affected at all.
|
||||||
|
/// * `slashed_total` - The aggregated balance that was lost due to the slash.
|
||||||
fn on_slash(
|
fn on_slash(
|
||||||
_stash: &AccountId,
|
_stash: &AccountId,
|
||||||
_slashed_active: Balance,
|
_slashed_active: Balance,
|
||||||
_slashed_unlocking: &BTreeMap<EraIndex, Balance>,
|
_slashed_unlocking: &BTreeMap<EraIndex, Balance>,
|
||||||
|
_slashed_total: Balance,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user