Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 40ebfc6

Browse files
authored
stake-pool: Set fee (#1604)
* stake-pool: Add set_fee instruction * Add more tests * Add set-fee CLI instruction * Update documentation * Cargo fmt * Re-format * Fix clippy
1 parent 30671aa commit 40ebfc6

File tree

8 files changed

+387
-70
lines changed

8 files changed

+387
-70
lines changed

docs/src/stake-pool.md

Lines changed: 60 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ inflation rate, total number of SOL staked on the network, and an individual
1414
validator’s uptime and commission (fee).
1515

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

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

2525
## Motivation
2626

27-
This document is intended for stake pool managers who want to create or manage
28-
stake pools, and users who want to provide staked SOL into an existing stake
29-
pool.
27+
This document is intended for the main actors of the stake pool system:
28+
29+
* manager: creates and manages the stake pool, earns fees, can update the fee, staker, and manager
30+
* staker: adds and removes validators to the pool, rebalances stake among valiators
31+
* user: provides staked SOL into an existing stake pool
3032

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

35-
This means that stake pool managers and users must be comfortable with creating
36-
and delegating stakes, which are more advanced operations than sending and
37+
This means that stake pool managers, stakers, and users must be comfortable with
38+
creating and delegating stakes, which are more advanced operations than sending and
3739
receiving SPL tokens and SOL. Additional information on stake operations are
3840
available at:
3941

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

4749
## Operation
4850

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

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

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

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

7173
## Background
7274

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

133-
### Stake Pool Administrator Examples
135+
### Stake Pool Manager Examples
134136

135137
#### Create a stake pool
136138

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

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

162+
#### Set manager
163+
164+
The stake pool manager may pass their administrator privileges to another account.
165+
166+
```sh
167+
$ spl-stake-pool set-manager 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC --new-manager 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
168+
Signature: 39N5gkaqXuWm6JPEUWfenKXeG4nSa71p7iHb9zurvdZcsWmbjdmSXwLVYfhAVHWucTY77sJ8SkUNpVpVAhe4eZ53
169+
```
170+
171+
#### Set fee
172+
173+
The stake pool manager may update the fee assessed every epoch.
174+
175+
```sh
176+
$ spl-stake-pool set-fee 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 10 100
177+
Signature: 5yPXfVj5cbKBfZiEVi2UR5bXzVDuc2c3ruBwSjkAqpvxPHigwGHiS1mXQVE4qwok5moMWT5RNYAMvkE9bnfQ1i93
178+
```
179+
180+
#### Set staker
181+
182+
In order to manage the stake accounts, the stake pool manager or
183+
staker can set the staker authority of the stake pool's managed accounts.
184+
185+
```sh
186+
$ spl-stake-pool set-staker 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
187+
Signature: 39N5gkaqXuWm6JPEUWfenKXeG4nSa71p7iHb9zurvdZcsWmbjdmSXwLVYfhAVHWucTY77sJ8SkUNpVpVAhe4eZ53
188+
```
189+
190+
Now, the new staker can perform any normal stake pool operations, including
191+
adding and removing validators and rebalancing stake.
192+
193+
Important security note: the stake pool program only gives staking authority to
194+
the pool staker and always retains withdraw authority. Therefore, a malicious
195+
stake pool staker cannot steal funds from the stake pool.
196+
197+
Note: to avoid "disturbing the manager", the staker can also reassign their stake
198+
authority.
199+
200+
### Stake Pool Staker Examples
201+
160202
#### Create a validator stake account
161203

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

261303
#### Remove validator stake account
262304

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

@@ -301,9 +343,9 @@ Total: ◎15.849959206
301343
#### Rebalance the stake pool
302344

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

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

309351
```sh
@@ -315,7 +357,7 @@ Total: ◎15.849959206
315357
```
316358

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

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

396-
#### Set staking authority
397-
398-
In order to manage the stake accounts more directly, the stake pool owner can
399-
set the stake authority of the stake pool's managed accounts.
400-
401-
```sh
402-
$ spl-stake-pool set-staking-auth 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC --stake-account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN --new-staker 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
403-
Signature: 39N5gkaqXuWm6JPEUWfenKXeG4nSa71p7iHb9zurvdZcsWmbjdmSXwLVYfhAVHWucTY77sJ8SkUNpVpVAhe4eZ53
404-
```
405-
406-
Now, the new staking authority can perform any normal staking operations,
407-
including deactivating or re-staking.
408-
409-
Important security note: the stake pool program only gives staking authority to
410-
the pool owner and always retains withdraw authority. Therefore, a malicious
411-
stake pool manager cannot steal funds from the stake pool.
412-
413-
#### Set owner
414-
415-
The stake pool owner may pass their administrator privileges to another account.
416-
417-
```sh
418-
$ spl-stake-pool 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC --new-owner 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
419-
Signature: 39N5gkaqXuWm6JPEUWfenKXeG4nSa71p7iHb9zurvdZcsWmbjdmSXwLVYfhAVHWucTY77sJ8SkUNpVpVAhe4eZ53
420-
```
421-
422438
### User Examples
423439

424440
#### List validator stake accounts

stake-pool/cli/src/main.rs

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use {
3131
find_deposit_authority_program_address, find_stake_program_address,
3232
find_withdraw_authority_program_address,
3333
stake_program::{self, StakeAuthorize, StakeState},
34-
state::{StakePool, ValidatorList},
34+
state::{Fee, StakePool, ValidatorList},
3535
},
3636
std::process::exit,
3737
};
@@ -94,11 +94,7 @@ fn send_transaction(
9494
Ok(())
9595
}
9696

97-
fn command_create_pool(
98-
config: &Config,
99-
fee: spl_stake_pool::instruction::Fee,
100-
max_validators: u32,
101-
) -> CommandResult {
97+
fn command_create_pool(config: &Config, fee: Fee, max_validators: u32) -> CommandResult {
10298
let mint_account = Keypair::new();
10399
println!("Creating mint {}", mint_account.pubkey());
104100

@@ -921,6 +917,26 @@ fn command_set_staker(
921917
Ok(())
922918
}
923919

920+
fn command_set_fee(config: &Config, stake_pool_address: &Pubkey, new_fee: Fee) -> CommandResult {
921+
let mut transaction = Transaction::new_with_payer(
922+
&[spl_stake_pool::instruction::set_fee(
923+
&spl_stake_pool::id(),
924+
&stake_pool_address,
925+
&config.manager.pubkey(),
926+
new_fee,
927+
)],
928+
Some(&config.fee_payer.pubkey()),
929+
);
930+
931+
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
932+
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
933+
let mut signers = vec![config.fee_payer.as_ref(), config.manager.as_ref()];
934+
unique_signers!(signers);
935+
transaction.sign(&signers, recent_blockhash);
936+
send_transaction(&config, transaction)?;
937+
Ok(())
938+
}
939+
924940
fn main() {
925941
solana_logger::setup_with_default("solana=info");
926942

@@ -1290,6 +1306,36 @@ fn main() {
12901306
.help("Public key for the new stake pool staker."),
12911307
)
12921308
)
1309+
.subcommand(SubCommand::with_name("set-fee")
1310+
.about("Change the fee assessed by the stake pool. Must be signed by the manager.")
1311+
.arg(
1312+
Arg::with_name("pool")
1313+
.index(1)
1314+
.validator(is_pubkey)
1315+
.value_name("POOL_ADDRESS")
1316+
.takes_value(true)
1317+
.required(true)
1318+
.help("Stake pool address."),
1319+
)
1320+
.arg(
1321+
Arg::with_name("fee_numerator")
1322+
.index(2)
1323+
.validator(is_parsable::<u64>)
1324+
.value_name("NUMERATOR")
1325+
.takes_value(true)
1326+
.required(true)
1327+
.help("Fee numerator, fee amount is numerator divided by denominator."),
1328+
)
1329+
.arg(
1330+
Arg::with_name("fee_denominator")
1331+
.index(3)
1332+
.validator(is_parsable::<u64>)
1333+
.value_name("DENOMINATOR")
1334+
.takes_value(true)
1335+
.required(true)
1336+
.help("Fee denominator, fee amount is numerator divided by denominator."),
1337+
)
1338+
)
12931339
.get_matches();
12941340

12951341
let mut wallet_manager = None;
@@ -1365,7 +1411,7 @@ fn main() {
13651411
let max_validators = value_t_or_exit!(arg_matches, "max_validators", u32);
13661412
command_create_pool(
13671413
&config,
1368-
spl_stake_pool::instruction::Fee {
1414+
Fee {
13691415
denominator,
13701416
numerator,
13711417
},
@@ -1436,6 +1482,16 @@ fn main() {
14361482
let new_staker = pubkey_of(arg_matches, "new_staker").unwrap();
14371483
command_set_staker(&config, &stake_pool_address, &new_staker)
14381484
}
1485+
("set-fee", Some(arg_matches)) => {
1486+
let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();
1487+
let numerator = value_t_or_exit!(arg_matches, "fee_numerator", u64);
1488+
let denominator = value_t_or_exit!(arg_matches, "fee_denominator", u64);
1489+
let new_fee = Fee {
1490+
denominator,
1491+
numerator,
1492+
};
1493+
command_set_fee(&config, &stake_pool_address, new_fee)
1494+
}
14391495
_ => unreachable!(),
14401496
}
14411497
.map_err(|err| {

stake-pool/program/src/instruction.rs

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#![allow(clippy::too_many_arguments)]
44

55
use {
6-
crate::stake_program,
6+
crate::{stake_program, state::Fee},
77
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
88
solana_program::{
99
instruction::{AccountMeta, Instruction},
@@ -13,17 +13,6 @@ use {
1313
},
1414
};
1515

16-
/// Fee rate as a ratio, minted on `UpdateStakePoolBalance` as a proportion of
17-
/// the rewards
18-
#[repr(C)]
19-
#[derive(Clone, Copy, Debug, Default, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)]
20-
pub struct Fee {
21-
/// denominator of the fee ratio
22-
pub denominator: u64,
23-
/// numerator of the fee ratio
24-
pub numerator: u64,
25-
}
26-
2716
/// Instructions supported by the StakePool program.
2817
#[repr(C)]
2918
#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)]
@@ -234,6 +223,17 @@ pub enum StakePoolInstruction {
234223
/// 3. '[]` New manager fee account
235224
SetManager,
236225

226+
/// (Manager only) Update fee
227+
///
228+
/// 0. `[w]` StakePool
229+
/// 1. `[s]` Manager
230+
/// 2. `[]` Sysvar clock
231+
SetFee {
232+
/// Fee assessed as percentage of perceived rewards
233+
#[allow(dead_code)] // but it's not
234+
fee: Fee,
235+
},
236+
237237
/// (Manager or staker only) Update staker
238238
///
239239
/// 0. `[w]` StakePool
@@ -507,6 +507,25 @@ pub fn set_manager(
507507
})
508508
}
509509

510+
/// Creates a 'set fee' instruction.
511+
pub fn set_fee(
512+
program_id: &Pubkey,
513+
stake_pool: &Pubkey,
514+
manager: &Pubkey,
515+
fee: Fee,
516+
) -> Instruction {
517+
let accounts = vec![
518+
AccountMeta::new(*stake_pool, false),
519+
AccountMeta::new_readonly(*manager, true),
520+
AccountMeta::new_readonly(sysvar::clock::id(), false),
521+
];
522+
Instruction {
523+
program_id: *program_id,
524+
accounts,
525+
data: StakePoolInstruction::SetFee { fee }.try_to_vec().unwrap(),
526+
}
527+
}
528+
510529
/// Creates a 'set staker' instruction.
511530
pub fn set_staker(
512531
program_id: &Pubkey,

0 commit comments

Comments
 (0)