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

Commit 30671aa

Browse files
authored
stake-pool: Rework add / remove validator to not use pool tokens (#1581)
* Rework remove * Add tests * Transition to checked math * Update CLI for new types / instructions * Cargo fmt * Rename voter_pubkey -> vote_account_address * Remove max check * Update validator balance test
1 parent cf8eeb0 commit 30671aa

File tree

14 files changed

+639
-813
lines changed

14 files changed

+639
-813
lines changed

stake-pool/cli/src/main.rs

Lines changed: 6 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -264,12 +264,7 @@ fn command_vsa_create(
264264
Ok(())
265265
}
266266

267-
fn command_vsa_add(
268-
config: &Config,
269-
stake_pool_address: &Pubkey,
270-
stake: &Pubkey,
271-
token_receiver: &Option<Pubkey>,
272-
) -> CommandResult {
267+
fn command_vsa_add(config: &Config, stake_pool_address: &Pubkey, stake: &Pubkey) -> CommandResult {
273268
if config.rpc_client.get_stake_activation(*stake, None)?.state != StakeActivationState::Active {
274269
return Err("Stake account is not active.".into());
275270
}
@@ -280,26 +275,9 @@ fn command_vsa_add(
280275

281276
let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?;
282277

283-
let mut total_rent_free_balances: u64 = 0;
284-
285-
let token_receiver_account = Keypair::new();
286-
287278
let mut instructions: Vec<Instruction> = vec![];
288279
let mut signers = vec![config.fee_payer.as_ref(), config.staker.as_ref()];
289280

290-
// Create token account if not specified
291-
let token_receiver = unwrap_create_token_account(
292-
&config,
293-
&token_receiver,
294-
&token_receiver_account,
295-
&stake_pool.pool_mint,
296-
&mut instructions,
297-
|balance| {
298-
signers.push(&token_receiver_account);
299-
total_rent_free_balances += balance;
300-
},
301-
)?;
302-
303281
// Calculate Deposit and Withdraw stake pool authorities
304282
let pool_deposit_authority =
305283
find_deposit_authority_program_address(&spl_stake_pool::id(), stake_pool_address).0;
@@ -331,20 +309,14 @@ fn command_vsa_add(
331309
&pool_withdraw_authority,
332310
&stake_pool.validator_list,
333311
&stake,
334-
&token_receiver,
335-
&stake_pool.pool_mint,
336-
&spl_token::id(),
337312
)?,
338313
]);
339314

340315
let mut transaction =
341316
Transaction::new_with_payer(&instructions, Some(&config.fee_payer.pubkey()));
342317

343318
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
344-
check_fee_payer_balance(
345-
config,
346-
total_rent_free_balances + fee_calculator.calculate_fee(&transaction.message()),
347-
)?;
319+
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
348320
unique_signers!(signers);
349321
transaction.sign(&signers, recent_blockhash);
350322
send_transaction(&config, transaction)?;
@@ -355,7 +327,6 @@ fn command_vsa_remove(
355327
config: &Config,
356328
stake_pool_address: &Pubkey,
357329
stake: &Pubkey,
358-
withdraw_from: &Pubkey,
359330
new_authority: &Option<Pubkey>,
360331
) -> CommandResult {
361332
if !config.no_update {
@@ -369,35 +340,8 @@ fn command_vsa_remove(
369340
let staker_pubkey = config.staker.pubkey();
370341
let new_authority = new_authority.as_ref().unwrap_or(&staker_pubkey);
371342

372-
// Calculate amount of tokens to withdraw
373-
let stake_account = config.rpc_client.get_account(&stake)?;
374-
let tokens_to_withdraw = stake_pool
375-
.calc_pool_tokens_for_withdraw(stake_account.lamports)
376-
.unwrap();
377-
378-
// Check balance and mint
379-
let token_account =
380-
get_token_account(&config.rpc_client, &withdraw_from, &stake_pool.pool_mint)?;
381-
382-
if token_account.amount < tokens_to_withdraw {
383-
let pool_mint = get_token_mint(&config.rpc_client, &stake_pool.pool_mint)?;
384-
return Err(format!(
385-
"Not enough balance to burn to remove validator stake account from the pool. {} pool tokens needed.",
386-
spl_token::amount_to_ui_amount(tokens_to_withdraw, pool_mint.decimals)
387-
).into());
388-
}
389-
390343
let mut transaction = Transaction::new_with_payer(
391344
&[
392-
// Approve spending token
393-
spl_token::instruction::approve(
394-
&spl_token::id(),
395-
&withdraw_from,
396-
&pool_withdraw_authority,
397-
&config.token_owner.pubkey(),
398-
&[],
399-
tokens_to_withdraw,
400-
)?,
401345
// Create new validator stake account address
402346
spl_stake_pool::instruction::remove_validator_from_pool(
403347
&spl_stake_pool::id(),
@@ -407,9 +351,6 @@ fn command_vsa_remove(
407351
&new_authority,
408352
&stake_pool.validator_list,
409353
&stake,
410-
&withdraw_from,
411-
&stake_pool.pool_mint,
412-
&spl_token::id(),
413354
)?,
414355
],
415356
Some(&config.fee_payer.pubkey()),
@@ -589,7 +530,7 @@ fn command_list(config: &Config, stake_pool_address: &Pubkey) -> CommandResult {
589530
for validator in validator_list.validators {
590531
println!(
591532
"Validator Vote Account: {}\tBalance: {}\tLast Update Epoch: {}{}",
592-
validator.vote_account,
533+
validator.vote_account_address,
593534
Sol(validator.stake_lamports),
594535
validator.last_update_epoch,
595536
if validator.last_update_epoch != epoch_info.epoch {
@@ -669,7 +610,7 @@ fn command_update(config: &Config, stake_pool_address: &Pubkey) -> CommandResult
669610
} else {
670611
let (stake_account, _) = find_stake_program_address(
671612
&spl_stake_pool::id(),
672-
&item.vote_account,
613+
&item.vote_account_address,
673614
&stake_pool_address,
674615
);
675616
Some(stake_account)
@@ -1439,26 +1380,13 @@ fn main() {
14391380
("add-validator", Some(arg_matches)) => {
14401381
let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();
14411382
let stake_account = pubkey_of(arg_matches, "stake_account").unwrap();
1442-
let token_receiver: Option<Pubkey> = pubkey_of(arg_matches, "token_receiver");
1443-
command_vsa_add(
1444-
&config,
1445-
&stake_pool_address,
1446-
&stake_account,
1447-
&token_receiver,
1448-
)
1383+
command_vsa_add(&config, &stake_pool_address, &stake_account)
14491384
}
14501385
("remove-validator", Some(arg_matches)) => {
14511386
let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();
14521387
let stake_account = pubkey_of(arg_matches, "stake_account").unwrap();
1453-
let withdraw_from = pubkey_of(arg_matches, "withdraw_from").unwrap();
14541388
let new_authority: Option<Pubkey> = pubkey_of(arg_matches, "new_authority");
1455-
command_vsa_remove(
1456-
&config,
1457-
&stake_pool_address,
1458-
&stake_account,
1459-
&withdraw_from,
1460-
&new_authority,
1461-
)
1389+
command_vsa_remove(&config, &stake_pool_address, &stake_account, &new_authority)
14621390
}
14631391
("deposit", Some(arg_matches)) => {
14641392
let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();

stake-pool/program/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ pub enum StakePoolError {
8585
/// Pool token supply is not zero on initialization
8686
#[error("NonZeroPoolTokenSupply")]
8787
NonZeroPoolTokenSupply,
88+
/// The lamports in the validator stake account is not equal to the minimum
89+
#[error("StakeLamportsNotEqualToMinimum")]
90+
StakeLamportsNotEqualToMinimum,
8891
}
8992
impl From<StakePoolError> for ProgramError {
9093
fn from(e: StakePoolError) -> Self {

stake-pool/program/src/instruction.rs

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -64,34 +64,40 @@ pub enum StakePoolInstruction {
6464
CreateValidatorStakeAccount,
6565

6666
/// (Staker only) Adds stake account delegated to validator to the pool's
67-
/// list of managed validators
67+
/// list of managed validators.
68+
///
69+
/// The stake account must have the rent-exempt amount plus at least 1 SOL,
70+
/// and at most 1.001 SOL.
71+
///
72+
/// Once we delegate even 1 SOL, it will accrue rewards one epoch later,
73+
/// so we'll have more than 1 active SOL at this point.
74+
/// At 10% annualized rewards, 1 epoch of 2 days will accrue
75+
/// 0.000547945 SOL, so we check that it is at least 1 SOL, and at most
76+
/// 1.001 SOL.
6877
///
6978
/// 0. `[w]` Stake pool
7079
/// 1. `[s]` Staker
7180
/// 2. `[]` Stake pool deposit authority
7281
/// 3. `[]` Stake pool withdraw authority
7382
/// 4. `[w]` Validator stake list storage account
7483
/// 5. `[w]` Stake account to add to the pool, its withdraw authority should be set to stake pool deposit
75-
/// 6. `[w]` User account to receive pool tokens
76-
/// 7. `[w]` Pool token mint account
77-
/// 8. `[]` Clock sysvar (required)
78-
/// 9. '[]' Sysvar stake history account
79-
/// 10. `[]` Pool token program id,
80-
/// 11. `[]` Stake program id,
84+
/// 6. `[]` Clock sysvar
85+
/// 7. '[]' Sysvar stake history account
86+
/// 8. `[]` Stake program
8187
AddValidatorToPool,
8288

8389
/// (Staker only) Removes validator from the pool
8490
///
91+
/// Only succeeds if the validator stake account has the minimum of 1 SOL
92+
/// plus the rent-exempt amount.
93+
///
8594
/// 0. `[w]` Stake pool
8695
/// 1. `[s]` Staker
8796
/// 2. `[]` Stake pool withdraw authority
8897
/// 3. `[]` New withdraw/staker authority to set in the stake account
8998
/// 4. `[w]` Validator stake list storage account
9099
/// 5. `[w]` Stake account to remove from the pool
91-
/// 6. `[w]` User account with pool tokens to burn from
92-
/// 7. `[w]` Pool token mint account
93-
/// 8. '[]' Sysvar clock account (required)
94-
/// 9. `[]` Pool token program id
100+
/// 8. '[]' Sysvar clock
95101
/// 10. `[]` Stake program id,
96102
RemoveValidatorFromPool,
97103

@@ -197,7 +203,10 @@ pub enum StakePoolInstruction {
197203
Deposit,
198204

199205
/// Withdraw the token from the pool at the current ratio.
200-
/// The amount withdrawn is the MIN(u64, stake size)
206+
///
207+
/// Succeeds if the stake account has enough SOL to cover the desired amount
208+
/// of pool tokens, and if the withdrawal keeps the total staked amount
209+
/// above the minimum of rent-exempt amount + 1 SOL.
201210
///
202211
/// A validator stake account can be withdrawn from freely, and the reserve
203212
/// can only be drawn from if there is no active stake left, where all
@@ -307,9 +316,6 @@ pub fn add_validator_to_pool(
307316
stake_pool_withdraw: &Pubkey,
308317
validator_list: &Pubkey,
309318
stake_account: &Pubkey,
310-
pool_token_receiver: &Pubkey,
311-
pool_mint: &Pubkey,
312-
token_program_id: &Pubkey,
313319
) -> Result<Instruction, ProgramError> {
314320
let accounts = vec![
315321
AccountMeta::new(*stake_pool, false),
@@ -318,11 +324,8 @@ pub fn add_validator_to_pool(
318324
AccountMeta::new_readonly(*stake_pool_withdraw, false),
319325
AccountMeta::new(*validator_list, false),
320326
AccountMeta::new(*stake_account, false),
321-
AccountMeta::new(*pool_token_receiver, false),
322-
AccountMeta::new(*pool_mint, false),
323327
AccountMeta::new_readonly(sysvar::clock::id(), false),
324328
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
325-
AccountMeta::new_readonly(*token_program_id, false),
326329
AccountMeta::new_readonly(stake_program::id(), false),
327330
];
328331
Ok(Instruction {
@@ -341,9 +344,6 @@ pub fn remove_validator_from_pool(
341344
new_stake_authority: &Pubkey,
342345
validator_list: &Pubkey,
343346
stake_account: &Pubkey,
344-
burn_from: &Pubkey,
345-
pool_mint: &Pubkey,
346-
token_program_id: &Pubkey,
347347
) -> Result<Instruction, ProgramError> {
348348
let accounts = vec![
349349
AccountMeta::new(*stake_pool, false),
@@ -352,10 +352,7 @@ pub fn remove_validator_from_pool(
352352
AccountMeta::new_readonly(*new_stake_authority, false),
353353
AccountMeta::new(*validator_list, false),
354354
AccountMeta::new(*stake_account, false),
355-
AccountMeta::new(*burn_from, false),
356-
AccountMeta::new(*pool_mint, false),
357355
AccountMeta::new_readonly(sysvar::clock::id(), false),
358-
AccountMeta::new_readonly(*token_program_id, false),
359356
AccountMeta::new_readonly(stake_program::id(), false),
360357
];
361358
Ok(Instruction {

stake-pool/program/src/lib.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,28 @@ pub mod entrypoint;
1414

1515
// Export current sdk types for downstream users building with a different sdk version
1616
pub use solana_program;
17-
use solana_program::pubkey::Pubkey;
17+
use {
18+
crate::stake_program::Meta,
19+
solana_program::{native_token::LAMPORTS_PER_SOL, pubkey::Pubkey},
20+
};
1821

1922
/// Seed for deposit authority seed
2023
const AUTHORITY_DEPOSIT: &[u8] = b"deposit";
2124

2225
/// Seed for withdraw authority seed
2326
const AUTHORITY_WITHDRAW: &[u8] = b"withdraw";
2427

28+
/// Minimum amount of staked SOL required in a validator stake account to allow
29+
/// for merges without a mismatch on credits observed
30+
pub const MINIMUM_ACTIVE_STAKE: u64 = LAMPORTS_PER_SOL;
31+
32+
/// Get the stake amount under consideration when calculating pool token
33+
/// conversions
34+
pub fn minimum_stake_lamports(meta: &Meta) -> u64 {
35+
meta.rent_exempt_reserve
36+
.saturating_add(MINIMUM_ACTIVE_STAKE)
37+
}
38+
2539
/// Generates the deposit authority program address for the stake pool
2640
pub fn find_deposit_authority_program_address(
2741
program_id: &Pubkey,

0 commit comments

Comments
 (0)