-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Tvl pool staking #14775
base: master
Are you sure you want to change the base?
Tvl pool staking #14775
Changes from 16 commits
f435c64
8b4c90d
fdd0f52
b80f63a
1461e0c
2e8027e
8c69fb8
6ead93a
bcffd00
e936de8
6116180
cb69b5c
5a463c3
c332c0c
ec91ce2
db59e96
35027bc
5098b18
2d7a7b1
672909f
6aa682a
53e99a0
de98dd6
4db0054
4c0dece
db8f4af
f0dc257
ed63e99
69e259e
605838c
6402f1f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -535,6 +535,16 @@ impl<T: Config> PoolMember<T> { | |
} | ||
} | ||
|
||
/// Total balance of the member. | ||
#[cfg(any(feature = "try-runtime", test))] | ||
fn total_balance(&self) -> BalanceOf<T> { | ||
if let Some(pool) = BondedPool::<T>::get(self.pool_id).defensive() { | ||
pool.points_to_balance(self.total_points()) | ||
} else { | ||
Zero::zero() | ||
} | ||
} | ||
|
||
/// Total points of this member, both active and unbonding. | ||
fn total_points(&self) -> BalanceOf<T> { | ||
self.active_points().saturating_add(self.unbonding_points()) | ||
|
@@ -963,9 +973,13 @@ impl<T: Config> BondedPool<T> { | |
} | ||
|
||
/// Issue points to [`Self`] for `new_funds`. | ||
/// Increase the TVL by the amount of funds. | ||
fn issue(&mut self, new_funds: BalanceOf<T>) -> BalanceOf<T> { | ||
let points_to_issue = self.balance_to_point(new_funds); | ||
self.points = self.points.saturating_add(points_to_issue); | ||
TotalValueLocked::<T>::mutate(|tvl| { | ||
tvl.saturating_accrue(new_funds); | ||
}); | ||
points_to_issue | ||
} | ||
|
||
|
@@ -1183,9 +1197,8 @@ impl<T: Config> BondedPool<T> { | |
|
||
/// Bond exactly `amount` from `who`'s funds into this pool. | ||
/// | ||
/// If the bond type is `Create`, `Staking::bond` is called, and `who` | ||
/// is allowed to be killed. Otherwise, `Staking::bond_extra` is called and `who` | ||
/// cannot be killed. | ||
/// If the bond type is `Create`, `Staking::bond` is called, and `who` is allowed to be killed. | ||
/// Otherwise, `Staking::bond_extra` is called and `who` cannot be killed. | ||
PieWol marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// | ||
/// Returns `Ok(points_issues)`, `Err` otherwise. | ||
fn try_bond_funds( | ||
|
@@ -1416,7 +1429,7 @@ impl<T: Config> UnbondPool<T> { | |
new_points | ||
} | ||
|
||
/// 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 and the TVL | ||
PieWol marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// proportionally. | ||
/// | ||
/// This is the opposite of `issue`. | ||
|
@@ -1427,6 +1440,10 @@ impl<T: Config> UnbondPool<T> { | |
self.points = self.points.saturating_sub(points); | ||
self.balance = self.balance.saturating_sub(balance_to_unbond); | ||
|
||
TotalValueLocked::<T>::mutate(|tvl| { | ||
tvl.saturating_reduce(balance_to_unbond); | ||
}); | ||
|
||
balance_to_unbond | ||
} | ||
} | ||
|
@@ -1577,6 +1594,10 @@ pub mod pallet { | |
type MaxUnbonding: Get<u32>; | ||
} | ||
|
||
/// Summary of all pools | ||
PieWol marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#[pallet::storage] | ||
pub type TotalValueLocked<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>; | ||
|
||
/// Minimum amount to bond to join a pool. | ||
#[pallet::storage] | ||
pub type MinJoinBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>; | ||
|
@@ -1795,9 +1816,9 @@ pub mod pallet { | |
CannotWithdrawAny, | ||
/// 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 | ||
/// `Pallet::depositor_min_bond`. The caller does not have nominating | ||
/// permissions for the pool. Members can never unbond to a value below `MinJoinBond`. | ||
/// The depositor can never unbond to a value less than `Pallet::depositor_min_bond`. The | ||
/// caller does not have nominating permissions for the pool. Members can never unbond to a | ||
/// value below `MinJoinBond`. | ||
MinimumBondNotMet, | ||
/// The transaction could not be executed due to overflow risk for the pool. | ||
OverflowRisk, | ||
|
@@ -3077,6 +3098,7 @@ impl<T: Config> Pallet<T> { | |
bonded_pools == reward_pools, | ||
"`BondedPools` and `RewardPools` must all have the EXACT SAME key-set." | ||
); | ||
let mut expected_tvl = Zero::zero(); | ||
|
||
ensure!( | ||
SubPoolsStorage::<T>::iter_keys().all(|k| bonded_pools.contains(&k)), | ||
|
@@ -3169,12 +3191,17 @@ impl<T: Config> Pallet<T> { | |
"depositor must always have MinCreateBond stake in the pool, except for when the \ | ||
pool is being destroyed and the depositor is the last member", | ||
); | ||
expected_tvl += T::Staking::total_stake(&bonded_pool.bonded_account()) | ||
.expect("all pools must have total stake"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than spread the try_state logic for expected TVL across multiple areas of the try-state hook, could you please move it all together? It's fine to iterate over pools again for this purpose. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I put the TVL checks into one place now but the check for TVL integrity is now done via the PoolMembers instead of all BondingPools. I would love some feedback on the way it's done now. |
||
|
||
Ok(()) | ||
})?; | ||
|
||
ensure!( | ||
MaxPoolMembers::<T>::get().map_or(true, |max| all_members <= max), | ||
Error::<T>::MaxPoolMembers | ||
); | ||
assert_eq!(TotalValueLocked::<T>::get(), expected_tvl); | ||
|
||
if level <= 1 { | ||
return Ok(()) | ||
|
@@ -3269,12 +3296,18 @@ impl<T: Config> sp_staking::OnStakingUpdate<T::AccountId, BalanceOf<T>> for Pall | |
// anything here. | ||
slashed_bonded: BalanceOf<T>, | ||
slashed_unlocking: &BTreeMap<EraIndex, BalanceOf<T>>, | ||
total_slashed: BalanceOf<T>, | ||
) { | ||
if let Some(pool_id) = ReversePoolIdLookup::<T>::get(pool_account) { | ||
let mut sub_pools = match SubPoolsStorage::<T>::get(pool_id).defensive() { | ||
if let Some(pool_id) = ReversePoolIdLookup::<T>::get(pool_account).defensive() { | ||
// TODO: bug here: a slash toward a pool who's subpools were missing would not be | ||
// tracked. write a test for it. | ||
Self::deposit_event(Event::<T>::PoolSlashed { pool_id, balance: slashed_bonded }); | ||
|
||
let mut sub_pools = match SubPoolsStorage::<T>::get(pool_id) { | ||
Some(sub_pools) => sub_pools, | ||
None => return, | ||
}; | ||
|
||
for (era, slashed_balance) in slashed_unlocking.iter() { | ||
if let Some(pool) = sub_pools.with_era.get_mut(era) { | ||
pool.balance = *slashed_balance; | ||
|
@@ -3285,9 +3318,11 @@ impl<T: Config> sp_staking::OnStakingUpdate<T::AccountId, BalanceOf<T>> for Pall | |
}); | ||
} | ||
} | ||
|
||
Self::deposit_event(Event::<T>::PoolSlashed { pool_id, balance: slashed_bonded }); | ||
SubPoolsStorage::<T>::insert(pool_id, sub_pools); | ||
|
||
TotalValueLocked::<T>::mutate(|tvl| { | ||
tvl.saturating_reduce(total_slashed); | ||
}); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -724,4 +724,100 @@ pub mod v5 { | |
Ok(()) | ||
} | ||
} | ||
|
||
/// This migration summarizes and initializes the [`TotalValueLocked`] for all Pools. | ||
pub struct VersionUncheckedMigrateV5ToV6<T>(sp_std::marker::PhantomData<T>); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I hope nobody is us using nomination pools on parachains, but we should mention in the migration that it is using lots of weight and may not be suitable for parachains. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for pointing it out. How do you think this mention should be done? Just a comment in the code? |
||
impl<T: Config> OnRuntimeUpgrade for VersionUncheckedMigrateV5ToV6<T> { | ||
fn on_runtime_upgrade() -> Weight { | ||
let migrated = BondedPools::<T>::count(); | ||
let tvl: BalanceOf<T> = BondedPools::<T>::iter() | ||
.map(|(id, inner)| { | ||
T::Staking::total_stake(&BondedPool { id, inner }.bonded_account()) | ||
.unwrap_or_default() | ||
}) | ||
.reduce(|acc, balance| acc + balance) | ||
.unwrap_or_default(); | ||
|
||
TotalValueLocked::<T>::set(tvl); | ||
|
||
log!(info, "Upgraded {} pools", migrated); | ||
|
||
// 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> { | ||
ensure!( | ||
PoolMembers::<T>::iter_keys().count() == PoolMembers::<T>::iter_values().count(), | ||
"There are undecodable PoolMembers in storage. This migration will not fix that." | ||
); | ||
ensure!( | ||
BondedPools::<T>::iter_keys().count() == BondedPools::<T>::iter_values().count(), | ||
"There are undecodable BondedPools in storage. This migration will not fix that." | ||
); | ||
ensure!( | ||
SubPoolsStorage::<T>::iter_keys().count() == | ||
SubPoolsStorage::<T>::iter_values().count(), | ||
"There are undecodable SubPools in storage. This migration will not fix that." | ||
); | ||
ensure!( | ||
Metadata::<T>::iter_keys().count() == Metadata::<T>::iter_values().count(), | ||
"There are undecodable Metadata in storage. This migration will not fix that." | ||
); | ||
|
||
Ok(Vec::new()) | ||
} | ||
|
||
#[cfg(feature = "try-runtime")] | ||
fn post_upgrade(data: Vec<u8>) -> Result<(), TryRuntimeError> { | ||
// ensure all TotalValueLocked::<T> contains a value greater zero if any instances of | ||
// BondedPools exist. | ||
if BondedPools::<T>::count() > 0 { | ||
let zero: BalanceOf<T> = Zero::zero(); | ||
ensure!(TotalValueLocked::<T>::get() > zero, "tvl written incorrectly"); | ||
PieWol marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
ensure!( | ||
Pallet::<T>::on_chain_storage_version() >= 6, | ||
"nomination-pools::migration::v6: wrong storage version" | ||
); | ||
|
||
// These should not have been touched - just in case. | ||
ensure!( | ||
PoolMembers::<T>::iter_keys().count() == PoolMembers::<T>::iter_values().count(), | ||
"There are undecodable PoolMembers in storage." | ||
); | ||
ensure!( | ||
BondedPools::<T>::iter_keys().count() == BondedPools::<T>::iter_values().count(), | ||
"There are undecodable BondedPools in storage." | ||
); | ||
ensure!( | ||
SubPoolsStorage::<T>::iter_keys().count() == | ||
SubPoolsStorage::<T>::iter_values().count(), | ||
"There are undecodable SubPools in storage." | ||
); | ||
ensure!( | ||
Metadata::<T>::iter_keys().count() == Metadata::<T>::iter_values().count(), | ||
"There are undecodable Metadata in storage." | ||
); | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
/// [`VersionUncheckedMigrateV5ToV6`] wrapped in a | ||
/// [`frame_support::migrations::VersionedRuntimeUpgrade`], ensuring the migration is only | ||
/// performed when on-chain version is 5. | ||
#[cfg(feature = "experimental")] | ||
pub type VersionCheckedMigrateV5ToV6<T, I> = frame_support::migrations::VersionedRuntimeUpgrade< | ||
5, | ||
6, | ||
VersionUncheckedMigrateV5ToV6<T, I>, | ||
crate::pallet::Pallet<T, I>, | ||
<T as frame_system::Config>::DbWeight, | ||
>; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.