Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tvl pool staking #1322

Merged
merged 57 commits into from
Oct 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
272ac35
migrate from substrate PR
PieWol Aug 30, 2023
7e7c16c
Merge remote-tracking branch 'origin/master' into tvl-pool-staking
PieWol Aug 30, 2023
a8fef3a
Merge remote-tracking branch 'origin/master' into tvl-pool-staking
PieWol Aug 30, 2023
b1f9927
use ensure in try_state
PieWol Aug 31, 2023
7b75db0
add doc comment
PieWol Aug 31, 2023
994315f
slashing doc comments
PieWol Aug 31, 2023
a38564f
simplify on_slash
PieWol Sep 2, 2023
8480667
Update substrate/frame/nomination-pools/src/mock.rs comment
PieWol Sep 2, 2023
a4f63f8
remove a test + adjust comments
PieWol Sep 2, 2023
ff5c2f5
enhance `post_upgrade`
PieWol Sep 2, 2023
6dfb210
Merge branch 'master' into tvl-pool-staking
PieWol Sep 2, 2023
5c1bb43
fixed migration
PieWol Sep 3, 2023
68527e9
improved readability
PieWol Sep 3, 2023
6d2bd96
Merge branch 'master' into tvl-pool-staking
PieWol Sep 3, 2023
2e55e65
comments improved
PieWol Sep 3, 2023
c24937b
Merge branch 'tvl-pool-staking' of github.com:PieWol/polkadot-sdk int…
PieWol Sep 3, 2023
babe2ed
keep events in the old order.
PieWol Sep 3, 2023
46940eb
Merge branch 'master' into tvl-pool-staking
kianenigma Sep 6, 2023
2e2dee8
revert useless conversion
PieWol Sep 8, 2023
1daa2e1
remove useless linking brackets
PieWol Sep 8, 2023
cbccde8
formatting
PieWol Sep 8, 2023
2b90665
fix unwanted defensive error spam
PieWol Sep 8, 2023
474f743
Merge branch 'tvl-pool-staking' of github.com:PieWol/polkadot-sdk int…
PieWol Sep 8, 2023
28aa4e9
log defensive error
PieWol Sep 8, 2023
34a032a
remove unwanted upgrade checks
PieWol Sep 11, 2023
3672950
defensively lookup SubPools
PieWol Sep 11, 2023
deb53ff
defensive TVL reduce
PieWol Sep 11, 2023
a05dc7b
integrate try_state iterations
PieWol Sep 11, 2023
5d07921
fix test
PieWol Sep 11, 2023
aadbddc
no defensive in off-chain code
PieWol Sep 11, 2023
fbf65bd
fix TVL doc
PieWol Sep 11, 2023
4b076a2
add defensive to TVL adjustment
PieWol Sep 11, 2023
b720191
Merge branch 'master' into tvl-pool-staking
PieWol Sep 11, 2023
f535298
fix test slash
PieWol Sep 13, 2023
9b94561
Merge branch 'tvl-pool-staking' of github.com:PieWol/polkadot-sdk int…
PieWol Sep 13, 2023
26f2baa
Update mock.rs error
PieWol Sep 13, 2023
b4e9966
showcase test for tvl diff
PieWol Sep 14, 2023
389fdf5
add migration warning
PieWol Sep 16, 2023
9f9f821
reintroduce test
PieWol Sep 29, 2023
5764dfc
fix test
PieWol Sep 29, 2023
fd30069
Merge branch 'master' into tvl-pool-staking
PieWol Sep 29, 2023
b3fab33
remove experimental flag
PieWol Sep 29, 2023
01bcdc5
formatting and rust docs
PieWol Sep 29, 2023
febaa2e
Merge branch 'master' into tvl-pool-staking
PieWol Sep 29, 2023
a361d2a
westend migration added
PieWol Sep 29, 2023
5300b8d
Merge branch 'tvl-pool-staking' of github.com:PieWol/polkadot-sdk int…
PieWol Sep 29, 2023
27226d8
Merge branch 'master' into tvl-pool-staking
PieWol Sep 29, 2023
d68d93e
formatting
PieWol Sep 29, 2023
3af3f5d
fix migration
PieWol Sep 29, 2023
0a85748
merging leftovers cleared
PieWol Sep 29, 2023
8489cda
migration fix
PieWol Sep 29, 2023
8d4ccb7
visibilty fix
PieWol Sep 29, 2023
1a22b0a
pallet version 7
PieWol Sep 29, 2023
5bfd72d
Merge branch 'master' into tvl-pool-staking
liamaharon Sep 30, 2023
d6f5646
make the unchecked migration module private
Ank4n Sep 30, 2023
c9287cd
rust doc update
Ank4n Oct 1, 2023
71175c9
".git/.scripts/commands/fmt/fmt.sh"
Oct 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions polkadot/runtime/westend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1508,6 +1508,7 @@ pub mod migrations {
paras_registrar::migration::VersionCheckedMigrateToV1<Runtime, ()>,
pallet_nomination_pools::migration::versioned_migrations::V5toV6<Runtime>,
pallet_referenda::migration::v1::MigrateV0ToV1<Runtime, ()>,
pallet_nomination_pools::migration::versioned_migrations::V6ToV7<Runtime>,
);
}

Expand Down
155 changes: 120 additions & 35 deletions substrate/frame/nomination-pools/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
fn total_points(&self) -> BalanceOf<T> {
self.active_points().saturating_add(self.unbonding_points())
Expand Down Expand Up @@ -1189,11 +1214,11 @@ impl<T: Config> BondedPool<T> {
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`
/// is allowed to be killed. Otherwise, `Staking::bond_extra` is called and `who`
/// cannot be killed.
/// If the bond is [`BondType::Create`], [`Staking::bond`] is called, and `who` is allowed to be
/// killed. Otherwise, [`Staking::bond_extra`] is called and `who` cannot be killed.
///
/// Returns `Ok(points_issues)`, `Err` otherwise.
fn try_bond_funds(
Expand Down Expand Up @@ -1224,6 +1249,9 @@ impl<T: Config> BondedPool<T> {
// found, we exit early.
BondType::Later => T::Staking::bond_extra(&bonded_account, amount)?,
}
TotalValueLocked::<T>::mutate(|tvl| {
tvl.saturating_accrue(amount);
});

Ok(points_issued)
}
Expand All @@ -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> {
PieWol marked this conversation as resolved.
Show resolved Hide resolved
let bonded_account = self.bonded_account();
PieWol marked this conversation as resolved.
Show resolved Hide resolved

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.
Expand Down Expand Up @@ -1437,9 +1486,7 @@ impl<T: Config> UnbondPool<T> {
}

/// Dissolve some points from the unbonding pool, reducing the balance of the pool
/// proportionally.
///
/// This is the opposite of `issue`.
/// proportionally. This is the opposite of `issue`.
///
/// Returns the actual amount of `Balance` that was removed from the pool.
fn dissolve(&mut self, points: BalanceOf<T>) -> BalanceOf<T> {
Expand Down Expand Up @@ -1525,7 +1572,7 @@ pub mod pallet {
use sp_runtime::Perbill;

/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(6);
const STORAGE_VERSION: StorageVersion = StorageVersion::new(7);

#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
Expand Down Expand Up @@ -1602,6 +1649,14 @@ pub mod pallet {
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.
#[pallet::storage]
pub type MinJoinBond<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;
Expand Down Expand Up @@ -1825,9 +1880,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,
Expand Down Expand Up @@ -2114,7 +2169,7 @@ pub mod pallet {

/// 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
/// would probably see an error like `NoMoreChunks` emitted from the staking system when
/// they attempt to unbond.
Expand All @@ -2127,10 +2182,12 @@ pub mod pallet {
) -> DispatchResult {
let _ = ensure_signed(origin)?;
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
// is destroying then `withdraw_unbonded` can be used.
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)?;
PieWol marked this conversation as resolved.
Show resolved Hide resolved

Ok(())
}

Expand Down Expand Up @@ -2180,9 +2237,8 @@ pub mod pallet {
ensure!(!withdrawn_points.is_empty(), Error::<T>::CannotWithdrawAny);

// Before calculating the `balance_to_unbond`, we call withdraw unbonded to ensure the
// `transferable_balance` is correct.
let stash_killed =
T::Staking::withdraw_unbonded(bonded_pool.bonded_account(), num_slashing_spans)?;
// `transferrable_balance` is correct.
let stash_killed = bonded_pool.withdraw_from_staking(num_slashing_spans)?;

// defensive-only: the depositor puts enough funds into the stash so that it will only
// be destroyed when they are leaving.
Expand Down Expand Up @@ -2846,12 +2902,9 @@ impl<T: Config> Pallet<T> {
},
(false, false) => {
// Equivalent to (current_points / current_balance) * new_funds
balance(
u256(current_points)
.saturating_mul(u256(new_funds))
// We check for zero above
.div(u256(current_balance)),
)
balance(u256(current_points).saturating_mul(u256(new_funds)))
// We check for zero above
.div(current_balance)
},
}
}
Expand All @@ -2871,9 +2924,12 @@ impl<T: Config> Pallet<T> {
}

// Equivalent of (current_balance / current_points) * points
balance(u256(current_balance).saturating_mul(u256(points)))
// We check for zero above
.div(current_points)
balance(
u256(current_balance)
.saturating_mul(u256(points))
// We check for zero above
.div(u256(current_points)),
PieWol marked this conversation as resolved.
Show resolved Hide resolved
)
}

/// If the member has some rewards, transfer a payout from the reward pool to the member.
Expand Down Expand Up @@ -3242,6 +3298,7 @@ impl<T: Config> Pallet<T> {
let mut pools_members = BTreeMap::<PoolId, u32>::new();
let mut pools_members_pending_rewards = BTreeMap::<PoolId, BalanceOf<T>>::new();
let mut all_members = 0u32;
let mut total_balance_members = Default::default();
PoolMembers::<T>::iter().try_for_each(|(_, d)| -> Result<(), TryRuntimeError> {
let bonded_pool = BondedPools::<T>::get(d.pool_id).unwrap();
ensure!(!d.total_points().is_zero(), "No member should have zero points");
Expand All @@ -3257,6 +3314,7 @@ impl<T: Config> Pallet<T> {
let pending_rewards = d.pending_rewards(current_rc).unwrap();
*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.
total_balance_members += d.total_balance();

Ok(())
})?;
Expand All @@ -3280,6 +3338,7 @@ impl<T: Config> Pallet<T> {
Ok(())
})?;

let mut expected_tvl: BalanceOf<T> = Default::default();
BondedPools::<T>::iter().try_for_each(|(id, inner)| -> Result<(), TryRuntimeError> {
let bonded_pool = BondedPool { id, inner };
ensure!(
Expand All @@ -3300,13 +3359,28 @@ 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()).unwrap_or_default();

Ok(())
})?;

ensure!(
MaxPoolMembers::<T>::get().map_or(true, |max| all_members <= max),
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."
);
PieWol marked this conversation as resolved.
Show resolved Hide resolved

if level <= 1 {
return Ok(())
}
Expand Down Expand Up @@ -3424,31 +3498,42 @@ impl<T: Config> 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(
pool_account: &T::AccountId,
// Bonded balance is always read directly from staking, therefore we don't need to update
// anything here.
slashed_bonded: BalanceOf<T>,
slashed_unlocking: &BTreeMap<EraIndex, BalanceOf<T>>,
total_slashed: BalanceOf<T>,
Ank4n marked this conversation as resolved.
Show resolved Hide resolved
) {
if let Some(pool_id) = ReversePoolIdLookup::<T>::get(pool_account) {
let mut sub_pools = match SubPoolsStorage::<T>::get(pool_id).defensive() {
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) {
let Some(pool_id) = ReversePoolIdLookup::<T>::get(pool_account) else { return };
// As the slashed account belongs to a `BondedPool` the `TotalValueLocked` decreases and
// an event is emitted.
TotalValueLocked::<T>::mutate(|tvl| {
tvl.defensive_saturating_reduce(total_slashed);
});

if let Some(mut sub_pools) = SubPoolsStorage::<T>::get(pool_id) {
PieWol marked this conversation as resolved.
Show resolved Hide resolved
// 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() {
Ank4n marked this conversation as resolved.
Show resolved Hide resolved
pool.balance = *slashed_balance;
Self::deposit_event(Event::<T>::UnbondingPoolSlashed {
era: *era,
pool_id,
balance: *slashed_balance,
});
}
}

Self::deposit_event(Event::<T>::PoolSlashed { pool_id, balance: slashed_bonded });
});
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 });
}
}
Loading
Loading