-
Notifications
You must be signed in to change notification settings - Fork 696
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
Reap pool member whose balance goes below ED #5769
base: master
Are you sure you want to change the base?
Conversation
} | ||
} | ||
|
||
Ok(()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is basically a service for the greater good of the chain. If the account has been successfully reaped it would make sense to return fees at the end.
/// Remove the member if stake falls below the existential deposit. | ||
#[pallet::call_index(26)] | ||
// FIXME weight functions | ||
#[pallet::weight(T::WeightInfo::reap_member_below_ed())] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could just put a constant value in here before you create the benchmarks. then the compiler won't complain.
let pool = Pool::from(bonded_pool.bonded_account()); | ||
|
||
// Ensure any dangling delegation is withdrawn. | ||
let dangling_withdrawal = match T::StakeAdapter::member_delegation_balance(member.clone()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The general approach looks fine to me. Good next step would be to write an integration test (delegate-stake and transfer stake) to ensure the stake is updated and storages are cleaned as expected.
What should happen If the active balance of the pool creator falls below ED? To be aligned with the logic of withdrawal, I suppose if it's removed, the pool should be destroyed? Is it appropriate to make Given 3 members with 15/10/5 deposit and a 15 slash, they will be slashed by 7.5/5/2.5 in proportion to their stake, rounded up to 8/5/3 respectively. Sounds right? /// 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>; The value of |
Rounding the remainding value does not sound right at all. Where did you get that from? |
That's what I deducted from the resulted balances. 👆 Now that I think of it, maybe this is specific to the tests coz they are using ints for simpler comparisons. If that's the case then never mind. :)) |
I think that using ints might be the reason, but I would need to check the code to see where the actual differences come from. I don't think the doc needs an update. |
@@ -1880,6 +1881,28 @@ impl<T: Config> StakingInterface for Pallet<T> { | |||
} | |||
} | |||
|
|||
fn force_withdraw(stash: &Self::AccountId, amount: Self::Balance) -> DispatchResult { | |||
let mut ledger = Self::ledger(Stash(stash.clone()))?; | |||
ledger.active = ledger.active.saturating_sub(amount); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be you should look to withdraw first from ledger.unlocking
chunks before trying to withdraw from active?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it affect other members who're unbonding funds? Let's say
ED = 100, MIN_BOND = ED
A deposits 500 and unbonds 100
B deposits 100
next era, slash happens (so the 100 unlocking fund isn't affected right?)
obviously, B will be reaped
assuming no one unbonds after the slash
if we withdraw first from unlocking, then when the fund is fully unlocked
what's left would be less than what A would expect to withdraw (100 - x) right?
Do you mean we withdraw from unlocking and unbond more to make up for the portion withdrawn from the unlocking? Then wouldn't it length the unlock period?
T::EventListeners::on_withdraw(stash, amount); | ||
Self::deposit_event(Event::<T>::Withdrawn { stash: stash.clone(), amount }); | ||
|
||
T::Currency::burn(amount); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems weird to withdraw as well as burn? Shouldn't it be nomination pool to decide what to do with withdrawn funds?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be nomination pool to decide what to do with withdrawn funds?
Yeah, that makes more sense :))
@@ -863,6 +863,8 @@ pub mod pallet { | |||
ForceEra { mode: Forcing }, | |||
/// Report of a controller batch deprecation. | |||
ControllerBatchDeprecated { failures: u32 }, | |||
/// Certain amount of bonded fund has been burned. | |||
Burnt { stash: T::AccountId, amount: BalanceOf<T> }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO we should just withdraw here, and not burn. Leave the burn (or something else such as transfer these funds to reward pool) logic with the NominationPools.
@@ -183,6 +183,15 @@ pub trait StakeStrategy { | |||
num_slashing_spans: u32, | |||
) -> DispatchResult; | |||
|
|||
/// Clean up ledger leftovers before reaping a dusted member. | |||
// FIXME eagr better name? | |||
fn member_dust( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
better name?
force_withdraw?
// FIXME eagr weight functions | ||
// #[pallet::weight(T::WeightInfo::reap_member())] | ||
#[pallet::weight(1)] | ||
pub fn reap_member( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You raised a great question of whether it needs to be an extrinsic and I was convinced that we can do better.
See here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I totally misunderstood your comment there. Then I'll try the other route. But we do need to recalibrate the weights of migrate_delegation()
and apply_slash()
right?
num_slashing_spans, | ||
)?; | ||
|
||
// FIXME eagr is this needed? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just one MemberReaped
event is ideal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. We don't need to emit a withdrawn event, which makes sense. Do you mean we need an extra MemberReaped
event besides MemberRemoved
?
); | ||
|
||
if contribution > Zero::zero() { | ||
T::StakeAdapter::member_dust( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still have to update member points and stuff right?
Self::deposit_event(Event::<T>::MemberRemoved { | ||
pool_id, | ||
member: member.clone(), | ||
// FIXME eagr there should not be any dangling delegation at this point right? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe dangling delegation is independent of reap logic. Dangling delegation can occur due to merging of unbonding pools in different eras.
|
||
Ok(()) | ||
|
||
// FIXME eagr should return fee info? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really depends on what you want to do.
For permissionless extrinsic (that anyone can call to cleanup chain if some criteria is fulfilled), we want to not charge any fees. So if the call succeeds, we should refund the fees.
See https://docs.substrate.io/build/tx-weights-fees/#post-dispatch-weight-correction.
Though, I believe we might not need this to be an extrinsic anyway?
fn force_withdraw(stash: &Self::AccountId, amount: Self::Balance) -> DispatchResult { | ||
let mut ledger = Self::ledger(Stash(stash.clone()))?; | ||
ledger.active = ledger.active.saturating_sub(amount); | ||
// FIXME eagr should we check if active >= ED before updating? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't strictly need to since saturating_sub
would just make it 0. But probably a good defensive measure to add is to ensure force_withdraw
can only be done for 0 < amount < ED
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I mean do we need to
ledger.active = ledger.active.saturating_sub(amount);
// check before updating the ledger
ensure!(ledger.active >= T::Currency::minimum_balance());
ledger.update()?;
The tests are somewhat broken (unrelated to the changes here). You could run this in /substrate/frame/nomination-pools/test-delegate-stake and watch it crash. #[test]
fn reap_member_delegate() {
new_test_ext().execute_with(|| {
assert_eq!(Balances::minimum_balance(), 5);
assert_eq!(Staking::current_era(), None);
assert_ok!(Pools::create(RuntimeOrigin::signed(10), 15, 10, 10, 10));
assert_eq!(LastPoolId::<Runtime>::get(), 1);
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
assert_ok!(Pools::join(RuntimeOrigin::signed(21), 5, 1));
CurrentEra::<Runtime>::set(Some(1));
// slash era 1
pallet_staking::slashing::do_slash::<Runtime>(
&POOL1_BONDED,
15,
&mut Default::default(),
&mut Default::default(),
1,
);
CurrentEra::<Runtime>::set(Some(5));
// >ED after slash
assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().total_balance(), 7);
// =ED after slash
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().total_balance(), 5);
// <ED after slash, will be reaped
assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().total_balance(), 2);
assert_ok!(Pools::apply_slash(RuntimeOrigin::signed(21), 10));
assert_ok!(Pools::apply_slash(RuntimeOrigin::signed(21), 20));
// here it crashes
assert_ok!(Pools::apply_slash(RuntimeOrigin::signed(21), 21));
})
}
which seems to come from
I suspect it's also related to ints being used for testing. |
Fixes #5009
TODO
New to the pallet code. I'd appreciate it if you could take a look to see if it's on the right direction before I do the bench and tests @Ank4n