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 3 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
8 changes: 6 additions & 2 deletions substrate/frame/nomination-pools/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ pallet-balances = { path = "../balances" }
sp-tracing = { path = "../../primitives/tracing" }

[features]
default = [ "std" ]
fuzzing = [ "pallet-balances", "sp-tracing" ]
default = ["std"]
# Enable `VersionedRuntimeUpgrade` for the migrations that is currently still experimental.
experimental = [
"frame-support/experimental"
]
fuzzing = ["pallet-balances", "sp-tracing"]
std = [
"codec/std",
"frame-support/std",
Expand Down
151 changes: 120 additions & 31 deletions substrate/frame/nomination-pools/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,35 @@ 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(&mut self) -> BalanceOf<T> {
let pool = match BondedPool::<T>::get(self.pool_id).defensive() {
PieWol marked this conversation as resolved.
Show resolved Hide resolved
Some(pool) => pool,
None => return Zero::zero(),
};

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.saturating_add(era_pool.point_to_balance(*unlocked_points))
},
);

active_balance.saturating_add(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 @@ -963,6 +992,7 @@ impl<T: Config> BondedPool<T> {
}

/// Issue points to [`Self`] for `new_funds`.
/// Increase the [`TotalValueLocked`] by `new_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);
Expand Down Expand Up @@ -1183,9 +1213,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 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 @@ -1216,6 +1245,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 @@ -1231,6 +1263,19 @@ impl<T: Config> BondedPool<T> {
});
};
}

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.saturating_sub(T::Staking::total_stake(&bonded_account).unwrap_or_default());
PieWol marked this conversation as resolved.
Show resolved Hide resolved
TotalValueLocked::<T>::mutate(|tvl| {
tvl.saturating_reduce(diff);
});
outcome
}
}

/// A reward pool.
Expand Down Expand Up @@ -1416,8 +1461,8 @@ impl<T: Config> UnbondPool<T> {
new_points
}

/// Dissolve some points from the unbonding pool, reducing the balance of the pool
/// proportionally.
/// Dissolve some points from the unbonding pool, reducing the balance of the pool and the
/// [`TotalValueLocked`] proportionally.
///
/// This is the opposite of `issue`.
///
Expand Down Expand Up @@ -1504,7 +1549,7 @@ pub mod pallet {
use sp_runtime::Perbill;

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

#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
Expand Down Expand Up @@ -1577,6 +1622,14 @@ pub mod pallet {
type MaxUnbonding: Get<u32>;
}

/// The sum of funds across all pools.
///
/// This might be higher but never lower than the actual sum of the currently unlocking and
PieWol marked this conversation as resolved.
Show resolved Hide resolved
/// bonded funds as this is only decreased if a user withdraws unlocked funds or a slash
/// happened.
#[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 @@ -1795,9 +1848,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 @@ -2074,7 +2127,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 @@ -2087,10 +2140,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 @@ -2141,8 +2196,7 @@ pub mod pallet {

// Before calculating the `balance_to_unbond`, we call withdraw unbonded to ensure the
// `transferrable_balance` is correct.
let stash_killed =
T::Staking::withdraw_unbonded(bonded_pool.bonded_account(), num_slashing_spans)?;
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 @@ -2796,9 +2850,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 @@ -3169,8 +3226,35 @@ 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",
);

let expected_tvl: BalanceOf<T> = BondedPools::<T>::iter()
PieWol marked this conversation as resolved.
Show resolved Hide resolved
.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();

assert_eq!(
PieWol marked this conversation as resolved.
Show resolved Hide resolved
TotalValueLocked::<T>::get(),
expected_tvl,
"TVL deviates from the actual sum of funds of all Pools."
);

let total_balance_members: BalanceOf<T> = PoolMembers::<T>::iter()
PieWol marked this conversation as resolved.
Show resolved Hide resolved
.map(|(_, mut member)| member.total_balance())
.reduce(|acc, total_balance| acc + total_balance)
.unwrap_or_default();

assert!(
TotalValueLocked::<T>::get() <= total_balance_members,
"TVL must be equal to or less than the total balance of all PoolMembers."
);
Ok(())
})?;

ensure!(
MaxPoolMembers::<T>::get().map_or(true, |max| all_members <= max),
Error::<T>::MaxPoolMembers
Expand Down Expand Up @@ -3269,25 +3353,30 @@ 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>,
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,
if let Some(pool_id) = ReversePoolIdLookup::<T>::get(pool_account).defensive() {
match SubPoolsStorage::<T>::get(pool_id) {
Some(mut sub_pools) => {
for (era, slashed_balance) in slashed_unlocking.iter() {
if let Some(pool) = sub_pools.with_era.get_mut(era) {
pool.balance = *slashed_balance;
Self::deposit_event(Event::<T>::UnbondingPoolSlashed {
era: *era,
pool_id,
balance: *slashed_balance,
});
}
}
SubPoolsStorage::<T>::insert(pool_id, sub_pools);
},
None => {},
};
liamaharon marked this conversation as resolved.
Show resolved Hide resolved
for (era, slashed_balance) in slashed_unlocking.iter() {
if let Some(pool) = sub_pools.with_era.get_mut(era) {
pool.balance = *slashed_balance;
Self::deposit_event(Event::<T>::UnbondingPoolSlashed {
era: *era,
pool_id,
balance: *slashed_balance,
});
}
}

TotalValueLocked::<T>::mutate(|tvl| {
tvl.saturating_reduce(total_slashed);
});
Self::deposit_event(Event::<T>::PoolSlashed { pool_id, balance: slashed_bonded });
SubPoolsStorage::<T>::insert(pool_id, sub_pools);
}
}
}
99 changes: 99 additions & 0 deletions substrate/frame/nomination-pools/src/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -724,4 +724,103 @@ pub mod v5 {
Ok(())
}
}

/// This migration accumulates and initializes the [`TotalValueLocked`] for all pools.
pub struct VersionUncheckedMigrateV5ToV6<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for VersionUncheckedMigrateV5ToV6<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> = 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();

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> {
ensure!(
PieWol marked this conversation as resolved.
Show resolved Hide resolved
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> {
PieWol marked this conversation as resolved.
Show resolved Hide resolved
// ensure [`TotalValueLocked`] contains a value greater zero if any instances of
// BondedPools exist.
if !BondedPools::<T>::count().is_zero() {
ensure!(!TotalValueLocked::<T>::get().is_zero(), "tvl written incorrectly");
}

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> = frame_support::migrations::VersionedRuntimeUpgrade<
5,
6,
VersionUncheckedMigrateV5ToV6<T>,
crate::pallet::Pallet<T>,
<T as frame_system::Config>::DbWeight,
>;
}
Loading