Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

store nomination pools total value locked on-chain #13319

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
52 changes: 42 additions & 10 deletions frame/nomination-pools/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,16 @@ impl<T: Config> PoolMember<T> {
}
}

/// Total balance of the member.
#[cfg(any(feature = "try-runtime", test))]
fn total_balance(&self) -> BalanceOf<T> {
// TODO
self.active_balance().saturating_add(self.unbonding_eras
.as_ref()
.iter()
.fold(BalanceOf::<T>::zero(), |acc, (_, v)| acc.saturating_add(*v)))
}

/// 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 @@ -684,6 +694,9 @@ impl<T: Config> BondedPool<T> {
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
}

Expand Down Expand Up @@ -900,9 +913,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.
///
/// Returns `Ok(points_issues)`, `Err` otherwise.
fn try_bond_funds(
Expand Down Expand Up @@ -1097,6 +1109,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
}
}
Expand Down Expand Up @@ -1247,6 +1263,10 @@ pub mod pallet {
type MaxUnbonding: Get<u32>;
}

/// Summary of all pools
#[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 @@ -1439,9 +1459,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 @@ -2464,6 +2484,7 @@ impl<T: Config> Pallet<T> {
let bonded_pools = BondedPools::<T>::iter_keys().collect::<Vec<_>>();
let reward_pools = RewardPools::<T>::iter_keys().collect::<Vec<_>>();
assert_eq!(bonded_pools, reward_pools);
let mut expected_tvl = Zero::zero();

assert!(Metadata::<T>::iter_keys().all(|k| bonded_pools.contains(&k)));
assert!(SubPoolsStorage::<T>::iter_keys().all(|k| bonded_pools.contains(&k)));
Expand Down Expand Up @@ -2493,6 +2514,7 @@ impl<T: Config> Pallet<T> {
assert!(!d.total_points().is_zero(), "no member should have zero points: {:?}", d);
*pools_members.entry(d.pool_id).or_default() += 1;
all_members += 1;
expected_tvl += d.tota

let reward_pool = RewardPools::<T>::get(d.pool_id).unwrap();
if !bonded_pool.points.is_zero() {
Expand Down Expand Up @@ -2535,8 +2557,11 @@ 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");
});
assert!(MaxPoolMembers::<T>::get().map_or(true, |max| all_members <= max));
assert_eq!(TotalValueLocked::<T>::get(), expected_tvl);

if level <= 1 {
return Ok(())
Expand Down Expand Up @@ -2586,12 +2611,21 @@ impl<T: Config> OnStakerSlash<T::AccountId, BalanceOf<T>> for Pallet<T> {
// 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 });

TotalValueLocked::<T>::mutate(|tvl| {
tvl.saturating_reduce(total_slashed);
});

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;
Expand All @@ -2602,8 +2636,6 @@ impl<T: Config> OnStakerSlash<T::AccountId, BalanceOf<T>> for Pallet<T> {
});
}
}

Self::deposit_event(Event::<T>::PoolSlashed { pool_id, balance: slashed_bonded });
SubPoolsStorage::<T>::insert(pool_id, sub_pools);
}
}
Expand Down
38 changes: 27 additions & 11 deletions frame/nomination-pools/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ parameter_types! {
pub static CurrentEra: EraIndex = 0;
pub static BondingDuration: EraIndex = 3;
pub storage BondedBalanceMap: BTreeMap<AccountId, Balance> = Default::default();
pub storage UnbondingBalanceMap: BTreeMap<AccountId, Balance> = Default::default();
// mao from user, to a vec of era to amount being unlocked in that era.
pub storage UnbondingBalanceMap: BTreeMap<AccountId, Vec<(EraIndex, Balance)>> = Default::default();
#[derive(Clone, PartialEq)]
pub static MaxUnbonding: u32 = 8;
pub static StakingMinBond: Balance = 10;
Expand All @@ -42,6 +43,14 @@ impl StakingMock {
x.insert(who, bonded);
BondedBalanceMap::set(&x)
}

pub(crate) fn slash_to(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 {
Expand Down Expand Up @@ -78,8 +87,11 @@ impl sp_staking::StakingInterface for StakingMock {
let mut x = BondedBalanceMap::get();
*x.get_mut(who).unwrap() = x.get_mut(who).unwrap().saturating_sub(amount);
BondedBalanceMap::set(&x);

let era = Self::current_era();
let unlocking_at = era + Self::bonding_duration();
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);
Ok(())
}
Expand All @@ -89,11 +101,13 @@ impl sp_staking::StakingInterface for StakingMock {
}

fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result<bool, DispatchError> {
// Simulates removing unlocking chunks and only having the bonded balance locked
let mut x = UnbondingBalanceMap::get();
x.remove(&who);
UnbondingBalanceMap::set(&x);
let mut unbonding_map = UnbondingBalanceMap::get();
let staker_map = unbonding_map.get_mut(&who).ok_or("not a staker")?;

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())
}

Expand All @@ -118,13 +132,15 @@ impl sp_staking::StakingInterface for StakingMock {

fn stake(who: &Self::AccountId) -> Result<Stake<Self>, DispatchError> {
match (
UnbondingBalanceMap::get().get(who).map(|v| *v),
BondedBalanceMap::get().get(who).map(|v| *v),
UnbondingBalanceMap::get().get(who).cloned(),
BondedBalanceMap::get().get(who).cloned(),
) {
(None, None) => Err(DispatchError::Other("balance not found")),
(Some(v), None) => Ok(Stake { total: v, active: 0, stash: *who }),
(None, None) => Err(DispatchError::Other("stake not found")),
(Some(m), None) =>
Ok(Stake { total: m.into_iter().map(|(_, v)| v).sum(), active: 0, stash: *who }),
(None, Some(v)) => Ok(Stake { total: v, active: v, stash: *who }),
(Some(a), Some(b)) => Ok(Stake { total: a + b, active: b, stash: *who }),
(Some(m), Some(v)) =>
Ok(Stake { total: v + m.into_iter().map(|(_, v)| v).sum::<u128>(), active: v, stash: *who }),
}
}

Expand Down
Loading