Skip to content

Commit

Permalink
cli: Add stake redelegation support
Browse files Browse the repository at this point in the history
  • Loading branch information
mvines committed Jul 12, 2022
1 parent 4a57787 commit 3dfe888
Show file tree
Hide file tree
Showing 4 changed files with 384 additions and 9 deletions.
6 changes: 6 additions & 0 deletions cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ pub enum CliCommand {
nonce_authority: SignerIndex,
memo: Option<String>,
fee_payer: SignerIndex,
redelegate_stake_account_pubkey: Option<Pubkey>,
},
SplitStake {
stake_account_pubkey: Pubkey,
Expand Down Expand Up @@ -680,6 +681,9 @@ pub fn parse_command(
("delegate-stake", Some(matches)) => {
parse_stake_delegate_stake(matches, default_signer, wallet_manager)
}
("redelegate-stake", Some(matches)) => {
parse_stake_delegate_stake(matches, default_signer, wallet_manager)
}
("withdraw-stake", Some(matches)) => {
parse_stake_withdraw_stake(matches, default_signer, wallet_manager)
}
Expand Down Expand Up @@ -1132,6 +1136,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
nonce_authority,
memo,
fee_payer,
redelegate_stake_account_pubkey,
} => process_delegate_stake(
&rpc_client,
config,
Expand All @@ -1146,6 +1151,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
*nonce_authority,
memo.as_ref(),
*fee_payer,
redelegate_stake_account_pubkey.as_ref(),
),
CliCommand::SplitStake {
stake_account_pubkey,
Expand Down
133 changes: 127 additions & 6 deletions cli/src/stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,51 @@ impl StakeSubCommands for App<'_, '_> {
.arg(fee_payer_arg())
.arg(memo_arg())
)
.subcommand(
SubCommand::with_name("redelegate-stake")
.about("Redelegate active stake to another vote account")
.arg(
Arg::with_name("force")
.long("force")
.takes_value(false)
.hidden(true) // Don't document this argument to discourage its use
.help("Override vote account sanity checks (use carefully!)")
)
.arg(
pubkey!(Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("STAKE_ACCOUNT_ADDRESS")
.required(true),
"Existing delegated stake account that has been fully activated. \
On success this stake account will be scheduled for deactivation and the rent-exempt balance \
may be withdrawn once fully deactivated")
)
.arg(
pubkey!(Arg::with_name("vote_account_pubkey")
.index(2)
.value_name("REDELEGATED_VOTE_ACCOUNT_ADDRESS")
.required(true),
"The vote account to which the stake will be redelegated")
)
.arg(
Arg::with_name("redelegate_stake_account")
.index(3)
.value_name("REDELEGATED_STAKE_ACCOUNT")
.takes_value(true)
.required(true)
.validator(is_valid_signer)
.help("Stake account to create for the redelegation. \
On success this stake account will be created and scheduled for activation with all \
the stake in the existing stake account, exclusive of the rent-exempt balance retained \
in the existing account")
)
.arg(stake_authority_arg())
.offline_args()
.nonce_args(false)
.arg(fee_payer_arg())
.arg(memo_arg())
)

.subcommand(
SubCommand::with_name("stake-authorize")
.about("Authorize a new signing keypair for the given stake account")
Expand Down Expand Up @@ -742,6 +787,8 @@ pub fn parse_stake_delegate_stake(
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
let vote_account_pubkey =
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
let (redelegate_stake_account, redelegate_stake_account_pubkey) =
signer_of(matches, "redelegate_stake_account", wallet_manager)?;
let force = matches.is_present("force");
let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
Expand All @@ -754,7 +801,7 @@ pub fn parse_stake_delegate_stake(
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;

let mut bulk_signers = vec![stake_authority, fee_payer];
let mut bulk_signers = vec![stake_authority, fee_payer, redelegate_stake_account];
if nonce_account.is_some() {
bulk_signers.push(nonce_authority);
}
Expand All @@ -774,6 +821,7 @@ pub fn parse_stake_delegate_stake(
nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
memo,
fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
redelegate_stake_account_pubkey,
},
signers: signer_info.signers,
})
Expand Down Expand Up @@ -2393,11 +2441,21 @@ pub fn process_delegate_stake(
nonce_authority: SignerIndex,
memo: Option<&String>,
fee_payer: SignerIndex,
redelegate_stake_account_pubkey: Option<&Pubkey>,
) -> ProcessResult {
check_unique_pubkeys(
(&config.signers[0].pubkey(), "cli keypair".to_string()),
(stake_account_pubkey, "stake_account_pubkey".to_string()),
)?;
if let Some(redelegate_stake_account_pubkey) = &redelegate_stake_account_pubkey {
check_unique_pubkeys(
(stake_account_pubkey, "stake_account_pubkey".to_string()),
(
redelegate_stake_account_pubkey,
"redelegate_stake_account".to_string(),
),
)?;
}
let stake_authority = config.signers[stake_authority];

if !sign_only {
Expand Down Expand Up @@ -2450,12 +2508,22 @@ pub fn process_delegate_stake(

let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;

let ixs = vec![stake_instruction::delegate_stake(
stake_account_pubkey,
&stake_authority.pubkey(),
vote_account_pubkey,
)]
let ixs = if let Some(redelegate_stake_account_pubkey) = &redelegate_stake_account_pubkey {
stake_instruction::redelegate(
stake_account_pubkey,
&stake_authority.pubkey(),
vote_account_pubkey,
redelegate_stake_account_pubkey,
)
} else {
vec![stake_instruction::delegate_stake(
stake_account_pubkey,
&stake_authority.pubkey(),
vote_account_pubkey,
)]
}
.with_memo(memo);

let nonce_authority = config.signers[nonce_authority];
let fee_payer = config.signers[fee_payer];

Expand Down Expand Up @@ -3824,6 +3892,7 @@ mod tests {
nonce_authority: 0,
memo: None,
fee_payer: 0,
redelegate_stake_account_pubkey: None,
},
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
}
Expand Down Expand Up @@ -3855,6 +3924,7 @@ mod tests {
nonce_authority: 0,
memo: None,
fee_payer: 0,
redelegate_stake_account_pubkey: None,
},
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Expand Down Expand Up @@ -3888,6 +3958,7 @@ mod tests {
nonce_authority: 0,
memo: None,
fee_payer: 0,
redelegate_stake_account_pubkey: None,
},
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
}
Expand Down Expand Up @@ -3922,6 +3993,7 @@ mod tests {
nonce_authority: 0,
memo: None,
fee_payer: 0,
redelegate_stake_account_pubkey: None,
},
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
}
Expand Down Expand Up @@ -3951,6 +4023,7 @@ mod tests {
nonce_authority: 0,
memo: None,
fee_payer: 0,
redelegate_stake_account_pubkey: None,
},
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
}
Expand Down Expand Up @@ -3990,6 +4063,7 @@ mod tests {
nonce_authority: 0,
memo: None,
fee_payer: 1,
redelegate_stake_account_pubkey: None,
},
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Expand Down Expand Up @@ -4038,6 +4112,7 @@ mod tests {
nonce_authority: 2,
memo: None,
fee_payer: 1,
redelegate_stake_account_pubkey: None,
},
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Expand Down Expand Up @@ -4074,6 +4149,7 @@ mod tests {
nonce_authority: 0,
memo: None,
fee_payer: 1,
redelegate_stake_account_pubkey: None,
},
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Expand All @@ -4082,6 +4158,51 @@ mod tests {
}
);

// Test RedelegateStake Subcommand (minimal test due to the significant implementation
// overlap with DelegateStake)
let (redelegate_stake_account_keypair_file, mut redelegate_stake_account_tmp_file) =
make_tmp_file();
let redelegate_stake_account_keypair = Keypair::new();
write_keypair(
&redelegate_stake_account_keypair,
redelegate_stake_account_tmp_file.as_file_mut(),
)
.unwrap();
let redelegate_stake_account_pubkey = redelegate_stake_account_keypair.pubkey();

let test_redelegate_stake = test_commands.clone().get_matches_from(vec![
"test",
"redelegate-stake",
&stake_account_string,
&vote_account_string,
&redelegate_stake_account_keypair_file,
]);
assert_eq!(
parse_command(&test_redelegate_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::DelegateStake {
stake_account_pubkey,
vote_account_pubkey,
stake_authority: 0,
force: false,
sign_only: false,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::default(),
nonce_account: None,
nonce_authority: 0,
memo: None,
fee_payer: 0,
redelegate_stake_account_pubkey: Some(redelegate_stake_account_pubkey),
},
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
read_keypair_file(&redelegate_stake_account_keypair_file)
.unwrap()
.into()
],
}
);

// Test WithdrawStake Subcommand
let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
"test",
Expand Down
18 changes: 17 additions & 1 deletion cli/src/test_utils.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use {
solana_client::rpc_client::RpcClient,
solana_sdk::{clock::DEFAULT_MS_PER_SLOT, commitment_config::CommitmentConfig},
solana_sdk::{
clock::{Epoch, DEFAULT_MS_PER_SLOT},
commitment_config::CommitmentConfig,
},
std::{thread::sleep, time::Duration},
};

Expand Down Expand Up @@ -35,3 +38,16 @@ pub fn check_ready(rpc_client: &RpcClient) {
sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT));
}
}

pub fn wait_for_next_epoch(rpc_client: &RpcClient) -> Epoch {
let current_epoch = rpc_client.get_epoch_info().unwrap().epoch;
println!("waiting for epoch {}", current_epoch + 1);
loop {
sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT));

let next_epoch = rpc_client.get_epoch_info().unwrap().epoch;
if next_epoch > current_epoch {
return next_epoch;
}
}
}
Loading

0 comments on commit 3dfe888

Please sign in to comment.