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

Commit b9c2bbc

Browse files
authored
Added set-staking-auth and set-owner commands to stake pool CLI (#827)
* Added set-staking-auth and set-owner commands to stake pool CLI * Error handling refactored, fixed new fee owner setting, fixed set owner arg group settings * Added check for fee account mint to stake pool initialization and set owner methods
1 parent 39c574a commit b9c2bbc

File tree

3 files changed

+211
-22
lines changed

3 files changed

+211
-22
lines changed

stake-pool/cli/src/main.rs

Lines changed: 191 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use clap::{
22
crate_description, crate_name, crate_version, value_t, value_t_or_exit, App, AppSettings, Arg,
3-
SubCommand,
3+
ArgGroup, SubCommand,
44
};
55
use solana_account_decoder::UiAccountEncoding;
66
use solana_clap_utils::{
@@ -25,8 +25,8 @@ use solana_sdk::{
2525
};
2626
use spl_stake_pool::{
2727
instruction::{
28-
claim, deposit, initialize as initialize_pool, withdraw, Fee as PoolFee,
29-
InitArgs as PoolInitArgs,
28+
claim, deposit, initialize as initialize_pool, set_owner, set_staking_authority, withdraw,
29+
Fee as PoolFee, InitArgs as PoolInitArgs,
3030
},
3131
processor::Processor as PoolProcessor,
3232
stake::authorize as authorize_stake,
@@ -361,16 +361,16 @@ fn command_list(config: &Config, pool: &Pubkey) -> CommandResult {
361361
let accounts = get_authority_accounts(config, &pool_withdraw_authority);
362362

363363
if accounts.is_empty() {
364-
println!("No accounts found.")
365-
} else {
366-
let mut total_balance: u64 = 0;
367-
for (pubkey, account) in accounts {
368-
let balance = account.lamports;
369-
total_balance += balance;
370-
println!("{}\t{} SOL", pubkey, lamports_to_sol(balance));
371-
}
372-
println!("Total: {} SOL", lamports_to_sol(total_balance));
364+
return Err("No accounts found.".to_string().into());
365+
}
366+
367+
let mut total_balance: u64 = 0;
368+
for (pubkey, account) in accounts {
369+
let balance = account.lamports;
370+
total_balance += balance;
371+
println!("{}\t{} SOL", pubkey, lamports_to_sol(balance));
373372
}
373+
println!("Total: {} SOL", lamports_to_sol(total_balance));
374374

375375
Ok(None)
376376
}
@@ -419,25 +419,23 @@ fn command_withdraw(
419419
TokenAccount::unpack_from_slice(account_data.as_slice()).unwrap();
420420

421421
if account_data.mint != pool_data.pool_mint {
422-
println!("Wrong token account.");
423-
return Ok(None);
422+
return Err("Wrong token account.".to_string().into());
424423
}
425424

426425
// Check burn_from balance
427426
let max_withdraw_amount = pool_tokens_to_stake_amount(&pool_data, account_data.amount);
428427
if max_withdraw_amount < amount {
429-
println!(
428+
return Err(format!(
430429
"Not enough token balance to withdraw {} SOL.\nMaximum withdraw amount is {} SOL.",
431430
lamports_to_sol(amount),
432431
lamports_to_sol(max_withdraw_amount)
433-
);
434-
return Ok(None);
432+
)
433+
.into());
435434
}
436435

437436
let mut accounts = get_authority_accounts(config, &pool_withdraw_authority);
438437
if accounts.is_empty() {
439-
println!("No accounts found.");
440-
return Ok(None);
438+
return Err("No accounts found.".to_string().into());
441439
}
442440
// Sort from lowest to highest balance
443441
accounts.sort_by(|a, b| a.1.lamports.cmp(&b.1.lamports));
@@ -555,11 +553,109 @@ fn command_withdraw(
555553
return Ok(Some(transaction));
556554
}
557555

558-
println!(
556+
Err(format!(
559557
"No stake accounts found in this pool with enough balance to withdraw {} SOL.",
560558
lamports_to_sol(amount)
559+
)
560+
.into())
561+
}
562+
563+
fn command_set_staking_auth(
564+
config: &Config,
565+
pool: &Pubkey,
566+
stake_account: &Pubkey,
567+
new_staker: &Pubkey,
568+
) -> CommandResult {
569+
let pool_data = config.rpc_client.get_account_data(&pool)?;
570+
let pool_data: StakePool = PoolState::deserialize(pool_data.as_slice())
571+
.unwrap()
572+
.stake_pool()
573+
.unwrap();
574+
575+
let pool_withdraw_authority: Pubkey = PoolProcessor::authority_id(
576+
&spl_stake_pool::id(),
577+
pool,
578+
PoolProcessor::AUTHORITY_WITHDRAW,
579+
pool_data.withdraw_bump_seed,
580+
)
581+
.unwrap();
582+
583+
let mut transaction = Transaction::new_with_payer(
584+
&[set_staking_authority(
585+
&spl_stake_pool::id(),
586+
&pool,
587+
&config.owner.pubkey(),
588+
&pool_withdraw_authority,
589+
&stake_account,
590+
&new_staker,
591+
&stake_program_id(),
592+
)?],
593+
Some(&config.fee_payer.pubkey()),
561594
);
562-
Ok(None)
595+
596+
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
597+
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
598+
let mut signers = vec![config.fee_payer.as_ref(), config.owner.as_ref()];
599+
unique_signers!(signers);
600+
transaction.sign(&signers, recent_blockhash);
601+
Ok(Some(transaction))
602+
}
603+
604+
fn command_set_owner(
605+
config: &Config,
606+
pool: &Pubkey,
607+
new_owner: &Option<Pubkey>,
608+
new_fee_receiver: &Option<Pubkey>,
609+
) -> CommandResult {
610+
let pool_data = config.rpc_client.get_account_data(&pool)?;
611+
let pool_data: StakePool = PoolState::deserialize(pool_data.as_slice())
612+
.unwrap()
613+
.stake_pool()
614+
.unwrap();
615+
616+
// If new accounts are missing in the arguments use the old ones
617+
let new_owner: Pubkey = match new_owner {
618+
None => pool_data.owner,
619+
Some(value) => *value,
620+
};
621+
let new_fee_receiver: Pubkey = match new_fee_receiver {
622+
None => pool_data.owner_fee_account,
623+
Some(value) => {
624+
// Check for fee receiver being a valid token account and have to same mint as the stake pool
625+
let account_data = config.rpc_client.get_account_data(value)?;
626+
let account_data: TokenAccount =
627+
match TokenAccount::unpack_from_slice(account_data.as_slice()) {
628+
Ok(data) => data,
629+
Err(_) => {
630+
return Err(format!("{} is not a token account", value).into());
631+
}
632+
};
633+
if account_data.mint != pool_data.pool_mint {
634+
return Err("Fee receiver account belongs to a different mint"
635+
.to_string()
636+
.into());
637+
}
638+
*value
639+
}
640+
};
641+
642+
let mut transaction = Transaction::new_with_payer(
643+
&[set_owner(
644+
&spl_stake_pool::id(),
645+
&pool,
646+
&config.owner.pubkey(),
647+
&new_owner,
648+
&new_fee_receiver,
649+
)?],
650+
Some(&config.fee_payer.pubkey()),
651+
);
652+
653+
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
654+
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
655+
let mut signers = vec![config.fee_payer.as_ref(), config.owner.as_ref()];
656+
unique_signers!(signers);
657+
transaction.sign(&signers, recent_blockhash);
658+
Ok(Some(transaction))
563659
}
564660

565661
fn main() {
@@ -719,6 +815,68 @@ fn main() {
719815
.help("Stake account to receive SOL from the stake pool. Defaults to a new stake account."),
720816
)
721817
)
818+
.subcommand(SubCommand::with_name("set-staking-auth").about("Changes staking authority of one of the accounts from the stake pool.")
819+
.arg(
820+
Arg::with_name("pool")
821+
.long("pool")
822+
.validator(is_pubkey)
823+
.value_name("ADDRESS")
824+
.takes_value(true)
825+
.required(true)
826+
.help("Stake pool address."),
827+
)
828+
.arg(
829+
Arg::with_name("stake_account")
830+
.long("stake-account")
831+
.validator(is_pubkey)
832+
.value_name("ADDRESS")
833+
.takes_value(true)
834+
.required(true)
835+
.help("Stake account address to change staking authority."),
836+
)
837+
.arg(
838+
Arg::with_name("new_staker")
839+
.long("new-staker")
840+
.validator(is_pubkey)
841+
.value_name("ADDRESS")
842+
.takes_value(true)
843+
.required(true)
844+
.help("Public key of the new staker account."),
845+
)
846+
)
847+
.subcommand(SubCommand::with_name("set-owner").about("Changes owner or fee receiver account for the stake pool.")
848+
.arg(
849+
Arg::with_name("pool")
850+
.long("pool")
851+
.validator(is_pubkey)
852+
.value_name("ADDRESS")
853+
.takes_value(true)
854+
.required(true)
855+
.help("Stake pool address."),
856+
)
857+
.arg(
858+
Arg::with_name("new_owner")
859+
.long("new-owner")
860+
.validator(is_pubkey)
861+
.value_name("ADDRESS")
862+
.takes_value(true)
863+
.help("Public key for the new stake pool owner."),
864+
)
865+
.arg(
866+
Arg::with_name("new_fee_receiver")
867+
.long("new-fee-receiver")
868+
.validator(is_pubkey)
869+
.value_name("ADDRESS")
870+
.takes_value(true)
871+
.help("Public key for the new account to set as the stake pool fee receiver."),
872+
)
873+
.group(ArgGroup::with_name("new_accounts")
874+
.arg("new_owner")
875+
.arg("new_fee_receiver")
876+
.required(true)
877+
.multiple(true)
878+
)
879+
)
722880
.get_matches();
723881

724882
let mut wallet_manager = None;
@@ -793,6 +951,18 @@ fn main() {
793951
let stake_receiver: Option<Pubkey> = pubkey_of(arg_matches, "stake_receiver");
794952
command_withdraw(&config, &pool_account, amount, &burn_from, &stake_receiver)
795953
}
954+
("set-staking-auth", Some(arg_matches)) => {
955+
let pool_account: Pubkey = pubkey_of(arg_matches, "pool").unwrap();
956+
let stake_account: Pubkey = pubkey_of(arg_matches, "stake_account").unwrap();
957+
let new_staker: Pubkey = pubkey_of(arg_matches, "new_staker").unwrap();
958+
command_set_staking_auth(&config, &pool_account, &stake_account, &new_staker)
959+
}
960+
("set-owner", Some(arg_matches)) => {
961+
let pool_account: Pubkey = pubkey_of(arg_matches, "pool").unwrap();
962+
let new_owner: Option<Pubkey> = pubkey_of(arg_matches, "new_owner");
963+
let new_fee_receiver: Option<Pubkey> = pubkey_of(arg_matches, "new_fee_receiver");
964+
command_set_owner(&config, &pool_account, &new_owner, &new_fee_receiver)
965+
}
796966
_ => unreachable!(),
797967
}
798968
.and_then(|transaction| {

stake-pool/program/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ pub enum Error {
4343
/// Stake pool fee > 1.
4444
#[error("FeeTooHigh")]
4545
FeeTooHigh,
46+
/// Token account is associated with the wrong mint.
47+
#[error("WrongAccountMint")]
48+
WrongAccountMint,
4649
}
4750
impl From<Error> for ProgramError {
4851
fn from(e: Error) -> Self {

stake-pool/program/src/processor.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use num_traits::FromPrimitive;
1010
use solana_program::{
1111
account_info::next_account_info, account_info::AccountInfo, decode_error::DecodeError,
1212
entrypoint::ProgramResult, info, program::invoke_signed, program_error::PrintProgramError,
13-
program_error::ProgramError, pubkey::Pubkey,
13+
program_error::ProgramError, program_pack::Pack, pubkey::Pubkey,
1414
};
1515
use std::convert::TryFrom;
1616

@@ -184,6 +184,13 @@ impl Processor {
184184
return Err(Error::FeeTooHigh.into());
185185
}
186186

187+
// Check for owner fee account to have proper mint assigned
188+
if *pool_mint_info.key
189+
!= spl_token::state::Account::unpack_from_slice(&owner_fee_info.data.borrow())?.mint
190+
{
191+
return Err(Error::WrongAccountMint.into());
192+
}
193+
187194
let (_, deposit_bump_seed) = Self::find_authority_bump_seed(
188195
program_id,
189196
stake_pool_info.key,
@@ -557,6 +564,14 @@ impl Processor {
557564
if !owner_info.is_signer {
558565
return Err(Error::InvalidInput.into());
559566
}
567+
568+
// Check for owner fee account to have proper mint assigned
569+
if stake_pool.pool_mint
570+
!= spl_token::state::Account::unpack_from_slice(&new_owner_fee_info.data.borrow())?.mint
571+
{
572+
return Err(Error::WrongAccountMint.into());
573+
}
574+
560575
stake_pool.owner = *new_owner_info.key;
561576
stake_pool.owner_fee_account = *new_owner_fee_info.key;
562577
State::Init(stake_pool).serialize(&mut stake_pool_info.data.borrow_mut())?;
@@ -612,6 +627,7 @@ impl PrintProgramError for Error {
612627
Error::InvalidOutput => info!("Error: InvalidOutput"),
613628
Error::CalculationFailure => info!("Error: CalculationFailure"),
614629
Error::FeeTooHigh => info!("Error: FeeTooHigh"),
630+
Error::WrongAccountMint => info!("Error: WrongAccountMint"),
615631
}
616632
}
617633
}

0 commit comments

Comments
 (0)