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

[NPoS] When pool contribution of a pool member goes below ED, it should be reaped. #5009

Open
Ank4n opened this issue Jul 12, 2024 · 13 comments · May be fixed by #5769
Open

[NPoS] When pool contribution of a pool member goes below ED, it should be reaped. #5009

Ank4n opened this issue Jul 12, 2024 · 13 comments · May be fixed by #5769
Labels
C1-mentor A task where a mentor is available. Please indicate in the issue who the mentor could be. D1-medium Can be fixed by a coder with good Rust knowledge but little knowledge of the codebase.

Comments

@Ank4n
Copy link
Contributor

Ank4n commented Jul 12, 2024

There are cases where because of slashing, nomination pool members have contribution less than ED. When this happens, we should have a way to reap these accounts. This could be exposed via a permissionless extrinsic.

Things it should do

  • Reap pool member in pallet-nomination-pools.
  • Reap delegator in pallet-delegated-staking
  • reap part of the staked balance in pallet-staking. This should never exceed ED.

How I see this to be implemented

  • Add new permissionless extrinsic to Nomination pool to reap member. It should only execute if pool contribution of member is below ED.
  • The pool associated with the member is a nominator account in pallet-staking. Force withdraw that amount from pallet-staking and burn it. This should be similar to how balance pallet handles dust (see fungible::Unbalanced::handle_dust). This should also be removed from StakingLedger.active.
  • Clean up the delegator (in pallet-delegated-staking) and pool member. This logic would be somewhat similar to what happens in pool::withdraw_unbonded when all member funds are withdrawn.

Additionally, this should also ensure when the pool is destroyed, the pool_account is reaped from pallet-staking. If the pool is already destroyed, but it exists in pallet-staking, it should get reaped as well.

@Ank4n Ank4n added the D0-easy Can be fixed primarily by duplicating and adapting code by an intermediate coder. label Jul 12, 2024
@Ank4n Ank4n added the C1-mentor A task where a mentor is available. Please indicate in the issue who the mentor could be. label Aug 14, 2024
github-merge-queue bot pushed a commit that referenced this issue Aug 14, 2024
## Context
Pool members using the old `TransferStake` strategy were able to
transfer all their funds to the pool. With `DelegateStake` changes, we
want to ensure similar behaviour by allowing members to delegate all
their stake to the pool.

## Changes
- Ensure all the balance including ED of an account can be delegated
(and used in the pool) by adding a provider for delegators.
- Gates calls that mutates the pool or pool member if they are in
unmigrated state. Closes
paritytech-secops/srlabs_findings#409.
- Adds remote test to migrate all pools and members to `DelegateStake`
which can be used with `Kusama` and `Polkadot` runtime state. closes
#4629.
- Add new runtime apis to read pool and member balance.

## Addressing possible migration errors 
Pool members migrating can run into two types of errors:
- Already Staking: If the pool member is already staking, we cannot
migrate them to `DelegateStake` since this may mean they are able to use
the same staked funds in the pool. Users would need to withdraw all
their funds from staking, in order to migrate their pool funds.
- Pool contribution below ED: For these cases transfer from pool account
to member account would fail. The affected users can top up their
accounts and redo migration.

Another error that was earlier possible was when member's free balance
is below ED. This PR adds a provider to delegator allowing all user
balance including ED can be contributed towards the pool. This helps
`1095` accounts in Polkadot and `41` accounts in Kusama to migrate now
which would have earlier failed.

## Results from RemoteExternalities Tests.

### Kusama
`Migration stats: success: 3017, direct_stakers: 361, unexpected_errors:
0`

### Polkadot
`Migration stats: success: 42859, direct_stakers: 643,
unexpected_errors: 0`

## TODO
- [x] Add runtime api for member total balance.
- [x] New
[issue](#5009) to reap
pool members with contribution below ED.
- [x] Add provider for delegators so whole balance including ED can be
held while contributing to pools.
- [x] Gate all pool extrinsics if pool/member is in non-migrated state.

---------

Co-authored-by: Gonçalo Pestana <g6pestana@gmail.com>
@eagr
Copy link
Contributor

eagr commented Aug 27, 2024

I’ll try to paraphrase your words, please correct me if misunderstand anything, coz some of the terms are not as straightforward to outsider like me. :))

There should be a way such that if the contribution (in terms of stake) of a member somehow goes below the existential deposit (however much on that network), they’ll be reaped, meaning to be removed from the pool. Correct?

@Ank4n
Copy link
Contributor Author

Ank4n commented Sep 6, 2024

I’ll try to paraphrase your words, please correct me if misunderstand anything, coz some of the terms are not as straightforward to outsider like me. :))

There should be a way such that if the contribution (in terms of stake) of a member somehow goes below the existential deposit (however much on that network), they’ll be reaped, meaning to be removed from the pool. Correct?

We need to add a permissionless call to Nomination Pools to reap a pool member. Reaping should be only possible if member contribution is below ED (can happen in case of slashing). When you reap a pool member, it should do some additional cleanup,

Reap delegator in pallet-delegated-staking
reap part of the staked balance in pallet-staking. This should never exceed ED.

Don't hesitate to ask any more clarifying questions if you end up taking this issue. :)

@PieWol
Copy link
Contributor

PieWol commented Sep 19, 2024

Hi @Ank4n , is this still open? I will have a go at it then.

@Ank4n
Copy link
Contributor Author

Ank4n commented Sep 19, 2024

Hi @Ank4n , is this still open? I will have a go at it then.

Yes, this can still be picked up. :) @eagr has already started working on this.

@eagr eagr linked a pull request Sep 19, 2024 that will close this issue
3 tasks
@eagr
Copy link
Contributor

eagr commented Sep 19, 2024

sorry I kinda forgot about this. I just have something drafted up. not sure if it's on the right direction though

@Ank4n
Copy link
Contributor Author

Ank4n commented Sep 19, 2024

sorry I kinda forgot about this. I just have something drafted up. not sure if it's on the right direction though

@eagr ahh okay didn't realise you have started working on this. I will have a look shortly.

@Ank4n Ank4n added D1-medium Can be fixed by a coder with good Rust knowledge but little knowledge of the codebase. and removed D0-easy Can be fixed primarily by duplicating and adapting code by an intermediate coder. labels Sep 20, 2024
@Ank4n
Copy link
Contributor Author

Ank4n commented Sep 20, 2024

sorry I kinda forgot about this. I just have something drafted up. not sure if it's on the right direction though

@eagr I have put some additional context in the description. I am also realising it has some subtle logic that requires knowledge of pool and delegation system. The issue is probably not as straight forward anymore but happy to help you if you are still keen to fix it. :)

@eagr
Copy link
Contributor

eagr commented Sep 20, 2024

@Ank4n Sure, sounds like a great opportunity to learn about staking/delegation! I'm going through the docs and codebase and accumulating questions. Will get back to you soon. Btw, have a nice weekend. :)

@eagr
Copy link
Contributor

eagr commented Sep 26, 2024

If <ED contribution is to be reaped, then it seems inappropriate to allow withdrawing certain amount of fund such that it’s leaving >0 && <ED behind. Then I guess the only way for a member’s contribution to go <ED is slashing? Curious why does this need to be an extrinsic, rather than a part of some post-slashing transition?

When one joins a pool,

  • for pool of StakeStrategyType::Transfer
    • transfer fund from member account to pool account
    • update pool account ledger
  • for pool of StakeStrategyType::Delegate
    • create delegation
    • "hold" fund in place
    • update agent ledger
    • update pool account ledger
  • increase TVL

I suppose we kinda need to do the opposite in the reversed order.

I could see the pallets in question are loosely coupled via the composition of the traits below. Below is a very rough idea for the extrinsic to trigger actions in those staking pallets.

impl StakingInterface for pallet_staking::Pallet {
  // with `dust_bonded()` added to the trait
  fn dust_bonded() {
    // update account ledger
    // burn (drop) balance
  }
}

impl DelegationInterface for pallet_nomination_pools::Pallet {
  fn dust_delegation() {
  }
}

impl StakeStrategy for TransferStake {
  // with `member_dust()` added to the trait
  // going along with other names in the trait:
  // member_withdraw, member_slash
  fn member_dust() -> DispatchResult {
    // ...
    Staking::dust_bonded();
    // ...
  }
}

impl StakeStrategy for DelegateStake {
  fn member_dust() -> DispatchResult {
    // ...
    Staking::dust_bonded();
    Delegation::dust_delegation();
    // ...
  }
}

// the extrinsic
fn dust() {
  // ..., if member active balance < ED
  T::StakeAdapter::member_dust();
  // ...
}

Any suggestions?

@PieWol
Copy link
Contributor

PieWol commented Sep 26, 2024

Hey @eagr ,

If <ED contribution is to be reaped, then it seems inappropriate to allow withdrawing certain amount of fund such that it’s leaving >0 && <ED behind. Then I guess the only way for a member’s contribution to go <ED is slashing? Curious why does this need to be an extrinsic, rather than a part of some post-slashing transition?

absolutely correct. this will only happen in the event of slashing.

This shall not be included in the regular slashing operation itself because we want to minimize the amount of computation it needs to do in one block. We basically clean up after the slash. We want to do it this way to make sure that we don't exceed the limits of computation that can be done within one block in case a slashing event occurs.

@Ank4n
Copy link
Contributor Author

Ank4n commented Sep 30, 2024

Curious why does this need to be an extrinsic, rather than a part of some post-slashing transition?

You raise a good point. The extrinsic was probably needed for members with the legacy transfer adapter. There are two scenarios where we need reap:

  • Members with transfer strategy and migrate_delegation fails because their pool contribution is too low. These members can be reaped when calling migrate_delegation.
  • Pool members are slashed lazily with Delegate adapter. These members can be reaped when apply_slash is called if their contribution goes below ED.

Not sure about the trait definition you shared. From StakingInterface point of view, these are not dust bonded. Infact staking does have a way to reap staker when balance falls below ED. The pool knows if contribution is below ED, and it should then trigger something like force_unbond in StakingInterface, withdraw that delegation from DelegationInterface (I don't think you need new functions here), and then clean up the pool member.

@eagr
Copy link
Contributor

eagr commented Oct 8, 2024

Updated the PR to achieve what you described here to the best of my knowledge. Still not quite sure if I'm on the right track tough. I left a few questions in the code prefixed by FIXME eagr. Could you take another look? @Ank4n @PieWol And a couple more questions.

In the PR, for legacy pools with Transfer adapter, the reaped deposit simply vanishes, that is, not transferred back to the member account. For pools with Delegate adapter, the reaped deposit is subtracted from the pool staking ledger, but remains available to the member account. What are the expected behaviors respectively?

Can the pool creator be reaped if their active stake goes below ED? If so, what should happen to the pool?

@Ank4n
Copy link
Contributor Author

Ank4n commented Oct 13, 2024

In the PR, for legacy pools with Transfer adapter, the reaped deposit simply vanishes, that is, not transferred back to the member account. For pools with Delegate adapter, the reaped deposit is subtracted from the pool staking ledger, but remains available to the member account. What are the expected behaviors respectively?

We should try to keep the same behaviour for both adapters. I can think of two choices (it can be configurable as well)

  • Burn the amount force withdrawn.
  • Transfer the force withdrawn amount to reward pool. This then just gets distributed to other pool members.

Either of the options are fine, with a little higher preference for burn.

Can the pool creator be reaped if their active stake goes below ED?

If pool is not in Destroying state or has other pool members this can never happen. Pool Creator is always the last member to be removed from the pool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C1-mentor A task where a mentor is available. Please indicate in the issue who the mentor could be. D1-medium Can be fixed by a coder with good Rust knowledge but little knowledge of the codebase.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants