Skip to content

Commit

Permalink
stake-pool: Set fee (#1604)
Browse files Browse the repository at this point in the history
* stake-pool: Add set_fee instruction

* Add more tests

* Add set-fee CLI instruction

* Update documentation

* Cargo fmt

* Re-format

* Fix clippy
  • Loading branch information
joncinque committed Apr 21, 2021
1 parent 30671aa commit 40ebfc6
Show file tree
Hide file tree
Showing 8 changed files with 387 additions and 70 deletions.
104 changes: 60 additions & 44 deletions docs/src/stake-pool.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ inflation rate, total number of SOL staked on the network, and an individual
validator’s uptime and commission (fee).

Stake pools are an alternative method of earning staking rewards. This on-chain
program pools together SOL to be staked by a manager, allowing SOL holders to
program pools together SOL to be staked by a staker, allowing SOL holders to
stake and earn rewards without managing stakes.

Additional information regarding staking and stake programming is available at:
Expand All @@ -24,16 +24,18 @@ Additional information regarding staking and stake programming is available at:

## Motivation

This document is intended for stake pool managers who want to create or manage
stake pools, and users who want to provide staked SOL into an existing stake
pool.
This document is intended for the main actors of the stake pool system:

* manager: creates and manages the stake pool, earns fees, can update the fee, staker, and manager
* staker: adds and removes validators to the pool, rebalances stake among valiators
* user: provides staked SOL into an existing stake pool

In its current iteration, the stake pool only processes totally active stakes.
Deposits must come from fully active stakes, and withdrawals return a fully
active stake account.

This means that stake pool managers and users must be comfortable with creating
and delegating stakes, which are more advanced operations than sending and
This means that stake pool managers, stakers, and users must be comfortable with
creating and delegating stakes, which are more advanced operations than sending and
receiving SPL tokens and SOL. Additional information on stake operations are
available at:

Expand All @@ -46,10 +48,10 @@ like [Token Swap](token-swap.md).

## Operation

A stake pool manager creates a stake pool and includes validators that will
A stake pool manager creates a stake pool, and the staker includes validators that will
receive delegations from the pool by creating "validator stake accounts" and
activating a delegation on them. Once a validator stake account's delegation is
active, the stake pool manager adds it to the stake pool.
active, the staker adds it to the stake pool.

At this point, users can participate with deposits. They must delegate a stake
account to the one of the validators in the stake pool. Once it's active, the
Expand All @@ -61,12 +63,12 @@ Over time, as the stake pool accrues staking rewards, the user's fractional
ownership will be worth more than their initial deposit. Whenever the user chooses,
they can withdraw their SPL staking derivatives in exchange for an activated stake.

The stake pool manager can add and remove validators, or rebalance the pool by
The stake pool staker can add and remove validators, or rebalance the pool by
withdrawing stakes from the pool, deactivating them, reactivating them on another
validator, then depositing back into the pool.

These manager operations require SPL staking derivatives and staked SOL, so the
stake pool manager will need liquidity on hand to properly manage the pool.
stake pool staker will need liquidity on hand to properly manage the pool.

## Background

Expand Down Expand Up @@ -130,12 +132,12 @@ Hardware Wallet URL (See [URL spec](https://docs.solana.com/wallet-guide/hardwar
solana config set --keypair usb://ledger/
```

### Stake Pool Administrator Examples
### Stake Pool Manager Examples

#### Create a stake pool

The pool administrator manages the stake accounts in a stake pool, and in exchange
receives a fee in the form of SPL token staking derivatives. The administrator
The stake pool manager controls the stake pool from a high level, and in exchange
receives a fee in the form of SPL token staking derivatives. The manager
sets the fee on creation. Let's create a pool with a 3% fee:

```sh
Expand All @@ -157,6 +159,46 @@ The pool creator's fee account identifier is
in the stake pool earn rewards, the program will mint SPL token staking derivatives
equal to 3% of the gains on that epoch into this account.

#### Set manager

The stake pool manager may pass their administrator privileges to another account.

```sh
$ spl-stake-pool set-manager 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC --new-manager 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
Signature: 39N5gkaqXuWm6JPEUWfenKXeG4nSa71p7iHb9zurvdZcsWmbjdmSXwLVYfhAVHWucTY77sJ8SkUNpVpVAhe4eZ53
```

#### Set fee

The stake pool manager may update the fee assessed every epoch.

```sh
$ spl-stake-pool set-fee 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 10 100
Signature: 5yPXfVj5cbKBfZiEVi2UR5bXzVDuc2c3ruBwSjkAqpvxPHigwGHiS1mXQVE4qwok5moMWT5RNYAMvkE9bnfQ1i93
```

#### Set staker

In order to manage the stake accounts, the stake pool manager or
staker can set the staker authority of the stake pool's managed accounts.

```sh
$ spl-stake-pool set-staker 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
Signature: 39N5gkaqXuWm6JPEUWfenKXeG4nSa71p7iHb9zurvdZcsWmbjdmSXwLVYfhAVHWucTY77sJ8SkUNpVpVAhe4eZ53
```

Now, the new staker can perform any normal stake pool operations, including
adding and removing validators and rebalancing stake.

Important security note: the stake pool program only gives staking authority to
the pool staker and always retains withdraw authority. Therefore, a malicious
stake pool staker cannot steal funds from the stake pool.

Note: to avoid "disturbing the manager", the staker can also reassign their stake
authority.

### Stake Pool Staker Examples

#### Create a validator stake account

In order to accommodate large numbers of user deposits into the stake pool, the
Expand Down Expand Up @@ -260,7 +302,7 @@ Withdraw Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn

#### Remove validator stake account

If the stake pool manager wants to stop delegating to a vote account, they can
If the stake pool staker wants to stop delegating to a vote account, they can
totally remove the validator stake account from the stake pool by providing
staking derivatives, just like `withdraw`.

Expand Down Expand Up @@ -301,9 +343,9 @@ Total: ◎15.849959206
#### Rebalance the stake pool

As time goes on, deposits and withdrawals will happen to all of the stake accounts
managed by the pool, and the stake pool manager may want to rebalance the stakes.
managed by the pool, and the stake pool staker may want to rebalance the stakes.

For example, let's say the manager wants the same delegation to every validator
For example, let's say the staker wants the same delegation to every validator
in the pool. When they look at the state of the pool, they see:

```sh
Expand All @@ -315,7 +357,7 @@ Total: ◎15.849959206
```

This isn't great! The last stake account, `E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie`
has too much allocated. For their strategy, the manager wants the `15.849959206`
has too much allocated. For their strategy, the staker wants the `15.849959206`
SOL to be distributed evenly, meaning around `5.283319735` in each account. They need
to move `4.281036854` to `FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13` and
`1.872447062` to `FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN`.
Expand Down Expand Up @@ -370,7 +412,7 @@ with `4.281036854` delegated to `8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm`
and stake account `GCJnuFGCDzaToPwJtG5GiK4g3DJBfuhQy6388NyGcfwf` with `1.872447062`
delegated to `2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3`.

Once the new stakes are ready, the manager deposits them back into the stake pool:
Once the new stakes are ready, the staker deposits them back into the stake pool:
```sh
$ spl-stake-pool deposit 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC GCJnuFGCDzaToPwJtG5GiK4g3DJBfuhQy6388NyGcfwf --token-receiver 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
Depositing into stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
Expand All @@ -393,32 +435,6 @@ Total: ◎15.851269888
Due to staking rewards that accrued during the rebalancing process, the pool is
not prefectly balanced. This is completely normal.

#### Set staking authority

In order to manage the stake accounts more directly, the stake pool owner can
set the stake authority of the stake pool's managed accounts.

```sh
$ spl-stake-pool set-staking-auth 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC --stake-account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN --new-staker 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
Signature: 39N5gkaqXuWm6JPEUWfenKXeG4nSa71p7iHb9zurvdZcsWmbjdmSXwLVYfhAVHWucTY77sJ8SkUNpVpVAhe4eZ53
```

Now, the new staking authority can perform any normal staking operations,
including deactivating or re-staking.

Important security note: the stake pool program only gives staking authority to
the pool owner and always retains withdraw authority. Therefore, a malicious
stake pool manager cannot steal funds from the stake pool.

#### Set owner

The stake pool owner may pass their administrator privileges to another account.

```sh
$ spl-stake-pool 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC --new-owner 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
Signature: 39N5gkaqXuWm6JPEUWfenKXeG4nSa71p7iHb9zurvdZcsWmbjdmSXwLVYfhAVHWucTY77sJ8SkUNpVpVAhe4eZ53
```

### User Examples

#### List validator stake accounts
Expand Down
70 changes: 63 additions & 7 deletions stake-pool/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use {
find_deposit_authority_program_address, find_stake_program_address,
find_withdraw_authority_program_address,
stake_program::{self, StakeAuthorize, StakeState},
state::{StakePool, ValidatorList},
state::{Fee, StakePool, ValidatorList},
},
std::process::exit,
};
Expand Down Expand Up @@ -94,11 +94,7 @@ fn send_transaction(
Ok(())
}

fn command_create_pool(
config: &Config,
fee: spl_stake_pool::instruction::Fee,
max_validators: u32,
) -> CommandResult {
fn command_create_pool(config: &Config, fee: Fee, max_validators: u32) -> CommandResult {
let mint_account = Keypair::new();
println!("Creating mint {}", mint_account.pubkey());

Expand Down Expand Up @@ -921,6 +917,26 @@ fn command_set_staker(
Ok(())
}

fn command_set_fee(config: &Config, stake_pool_address: &Pubkey, new_fee: Fee) -> CommandResult {
let mut transaction = Transaction::new_with_payer(
&[spl_stake_pool::instruction::set_fee(
&spl_stake_pool::id(),
&stake_pool_address,
&config.manager.pubkey(),
new_fee,
)],
Some(&config.fee_payer.pubkey()),
);

let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
let mut signers = vec![config.fee_payer.as_ref(), config.manager.as_ref()];
unique_signers!(signers);
transaction.sign(&signers, recent_blockhash);
send_transaction(&config, transaction)?;
Ok(())
}

fn main() {
solana_logger::setup_with_default("solana=info");

Expand Down Expand Up @@ -1290,6 +1306,36 @@ fn main() {
.help("Public key for the new stake pool staker."),
)
)
.subcommand(SubCommand::with_name("set-fee")
.about("Change the fee assessed by the stake pool. Must be signed by the manager.")
.arg(
Arg::with_name("pool")
.index(1)
.validator(is_pubkey)
.value_name("POOL_ADDRESS")
.takes_value(true)
.required(true)
.help("Stake pool address."),
)
.arg(
Arg::with_name("fee_numerator")
.index(2)
.validator(is_parsable::<u64>)
.value_name("NUMERATOR")
.takes_value(true)
.required(true)
.help("Fee numerator, fee amount is numerator divided by denominator."),
)
.arg(
Arg::with_name("fee_denominator")
.index(3)
.validator(is_parsable::<u64>)
.value_name("DENOMINATOR")
.takes_value(true)
.required(true)
.help("Fee denominator, fee amount is numerator divided by denominator."),
)
)
.get_matches();

let mut wallet_manager = None;
Expand Down Expand Up @@ -1365,7 +1411,7 @@ fn main() {
let max_validators = value_t_or_exit!(arg_matches, "max_validators", u32);
command_create_pool(
&config,
spl_stake_pool::instruction::Fee {
Fee {
denominator,
numerator,
},
Expand Down Expand Up @@ -1436,6 +1482,16 @@ fn main() {
let new_staker = pubkey_of(arg_matches, "new_staker").unwrap();
command_set_staker(&config, &stake_pool_address, &new_staker)
}
("set-fee", Some(arg_matches)) => {
let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();
let numerator = value_t_or_exit!(arg_matches, "fee_numerator", u64);
let denominator = value_t_or_exit!(arg_matches, "fee_denominator", u64);
let new_fee = Fee {
denominator,
numerator,
};
command_set_fee(&config, &stake_pool_address, new_fee)
}
_ => unreachable!(),
}
.map_err(|err| {
Expand Down
43 changes: 31 additions & 12 deletions stake-pool/program/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#![allow(clippy::too_many_arguments)]

use {
crate::stake_program,
crate::{stake_program, state::Fee},
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
solana_program::{
instruction::{AccountMeta, Instruction},
Expand All @@ -13,17 +13,6 @@ use {
},
};

/// Fee rate as a ratio, minted on `UpdateStakePoolBalance` as a proportion of
/// the rewards
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)]
pub struct Fee {
/// denominator of the fee ratio
pub denominator: u64,
/// numerator of the fee ratio
pub numerator: u64,
}

/// Instructions supported by the StakePool program.
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)]
Expand Down Expand Up @@ -234,6 +223,17 @@ pub enum StakePoolInstruction {
/// 3. '[]` New manager fee account
SetManager,

/// (Manager only) Update fee
///
/// 0. `[w]` StakePool
/// 1. `[s]` Manager
/// 2. `[]` Sysvar clock
SetFee {
/// Fee assessed as percentage of perceived rewards
#[allow(dead_code)] // but it's not
fee: Fee,
},

/// (Manager or staker only) Update staker
///
/// 0. `[w]` StakePool
Expand Down Expand Up @@ -507,6 +507,25 @@ pub fn set_manager(
})
}

/// Creates a 'set fee' instruction.
pub fn set_fee(
program_id: &Pubkey,
stake_pool: &Pubkey,
manager: &Pubkey,
fee: Fee,
) -> Instruction {
let accounts = vec![
AccountMeta::new(*stake_pool, false),
AccountMeta::new_readonly(*manager, true),
AccountMeta::new_readonly(sysvar::clock::id(), false),
];
Instruction {
program_id: *program_id,
accounts,
data: StakePoolInstruction::SetFee { fee }.try_to_vec().unwrap(),
}
}

/// Creates a 'set staker' instruction.
pub fn set_staker(
program_id: &Pubkey,
Expand Down
Loading

0 comments on commit 40ebfc6

Please sign in to comment.