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

Commit fc0d6a2

Browse files
authored
stake-pool: Split from stake account during removal (#2367)
If the stake pool gives over the validator stake account to the staker, they may keep it and make it impossible to re-add that validator in the future. Split the whole amount into a new stake account on removal.
1 parent 23c487d commit fc0d6a2

File tree

8 files changed

+200
-82
lines changed

8 files changed

+200
-82
lines changed

stake-pool/cli/src/main.rs

Lines changed: 70 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,33 @@ fn checked_transaction_with_signers<T: Signers>(
152152
Ok(transaction)
153153
}
154154

155+
fn new_stake_account(
156+
fee_payer: &Pubkey,
157+
instructions: &mut Vec<Instruction>,
158+
lamports: u64,
159+
) -> Keypair {
160+
// Account for tokens not specified, creating one
161+
let stake_receiver_keypair = Keypair::new();
162+
let stake_receiver_pubkey = stake_receiver_keypair.pubkey();
163+
println!(
164+
"Creating account to receive stake {}",
165+
stake_receiver_pubkey
166+
);
167+
168+
instructions.push(
169+
// Creating new account
170+
system_instruction::create_account(
171+
fee_payer,
172+
&stake_receiver_pubkey,
173+
lamports,
174+
STAKE_STATE_LEN as u64,
175+
&stake_program::id(),
176+
),
177+
);
178+
179+
stake_receiver_keypair
180+
}
181+
155182
#[allow(clippy::too_many_arguments)]
156183
fn command_create_pool(
157184
config: &Config,
@@ -423,6 +450,7 @@ fn command_vsa_remove(
423450
stake_pool_address: &Pubkey,
424451
vote_account: &Pubkey,
425452
new_authority: &Option<Pubkey>,
453+
stake_receiver: &Option<Pubkey>,
426454
) -> CommandResult {
427455
if !config.no_update {
428456
command_update(config, stake_pool_address, false, false)?;
@@ -437,6 +465,20 @@ fn command_vsa_remove(
437465

438466
let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?;
439467

468+
let mut instructions = vec![];
469+
let mut stake_keypair = None;
470+
471+
let stake_receiver = stake_receiver.unwrap_or_else(|| {
472+
let new_stake_keypair = new_stake_account(
473+
&config.fee_payer.pubkey(),
474+
&mut instructions,
475+
/* stake_receiver_account_balance = */ 0,
476+
);
477+
let stake_pubkey = new_stake_keypair.pubkey();
478+
stake_keypair = Some(new_stake_keypair);
479+
stake_pubkey
480+
});
481+
440482
let staker_pubkey = config.staker.pubkey();
441483
let new_authority = new_authority.as_ref().unwrap_or(&staker_pubkey);
442484

@@ -446,6 +488,9 @@ fn command_vsa_remove(
446488
.ok_or("Vote account not found in validator list")?;
447489

448490
let mut signers = vec![config.fee_payer.as_ref(), config.staker.as_ref()];
491+
if let Some(stake_keypair) = stake_keypair.as_ref() {
492+
signers.push(stake_keypair);
493+
}
449494
unique_signers!(signers);
450495
let transaction = checked_transaction_with_signers(
451496
config,
@@ -458,6 +503,7 @@ fn command_vsa_remove(
458503
vote_account,
459504
new_authority,
460505
validator_stake_info.transient_seed_suffix_start,
506+
&stake_receiver,
461507
),
462508
],
463509
&signers,
@@ -1270,33 +1316,19 @@ fn command_withdraw(
12701316

12711317
// Use separate mutable variable because withdraw might create a new account
12721318
let stake_receiver = stake_receiver_param.unwrap_or_else(|| {
1273-
// Account for tokens not specified, creating one
1274-
let stake_receiver_account = Keypair::new(); // Will be added to signers if creating new account
1275-
let stake_receiver_pubkey = stake_receiver_account.pubkey();
1276-
println!(
1277-
"Creating account to receive stake {}",
1278-
stake_receiver_pubkey
1279-
);
1280-
12811319
let stake_receiver_account_balance = config
12821320
.rpc_client
12831321
.get_minimum_balance_for_rent_exemption(STAKE_STATE_LEN)
12841322
.unwrap();
1285-
1286-
instructions.push(
1287-
// Creating new account
1288-
system_instruction::create_account(
1289-
&config.fee_payer.pubkey(),
1290-
&stake_receiver_pubkey,
1291-
stake_receiver_account_balance,
1292-
STAKE_STATE_LEN as u64,
1293-
&stake_program::id(),
1294-
),
1323+
let stake_keypair = new_stake_account(
1324+
&config.fee_payer.pubkey(),
1325+
&mut instructions,
1326+
stake_receiver_account_balance,
12951327
);
1296-
1328+
let stake_pubkey = stake_keypair.pubkey();
12971329
total_rent_free_balances += stake_receiver_account_balance;
1298-
new_stake_keypairs.push(stake_receiver_account);
1299-
stake_receiver_pubkey
1330+
new_stake_keypairs.push(stake_keypair);
1331+
stake_pubkey
13001332
});
13011333

13021334
instructions.push(spl_stake_pool::instruction::withdraw_stake(
@@ -1753,6 +1785,14 @@ fn main() {
17531785
.help("New authority to set as Staker and Withdrawer in the stake account removed from the pool.
17541786
Defaults to the client keypair."),
17551787
)
1788+
.arg(
1789+
Arg::with_name("stake_receiver")
1790+
.long("stake-receiver")
1791+
.validator(is_pubkey)
1792+
.value_name("ADDRESS")
1793+
.takes_value(true)
1794+
.help("Stake account to receive SOL from the stake pool. Defaults to a new stake account."),
1795+
)
17561796
)
17571797
.subcommand(SubCommand::with_name("increase-validator-stake")
17581798
.about("Increase stake to a validator, drawing from the stake pool reserve. Must be signed by the pool staker.")
@@ -2318,8 +2358,15 @@ fn main() {
23182358
("remove-validator", Some(arg_matches)) => {
23192359
let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();
23202360
let vote_account = pubkey_of(arg_matches, "vote_account").unwrap();
2321-
let new_authority: Option<Pubkey> = pubkey_of(arg_matches, "new_authority");
2322-
command_vsa_remove(&config, &stake_pool_address, &vote_account, &new_authority)
2361+
let new_authority = pubkey_of(arg_matches, "new_authority");
2362+
let stake_receiver = pubkey_of(arg_matches, "stake_receiver");
2363+
command_vsa_remove(
2364+
&config,
2365+
&stake_pool_address,
2366+
&vote_account,
2367+
&new_authority,
2368+
&stake_receiver,
2369+
)
23232370
}
23242371
("increase-validator-stake", Some(arg_matches)) => {
23252372
let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();

stake-pool/program/src/instruction.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,9 @@ pub enum StakePoolInstruction {
127127
/// 4. `[w]` Validator stake list storage account
128128
/// 5. `[w]` Stake account to remove from the pool
129129
/// 6. `[]` Transient stake account, to check that that we're not trying to activate
130-
/// 7. '[]' Sysvar clock
131-
/// 8. `[]` Stake program id,
130+
/// 7. `[w]` Destination stake account, to receive the minimum SOL from the validator stake account. Must be
131+
/// 8. `[]` Sysvar clock
132+
/// 9. `[]` Stake program id,
132133
RemoveValidatorFromPool,
133134

134135
/// (Staker only) Decrease active stake on a validator, eventually moving it to the reserve
@@ -483,6 +484,7 @@ pub fn remove_validator_from_pool(
483484
validator_list: &Pubkey,
484485
stake_account: &Pubkey,
485486
transient_stake_account: &Pubkey,
487+
destination_stake_account: &Pubkey,
486488
) -> Instruction {
487489
let accounts = vec![
488490
AccountMeta::new(*stake_pool, false),
@@ -492,6 +494,7 @@ pub fn remove_validator_from_pool(
492494
AccountMeta::new(*validator_list, false),
493495
AccountMeta::new(*stake_account, false),
494496
AccountMeta::new_readonly(*transient_stake_account, false),
497+
AccountMeta::new(*destination_stake_account, false),
495498
AccountMeta::new_readonly(sysvar::clock::id(), false),
496499
AccountMeta::new_readonly(stake_program::id(), false),
497500
];
@@ -658,6 +661,7 @@ pub fn remove_validator_from_pool_with_vote(
658661
vote_account_address: &Pubkey,
659662
new_stake_account_authority: &Pubkey,
660663
transient_stake_seed: u64,
664+
destination_stake_address: &Pubkey,
661665
) -> Instruction {
662666
let pool_withdraw_authority =
663667
find_withdraw_authority_program_address(program_id, stake_pool_address).0;
@@ -678,6 +682,7 @@ pub fn remove_validator_from_pool_with_vote(
678682
&stake_pool.validator_list,
679683
&stake_account_address,
680684
&transient_stake_account,
685+
destination_stake_address,
681686
)
682687
}
683688

stake-pool/program/src/processor.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,7 @@ impl Processor {
886886
let validator_list_info = next_account_info(account_info_iter)?;
887887
let stake_account_info = next_account_info(account_info_iter)?;
888888
let transient_stake_account_info = next_account_info(account_info_iter)?;
889+
let destination_stake_account_info = next_account_info(account_info_iter)?;
889890
let clock_info = next_account_info(account_info_iter)?;
890891
let clock = &Clock::from_account_info(clock_info)?;
891892
let stake_program_info = next_account_info(account_info_iter)?;
@@ -994,12 +995,23 @@ impl Processor {
994995
StakeStatus::ReadyForRemoval
995996
};
996997

997-
Self::stake_authorize_signed(
998+
// split whole thing into destination stake account
999+
Self::stake_split(
9981000
stake_pool_info.key,
9991001
stake_account_info.clone(),
10001002
withdraw_authority_info.clone(),
10011003
AUTHORITY_WITHDRAW,
10021004
stake_pool.stake_withdraw_bump_seed,
1005+
stake_account_info.lamports(),
1006+
destination_stake_account_info.clone(),
1007+
)?;
1008+
1009+
Self::stake_authorize_signed(
1010+
stake_pool_info.key,
1011+
destination_stake_account_info.clone(),
1012+
withdraw_authority_info.clone(),
1013+
AUTHORITY_WITHDRAW,
1014+
stake_pool.stake_withdraw_bump_seed,
10031015
new_stake_authority_info.key,
10041016
clock_info.clone(),
10051017
stake_program_info.clone(),

stake-pool/program/tests/helpers/mod.rs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,20 +1093,31 @@ impl StakePoolAccounts {
10931093
new_authority: &Pubkey,
10941094
validator_stake: &Pubkey,
10951095
transient_stake: &Pubkey,
1096+
destination_stake: &Keypair,
10961097
) -> Option<TransportError> {
10971098
let transaction = Transaction::new_signed_with_payer(
1098-
&[instruction::remove_validator_from_pool(
1099-
&id(),
1100-
&self.stake_pool.pubkey(),
1101-
&self.staker.pubkey(),
1102-
&self.withdraw_authority,
1103-
&new_authority,
1104-
&self.validator_list.pubkey(),
1105-
validator_stake,
1106-
transient_stake,
1107-
)],
1099+
&[
1100+
system_instruction::create_account(
1101+
&payer.pubkey(),
1102+
&destination_stake.pubkey(),
1103+
0,
1104+
std::mem::size_of::<stake_program::StakeState>() as u64,
1105+
&stake_program::id(),
1106+
),
1107+
instruction::remove_validator_from_pool(
1108+
&id(),
1109+
&self.stake_pool.pubkey(),
1110+
&self.staker.pubkey(),
1111+
&self.withdraw_authority,
1112+
&new_authority,
1113+
&self.validator_list.pubkey(),
1114+
validator_stake,
1115+
transient_stake,
1116+
&destination_stake.pubkey(),
1117+
),
1118+
],
11081119
Some(&payer.pubkey()),
1109-
&[payer, &self.staker],
1120+
&[payer, &self.staker, destination_stake],
11101121
*recent_blockhash,
11111122
);
11121123
banks_client.process_transaction(transaction).await.err()

stake-pool/program/tests/huge_pool.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ async fn remove_validator_from_pool() {
404404
);
405405

406406
let new_authority = Pubkey::new_unique();
407+
let destination_stake = Keypair::new();
407408
let error = stake_pool_accounts
408409
.remove_validator_from_pool(
409410
&mut context.banks_client,
@@ -412,6 +413,7 @@ async fn remove_validator_from_pool() {
412413
&new_authority,
413414
&stake_address,
414415
&transient_stake_address,
416+
&destination_stake,
415417
)
416418
.await;
417419
assert!(error.is_none());
@@ -431,6 +433,7 @@ async fn remove_validator_from_pool() {
431433
);
432434

433435
let new_authority = Pubkey::new_unique();
436+
let destination_stake = Keypair::new();
434437
let error = stake_pool_accounts
435438
.remove_validator_from_pool(
436439
&mut context.banks_client,
@@ -439,6 +442,7 @@ async fn remove_validator_from_pool() {
439442
&new_authority,
440443
&stake_address,
441444
&transient_stake_address,
445+
&destination_stake,
442446
)
443447
.await;
444448
assert!(error.is_none());
@@ -455,6 +459,7 @@ async fn remove_validator_from_pool() {
455459
);
456460

457461
let new_authority = Pubkey::new_unique();
462+
let destination_stake = Keypair::new();
458463
let error = stake_pool_accounts
459464
.remove_validator_from_pool(
460465
&mut context.banks_client,
@@ -463,6 +468,7 @@ async fn remove_validator_from_pool() {
463468
&new_authority,
464469
&stake_address,
465470
&transient_stake_address,
471+
&destination_stake,
466472
)
467473
.await;
468474
assert!(error.is_none());

stake-pool/program/tests/set_preferred.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ async fn fail_ready_for_removal() {
223223
transient_stake_seed,
224224
);
225225
let new_authority = Pubkey::new_unique();
226+
let destination_stake = Keypair::new();
226227
let remove_err = stake_pool_accounts
227228
.remove_validator_from_pool(
228229
&mut banks_client,
@@ -231,6 +232,7 @@ async fn fail_ready_for_removal() {
231232
&new_authority,
232233
&validator_stake_account.stake_account,
233234
&transient_stake_address,
235+
&destination_stake,
234236
)
235237
.await;
236238
assert!(remove_err.is_none());

stake-pool/program/tests/update_validator_list_balance.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ use {
66
helpers::*,
77
solana_program::{borsh::try_from_slice_unchecked, program_pack::Pack, pubkey::Pubkey},
88
solana_program_test::*,
9-
solana_sdk::{signature::Signer, system_instruction, transaction::Transaction},
9+
solana_sdk::{
10+
signature::{Keypair, Signer},
11+
system_instruction,
12+
transaction::Transaction,
13+
},
1014
spl_stake_pool::{
1115
find_transient_stake_program_address, id, instruction, stake_program,
1216
state::{StakePool, StakeStatus, ValidatorList},
@@ -462,6 +466,7 @@ async fn merge_transient_stake_after_remove() {
462466
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
463467
let deactivated_lamports = lamports;
464468
let new_authority = Pubkey::new_unique();
469+
let destination_stake = Keypair::new();
465470
// Decrease and remove all validators
466471
for stake_account in &stake_accounts {
467472
let error = stake_pool_accounts
@@ -484,6 +489,7 @@ async fn merge_transient_stake_after_remove() {
484489
&new_authority,
485490
&stake_account.stake_account,
486491
&stake_account.transient_stake_account,
492+
&destination_stake,
487493
)
488494
.await;
489495
assert!(error.is_none());

0 commit comments

Comments
 (0)