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

Commit 11e207c

Browse files
authored
stake-pool: Add user transfer authority on withdraw (#1640)
The stake pool expects pool tokens to be delegated to the withdraw authority before performing a withdrawal. If a user delegates too many tokens to the withdraw authority, anyone else can take the rest of their tokens by doing their own withdrawal. Delegate pool tokens to an ephemeral keypair and sign with that.
1 parent 14bdbdc commit 11e207c

File tree

6 files changed

+94
-43
lines changed

6 files changed

+94
-43
lines changed

stake-pool/cli/src/main.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -877,15 +877,20 @@ fn command_withdraw(
877877

878878
// Construct transaction to withdraw from withdraw_accounts account list
879879
let mut instructions: Vec<Instruction> = vec![];
880-
let mut signers = vec![config.fee_payer.as_ref(), config.token_owner.as_ref()];
880+
let user_transfer_authority = Keypair::new(); // ephemeral keypair just to do the transfer
881+
let mut signers = vec![
882+
config.fee_payer.as_ref(),
883+
config.token_owner.as_ref(),
884+
&user_transfer_authority,
885+
];
881886
let stake_receiver_account = Keypair::new(); // Will be added to signers if creating new account
882887

883888
instructions.push(
884889
// Approve spending token
885890
spl_token::instruction::approve(
886891
&spl_token::id(),
887892
&withdraw_from,
888-
&pool_withdraw_authority,
893+
&user_transfer_authority.pubkey(),
889894
&config.token_owner.pubkey(),
890895
&[],
891896
pool_amount,
@@ -948,6 +953,7 @@ fn command_withdraw(
948953
&withdraw_account.address,
949954
&stake_receiver.unwrap(), // Cannot be none at this point
950955
&config.staker.pubkey(),
956+
&user_transfer_authority.pubkey(),
951957
&withdraw_from,
952958
&stake_pool.pool_mint,
953959
&spl_token::id(),

stake-pool/program/src/instruction.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,13 @@ pub enum StakePoolInstruction {
225225
/// 3. `[w]` Validator or reserve stake account to split
226226
/// 4. `[w]` Unitialized stake account to receive withdrawal
227227
/// 5. `[]` User account to set as a new withdraw authority
228-
/// 6. `[w]` User account with pool tokens to burn from
229-
/// 7. `[w]` Pool token mint account
230-
/// 8. '[]' Sysvar clock account (required)
231-
/// 9. `[]` Pool token program id
232-
/// 10. `[]` Stake program id,
233-
/// userdata: amount to withdraw
228+
/// 6. `[s]` User transfer authority, for pool token account
229+
/// 7. `[w]` User account with pool tokens to burn from
230+
/// 8. `[w]` Pool token mint account
231+
/// 9. `[]` Sysvar clock account (required)
232+
/// 10. `[]` Pool token program id
233+
/// 11. `[]` Stake program id,
234+
/// userdata: amount of pool tokens to withdraw
234235
Withdraw(u64),
235236

236237
/// (Manager only) Update manager
@@ -645,8 +646,9 @@ pub fn withdraw(
645646
stake_pool_withdraw: &Pubkey,
646647
stake_to_split: &Pubkey,
647648
stake_to_receive: &Pubkey,
648-
user_withdrawer: &Pubkey,
649-
burn_from: &Pubkey,
649+
user_stake_authority: &Pubkey,
650+
user_transfer_authority: &Pubkey,
651+
user_pool_token_account: &Pubkey,
650652
pool_mint: &Pubkey,
651653
token_program_id: &Pubkey,
652654
amount: u64,
@@ -657,8 +659,9 @@ pub fn withdraw(
657659
AccountMeta::new_readonly(*stake_pool_withdraw, false),
658660
AccountMeta::new(*stake_to_split, false),
659661
AccountMeta::new(*stake_to_receive, false),
660-
AccountMeta::new_readonly(*user_withdrawer, false),
661-
AccountMeta::new(*burn_from, false),
662+
AccountMeta::new_readonly(*user_stake_authority, false),
663+
AccountMeta::new_readonly(*user_transfer_authority, true),
664+
AccountMeta::new(*user_pool_token_account, false),
662665
AccountMeta::new(*pool_mint, false),
663666
AccountMeta::new_readonly(sysvar::clock::id(), false),
664667
AccountMeta::new_readonly(*token_program_id, false),

stake-pool/program/src/processor.rs

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -336,19 +336,12 @@ impl Processor {
336336
/// Issue a spl_token `Burn` instruction.
337337
#[allow(clippy::too_many_arguments)]
338338
fn token_burn<'a>(
339-
stake_pool: &Pubkey,
340339
token_program: AccountInfo<'a>,
341340
burn_account: AccountInfo<'a>,
342341
mint: AccountInfo<'a>,
343342
authority: AccountInfo<'a>,
344-
authority_type: &[u8],
345-
bump_seed: u8,
346343
amount: u64,
347344
) -> Result<(), ProgramError> {
348-
let me_bytes = stake_pool.to_bytes();
349-
let authority_signature_seeds = [&me_bytes[..32], authority_type, &[bump_seed]];
350-
let signers = &[&authority_signature_seeds[..]];
351-
352345
let ix = spl_token::instruction::burn(
353346
token_program.key,
354347
burn_account.key,
@@ -358,11 +351,7 @@ impl Processor {
358351
amount,
359352
)?;
360353

361-
invoke_signed(
362-
&ix,
363-
&[burn_account, mint, authority, token_program],
364-
signers,
365-
)
354+
invoke(&ix, &[burn_account, mint, authority, token_program])
366355
}
367356

368357
/// Issue a spl_token `MintTo` instruction.
@@ -1640,7 +1629,8 @@ impl Processor {
16401629
let withdraw_authority_info = next_account_info(account_info_iter)?;
16411630
let stake_split_from = next_account_info(account_info_iter)?;
16421631
let stake_split_to = next_account_info(account_info_iter)?;
1643-
let user_stake_authority = next_account_info(account_info_iter)?;
1632+
let user_stake_authority_info = next_account_info(account_info_iter)?;
1633+
let user_transfer_authority_info = next_account_info(account_info_iter)?;
16441634
let burn_from_info = next_account_info(account_info_iter)?;
16451635
let pool_mint_info = next_account_info(account_info_iter)?;
16461636
let clock_info = next_account_info(account_info_iter)?;
@@ -1742,13 +1732,10 @@ impl Processor {
17421732
};
17431733

17441734
Self::token_burn(
1745-
stake_pool_info.key,
17461735
token_program_info.clone(),
17471736
burn_from_info.clone(),
17481737
pool_mint_info.clone(),
1749-
withdraw_authority_info.clone(),
1750-
AUTHORITY_WITHDRAW,
1751-
stake_pool.withdraw_bump_seed,
1738+
user_transfer_authority_info.clone(),
17521739
pool_tokens,
17531740
)?;
17541741

@@ -1768,7 +1755,7 @@ impl Processor {
17681755
withdraw_authority_info.clone(),
17691756
AUTHORITY_WITHDRAW,
17701757
stake_pool.withdraw_bump_seed,
1771-
user_stake_authority.key,
1758+
user_stake_authority_info.key,
17721759
clock_info.clone(),
17731760
stake_program_info.clone(),
17741761
)?;

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ pub async fn delegate_tokens(
182182
delegate: &Pubkey,
183183
amount: u64,
184184
) {
185-
let mut transaction = Transaction::new_with_payer(
185+
let transaction = Transaction::new_signed_with_payer(
186186
&[spl_token::instruction::approve(
187187
&spl_token::id(),
188188
&account,
@@ -193,8 +193,9 @@ pub async fn delegate_tokens(
193193
)
194194
.unwrap()],
195195
Some(&payer.pubkey()),
196+
&[payer, manager],
197+
*recent_blockhash,
196198
);
197-
transaction.sign(&[payer, manager], *recent_blockhash);
198199
banks_client.process_transaction(transaction).await.unwrap();
199200
}
200201

@@ -656,6 +657,7 @@ impl StakePoolAccounts {
656657
payer: &Keypair,
657658
recent_blockhash: &Hash,
658659
stake_recipient: &Pubkey,
660+
user_transfer_authority: &Keypair,
659661
pool_account: &Pubkey,
660662
validator_stake_account: &Pubkey,
661663
recipient_new_authority: &Pubkey,
@@ -670,14 +672,15 @@ impl StakePoolAccounts {
670672
validator_stake_account,
671673
stake_recipient,
672674
recipient_new_authority,
675+
&user_transfer_authority.pubkey(),
673676
pool_account,
674677
&self.pool_mint.pubkey(),
675678
&spl_token::id(),
676679
amount,
677680
)
678681
.unwrap()],
679682
Some(&payer.pubkey()),
680-
&[payer],
683+
&[payer, user_transfer_authority],
681684
*recent_blockhash,
682685
);
683686
banks_client.process_transaction(transaction).await.err()

stake-pool/program/tests/vsa_remove.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,13 +485,25 @@ async fn success_with_deactivating_transient_stake() {
485485
)
486486
.await;
487487

488+
let user_transfer_authority = Keypair::new();
488489
let new_authority = Pubkey::new_unique();
490+
delegate_tokens(
491+
&mut banks_client,
492+
&payer,
493+
&recent_blockhash,
494+
&deposit_info.pool_account.pubkey(),
495+
&deposit_info.authority,
496+
&user_transfer_authority.pubkey(),
497+
1,
498+
)
499+
.await;
489500
let error = stake_pool_accounts
490501
.withdraw_stake(
491502
&mut banks_client,
492503
&payer,
493504
&recent_blockhash,
494505
&user_stake_recipient.pubkey(),
506+
&user_transfer_authority,
495507
&deposit_info.pool_account.pubkey(),
496508
&validator_stake.stake_account,
497509
&new_authority,

0 commit comments

Comments
 (0)