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

Commit

Permalink
nomination-pools: allow pool-ids to be reused (#12407)
Browse files Browse the repository at this point in the history
* initial

* logic check

* do_create

* cargo fmt

* fixes

* ensure_signed

* update

* cargo fmt

* remove unused import
  • Loading branch information
Doordashcon authored Oct 29, 2022
1 parent e8cbc1e commit 21a7f8e
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 60 deletions.
159 changes: 99 additions & 60 deletions frame/nomination-pools/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
//!
//! ## Key terms
//!
//! * pool id: A unique identifier of each pool. Set to u12
//! * pool id: A unique identifier of each pool. Set to u32.
//! * bonded pool: Tracks the distribution of actively staked funds. See [`BondedPool`] and
//! [`BondedPoolInner`].
//! * reward pool: Tracks rewards earned by actively staked funds. See [`RewardPool`] and
Expand All @@ -47,7 +47,7 @@
//! exactly the same rules and conditions as a normal staker. Its bond increases or decreases as
//! members join, it can `nominate` or `chill`, and might not even earn staking rewards if it is
//! not nominating proper validators.
//! * reward account: A similar key-less account, that is set as the `Payee` account fo the bonded
//! * reward account: A similar key-less account, that is set as the `Payee` account for the bonded
//! account for all staking rewards.
//!
//! ## Usage
Expand Down Expand Up @@ -1429,6 +1429,10 @@ pub mod pallet {
Defensive(DefensiveError),
/// Partial unbonding now allowed permissionlessly.
PartialUnbondNotAllowedPermissionlessly,
/// Pool id currently in use.
PoolIdInUse,
/// Pool id provided is not correct/usable.
InvalidPoolId,
}

#[derive(Encode, Decode, PartialEq, TypeInfo, frame_support::PalletError, RuntimeDebug)]
Expand Down Expand Up @@ -1850,73 +1854,38 @@ pub mod pallet {
nominator: AccountIdLookupOf<T>,
state_toggler: AccountIdLookupOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let root = T::Lookup::lookup(root)?;
let nominator = T::Lookup::lookup(nominator)?;
let state_toggler = T::Lookup::lookup(state_toggler)?;

ensure!(amount >= Pallet::<T>::depositor_min_bond(), Error::<T>::MinimumBondNotMet);
ensure!(
MaxPools::<T>::get()
.map_or(true, |max_pools| BondedPools::<T>::count() < max_pools),
Error::<T>::MaxPools
);
ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
let depositor = ensure_signed(origin)?;

let pool_id = LastPoolId::<T>::try_mutate::<_, Error<T>, _>(|id| {
*id = id.checked_add(1).ok_or(Error::<T>::OverflowRisk)?;
Ok(*id)
})?;
let mut bonded_pool = BondedPool::<T>::new(
pool_id,
PoolRoles {
root: Some(root),
nominator: Some(nominator),
state_toggler: Some(state_toggler),
depositor: who.clone(),
},
);

bonded_pool.try_inc_members()?;
let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create)?;

T::Currency::transfer(
&who,
&bonded_pool.reward_account(),
T::Currency::minimum_balance(),
ExistenceRequirement::AllowDeath,
)?;
Self::do_create(depositor, amount, root, nominator, state_toggler, pool_id)
}

PoolMembers::<T>::insert(
who.clone(),
PoolMember::<T> {
pool_id,
points,
last_recorded_reward_counter: Zero::zero(),
unbonding_eras: Default::default(),
},
);
RewardPools::<T>::insert(
pool_id,
RewardPool::<T> {
last_recorded_reward_counter: Zero::zero(),
last_recorded_total_payouts: Zero::zero(),
total_rewards_claimed: Zero::zero(),
},
);
ReversePoolIdLookup::<T>::insert(bonded_pool.bonded_account(), pool_id);
/// Create a new delegation pool with a previously used pool id
///
/// # Arguments
///
/// same as `create` with the inclusion of
/// * `pool_id` - `A valid PoolId.
#[pallet::weight(T::WeightInfo::create())]
#[transactional]
pub fn create_with_pool_id(
origin: OriginFor<T>,
#[pallet::compact] amount: BalanceOf<T>,
root: AccountIdLookupOf<T>,
nominator: AccountIdLookupOf<T>,
state_toggler: AccountIdLookupOf<T>,
pool_id: PoolId,
) -> DispatchResult {
let depositor = ensure_signed(origin)?;

Self::deposit_event(Event::<T>::Created { depositor: who.clone(), pool_id });
ensure!(!BondedPools::<T>::contains_key(pool_id), Error::<T>::PoolIdInUse);
ensure!(pool_id < LastPoolId::<T>::get(), Error::<T>::InvalidPoolId);

Self::deposit_event(Event::<T>::Bonded {
member: who,
pool_id,
bonded: amount,
joined: true,
});
bonded_pool.put();

Ok(())
Self::do_create(depositor, amount, root, nominator, state_toggler, pool_id)
}

/// Nominate on behalf of the pool.
Expand Down Expand Up @@ -2340,6 +2309,76 @@ impl<T: Config> Pallet<T> {
Ok(pending_rewards)
}

fn do_create(
who: T::AccountId,
amount: BalanceOf<T>,
root: AccountIdLookupOf<T>,
nominator: AccountIdLookupOf<T>,
state_toggler: AccountIdLookupOf<T>,
pool_id: PoolId,
) -> DispatchResult {
let root = T::Lookup::lookup(root)?;
let nominator = T::Lookup::lookup(nominator)?;
let state_toggler = T::Lookup::lookup(state_toggler)?;

ensure!(amount >= Pallet::<T>::depositor_min_bond(), Error::<T>::MinimumBondNotMet);
ensure!(
MaxPools::<T>::get().map_or(true, |max_pools| BondedPools::<T>::count() < max_pools),
Error::<T>::MaxPools
);
ensure!(!PoolMembers::<T>::contains_key(&who), Error::<T>::AccountBelongsToOtherPool);
let mut bonded_pool = BondedPool::<T>::new(
pool_id,
PoolRoles {
root: Some(root),
nominator: Some(nominator),
state_toggler: Some(state_toggler),
depositor: who.clone(),
},
);

bonded_pool.try_inc_members()?;
let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create)?;

T::Currency::transfer(
&who,
&bonded_pool.reward_account(),
T::Currency::minimum_balance(),
ExistenceRequirement::AllowDeath,
)?;

PoolMembers::<T>::insert(
who.clone(),
PoolMember::<T> {
pool_id,
points,
last_recorded_reward_counter: Zero::zero(),
unbonding_eras: Default::default(),
},
);
RewardPools::<T>::insert(
pool_id,
RewardPool::<T> {
last_recorded_reward_counter: Zero::zero(),
last_recorded_total_payouts: Zero::zero(),
total_rewards_claimed: Zero::zero(),
},
);
ReversePoolIdLookup::<T>::insert(bonded_pool.bonded_account(), pool_id);

Self::deposit_event(Event::<T>::Created { depositor: who.clone(), pool_id });

Self::deposit_event(Event::<T>::Bonded {
member: who,
pool_id,
bonded: amount,
joined: true,
});
bonded_pool.put();

Ok(())
}

/// Ensure the correctness of the state of this pallet.
///
/// This should be valid before or after each state transition of this pallet.
Expand Down
38 changes: 38 additions & 0 deletions frame/nomination-pools/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4201,6 +4201,44 @@ mod create {
);
});
}

#[test]
fn create_with_pool_id_works() {
ExtBuilder::default().build_and_execute(|| {
let ed = Balances::minimum_balance();

Balances::make_free_balance_be(&11, StakingMock::minimum_bond() + ed);
assert_ok!(Pools::create(
RuntimeOrigin::signed(11),
StakingMock::minimum_bond(),
123,
456,
789
));

assert_eq!(Balances::free_balance(&11), 0);
// delete the initial pool created, then pool_Id `1` will be free

assert_noop!(
Pools::create_with_pool_id(RuntimeOrigin::signed(12), 20, 234, 654, 783, 1),
Error::<Runtime>::PoolIdInUse
);

assert_noop!(
Pools::create_with_pool_id(RuntimeOrigin::signed(12), 20, 234, 654, 783, 3),
Error::<Runtime>::InvalidPoolId
);

// start dismantling the pool.
assert_ok!(Pools::set_state(RuntimeOrigin::signed(902), 1, PoolState::Destroying));
assert_ok!(fully_unbond_permissioned(10));

CurrentEra::set(3);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 10));

assert_ok!(Pools::create_with_pool_id(RuntimeOrigin::signed(10), 20, 234, 654, 783, 1));
});
}
}

mod nominate {
Expand Down

0 comments on commit 21a7f8e

Please sign in to comment.