substrate icon indicating copy to clipboard operation
substrate copied to clipboard

nomination-pools: add optional commission

Open kianenigma opened this issue 3 years ago • 8 comments

Currently, we don't have commission, and we prefer to keep the code simple and ship without this. Nonetheless, it is reasonable to add an optional commission for pool operators.

kianenigma avatar Jun 15 '22 07:06 kianenigma

I have a couple of questions about this issue.

If I understand this correctly by pool operators you mean all the pool roles inside the PoolRoles struct?

  • Do all of the administrative roles get the commission? Maybe we could make choosing who receives a commission configurable(e.g setting that only the nominator gets the commission). Also, do all of them get the same share of the commission? That should probably also be configurable.
  • I assume that the commission is taken from the earned rewards.

Szegoo avatar Jun 26 '22 07:06 Szegoo

Do all of the administrative roles get the commission? Maybe we could make choosing who receives a commission configurable(e.g setting that only the nominator gets the commission). Also, do all of them get the same share of the commission? That should probably also be configurable.

Yeah it can probably be configurable.

I assume that the commission is taken from the earned rewards.

Correct.


Note that I would not start working on this yet, as Pools are not launched yet in Polkadot and it is more reasonable to keep them simple at the beginning.

kianenigma avatar Jun 29 '22 09:06 kianenigma

Ok, I will start working on this when the pools are launched in Polkadot.

Szegoo avatar Jun 29 '22 09:06 Szegoo

Some notes, I propose we re-think the commission and not make it be a "free forever commission" system like staking. Instead, each pool member can set two commission values:

struct Commission {
  current: Percent
  max_ever: Option<Percent>,
} 

impl Default for Commission {
  fn default() -> Self {
    current: Percent::zero(),
    max_ever: None,
  }
}

with the following rules:

  1. current can NEVER be set to a value more than max_ever.
  2. max_ever can never be increased.

kianenigma avatar Aug 16 '22 07:08 kianenigma

A rate limit on the maximum change is also a good idea.

kianenigma avatar Sep 22 '22 10:09 kianenigma

Instead, each pool member can set two commission values:

Shouldn't these two values be set by the pool operators?

Szegoo avatar Sep 23 '22 08:09 Szegoo

Instead, each pool member can set two commission values:

Shouldn't these two values be set by the pool operators?

indeed, I meant to say that.

kianenigma avatar Oct 13 '22 10:10 kianenigma

Having a rate change as well, this is the commission struct that has to be stored/updated per-pool (new field of BondedPools):

struct Commission {
  // current commission percentage. 
  current: Perbill,
  // If set, the commission can never go above this. 
  // 
  // If set, this value can never be updated to a higher amount. The only way to change this is to fully dismantle the pool. 
  max_ever: Option<Perbill>,
  // If set, a change is allowed if `CommissionChange::update` returns `true`.
  max_change: Option<CommissionChange>
 
} 

struct CommissionChange {
  // pool operator sets this only. Expresses: `Perbill` allowed to change every `BlockNumber` blocks. 
  pub change_rate: (Perbill, BlockNumber),
  
  // these are updated by the chain.
  previous: Perbill,
  pervious_set_at: BlockNumber
}

impl CommissionChange {
  // return true if the proposed change is allowed. 
  fn update(&mut self, new: Perbill) -> bool {
    let now = system::block_number();
    let duration = now = self.pervious_set_at;
    let allowed_change = self.change_rate.0 * (self.change_rate.1 / duration);
    let proposed_change = new - previous;
    
    if proposed_change <= allowed_change {
      self.previous = new;
      self.pervious_set_at = now;
      true
    } else {
      false 
    } 
  } 
} 

impl Commission {
  fn update(&mut self, new: Perbill) {
    if self.max_ever.map_or(false, |m| new > m) { // err } 
    if self.max_change.map_or(false, |c| !c.update(new)) { // err } 
    // change is okay -- allowed.
    self.current = new; 
  }

kianenigma avatar Oct 13 '22 10:10 kianenigma