Add split-stake command

This commit is contained in:
Michael Vines 2020-02-02 22:20:28 -07:00
parent d314e0395a
commit 4f85481a2b
2 changed files with 315 additions and 2 deletions

View File

@ -276,6 +276,18 @@ pub enum CliCommand {
nonce_account: Option<Pubkey>,
nonce_authority: Option<SigningAuthority>,
},
SplitStake {
stake_account_pubkey: Pubkey,
stake_authority: Option<SigningAuthority>,
sign_only: bool,
signers: Option<Vec<(Pubkey, Signature)>>,
blockhash_query: BlockhashQuery,
nonce_account: Option<Pubkey>,
nonce_authority: Option<SigningAuthority>,
split_stake_account: KeypairEq,
seed: Option<String>,
lamports: u64,
},
ShowStakeHistory {
use_lamports_unit: bool,
},
@ -493,6 +505,7 @@ pub fn parse_command(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, Box<dyn
("delegate-stake", Some(matches)) => parse_stake_delegate_stake(matches),
("withdraw-stake", Some(matches)) => parse_stake_withdraw_stake(matches),
("deactivate-stake", Some(matches)) => parse_stake_deactivate_stake(matches),
("split-stake", Some(matches)) => parse_split_stake(matches),
("stake-authorize-staker", Some(matches)) => {
parse_stake_authorize(matches, StakeAuthorize::Staker)
}
@ -1382,7 +1395,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
lockup,
*lamports,
),
// Deactivate stake account
CliCommand::DeactivateStake {
stake_account_pubkey,
ref stake_authority,
@ -1425,6 +1437,31 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
*nonce_account,
nonce_authority.as_ref(),
),
CliCommand::SplitStake {
stake_account_pubkey,
ref stake_authority,
sign_only,
ref signers,
blockhash_query,
nonce_account,
ref nonce_authority,
split_stake_account,
seed,
lamports,
} => process_split_stake(
&rpc_client,
config,
&stake_account_pubkey,
stake_authority.as_ref(),
*sign_only,
signers,
blockhash_query,
*nonce_account,
nonce_authority.as_ref(),
split_stake_account,
seed,
*lamports,
),
CliCommand::ShowStakeAccount {
pubkey: stake_account_pubkey,
use_lamports_unit,
@ -2775,6 +2812,23 @@ mod tests {
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
let stake_pubkey = Pubkey::new_rand();
let split_stake_account = Keypair::new();
config.command = CliCommand::SplitStake {
stake_account_pubkey: stake_pubkey,
stake_authority: None,
sign_only: false,
signers: None,
blockhash_query: BlockhashQuery::default(),
nonce_account: None,
nonce_authority: None,
split_stake_account: split_stake_account.into(),
seed: None,
lamports: 1234,
};
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
config.command = CliCommand::GetSlot {
commitment_config: CommitmentConfig::default(),
};

View File

@ -244,6 +244,55 @@ impl StakeSubCommands for App<'_, '_> {
.arg(nonce_arg())
.arg(nonce_authority_arg())
)
.subcommand(
SubCommand::with_name("split-stake")
.about("Split a stake account")
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("STAKE ACCOUNT")
.takes_value(true)
.required(true)
.help("Stake account to be split")
)
.arg(
Arg::with_name("split_stake_account")
.index(2)
.value_name("SPLIT STAKE ACCOUNT")
.takes_value(true)
.required(true)
.validator(is_keypair_or_ask_keyword)
.help("Keypair of the new stake account to split funds into")
)
.arg(
Arg::with_name("amount")
.index(3)
.value_name("AMOUNT")
.takes_value(true)
.validator(is_amount)
.required(true)
.help("The amount to move into the new stake account (default unit SOL)")
)
.arg(
Arg::with_name("unit")
.index(4)
.value_name("UNIT")
.takes_value(true)
.possible_values(&["SOL", "lamports"])
.help("Specify unit to use for request")
)
.arg(
Arg::with_name("seed")
.long("seed")
.value_name("SEED STRING")
.takes_value(true)
.help("Seed for address generation; if specified, the resulting account will be at a derived address of the SPLIT STAKE ACCOUNT pubkey")
)
.arg(stake_authority_arg())
.offline_args()
.arg(nonce_arg())
.arg(nonce_authority_arg())
)
.subcommand(
SubCommand::with_name("withdraw-stake")
.about("Withdraw the unstaked lamports from the stake account")
@ -410,6 +459,39 @@ pub fn parse_stake_authorize(
})
}
pub fn parse_split_stake(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
let split_stake_account = keypair_of(matches, "split_stake_account").unwrap();
let lamports = required_lamports_from(matches, "amount", "unit")?;
let seed = matches.value_of("seed").map(|s| s.to_string());
let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
let signers = pubkeys_sigs_of(&matches, SIGNER_ARG.name);
let blockhash_query = BlockhashQuery::new_from_matches(matches);
let require_keypair = signers.is_none();
let nonce_account = pubkey_of(&matches, NONCE_ARG.name);
let stake_authority =
SigningAuthority::new_from_matches(&matches, STAKE_AUTHORITY_ARG.name, signers.as_deref())?;
let nonce_authority =
SigningAuthority::new_from_matches(&matches, NONCE_AUTHORITY_ARG.name, signers.as_deref())?;
Ok(CliCommandInfo {
command: CliCommand::SplitStake {
stake_account_pubkey,
stake_authority,
sign_only,
signers,
blockhash_query,
nonce_account,
nonce_authority,
split_stake_account: split_stake_account.into(),
seed,
lamports,
},
require_keypair,
})
}
pub fn parse_stake_deactivate_stake(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
@ -512,7 +594,7 @@ pub fn process_create_stake_account(
if lamports < minimum_balance {
return Err(CliError::BadParameter(format!(
"need atleast {} lamports for stake account to be rent exempt, provided lamports: {}",
"need at least {} lamports for stake account to be rent exempt, provided lamports: {}",
minimum_balance, lamports
))
.into());
@ -732,6 +814,148 @@ pub fn process_withdraw_stake(
log_instruction_custom_error::<StakeError>(result)
}
#[allow(clippy::too_many_arguments)]
pub fn process_split_stake(
rpc_client: &RpcClient,
config: &CliConfig,
stake_account_pubkey: &Pubkey,
stake_authority: Option<&SigningAuthority>,
sign_only: bool,
signers: &Option<Vec<(Pubkey, Signature)>>,
blockhash_query: &BlockhashQuery,
nonce_account: Option<Pubkey>,
nonce_authority: Option<&SigningAuthority>,
split_stake_account: &Keypair,
split_stake_account_seed: &Option<String>,
lamports: u64,
) -> ProcessResult {
check_unique_pubkeys(
(&config.keypair.pubkey(), "cli keypair".to_string()),
(
&split_stake_account.pubkey(),
"split_stake_account".to_string(),
),
)?;
check_unique_pubkeys(
(&config.keypair.pubkey(), "cli keypair".to_string()),
(&stake_account_pubkey, "stake_account".to_string()),
)?;
check_unique_pubkeys(
(&stake_account_pubkey, "stake_account".to_string()),
(
&split_stake_account.pubkey(),
"split_stake_account".to_string(),
),
)?;
let stake_authority = stake_authority
.map(|a| a.keypair())
.unwrap_or(&config.keypair);
let split_stake_account_address = if let Some(seed) = split_stake_account_seed {
create_address_with_seed(
&split_stake_account.pubkey(),
&seed,
&solana_stake_program::id(),
)?
} else {
split_stake_account.pubkey()
};
if let Ok(stake_account) = rpc_client.get_account(&split_stake_account_address) {
let err_msg = if stake_account.owner == solana_stake_program::id() {
format!(
"Stake account {} already exists",
split_stake_account_address
)
} else {
format!(
"Account {} already exists and is not a stake account",
split_stake_account_address
)
};
return Err(CliError::BadParameter(err_msg).into());
}
let minimum_balance =
rpc_client.get_minimum_balance_for_rent_exemption(std::mem::size_of::<StakeState>())?;
if lamports < minimum_balance {
return Err(CliError::BadParameter(format!(
"need at least {} lamports for stake account to be rent exempt, provided lamports: {}",
minimum_balance, lamports
))
.into());
}
let (recent_blockhash, fee_calculator) =
blockhash_query.get_blockhash_fee_calculator(rpc_client)?;
let ixs = if let Some(seed) = split_stake_account_seed {
stake_instruction::split_with_seed(
&stake_account_pubkey,
&stake_authority.pubkey(),
lamports,
&split_stake_account_address,
&split_stake_account.pubkey(),
seed,
)
} else {
stake_instruction::split(
&stake_account_pubkey,
&stake_authority.pubkey(),
lamports,
&split_stake_account_address,
)
};
let (nonce_authority, nonce_authority_pubkey) = nonce_authority
.map(|a| (a.keypair(), a.pubkey()))
.unwrap_or((&config.keypair, config.keypair.pubkey()));
let mut tx = if let Some(nonce_account) = &nonce_account {
Transaction::new_signed_with_nonce(
ixs,
Some(&config.keypair.pubkey()),
&[
&config.keypair,
nonce_authority,
stake_authority,
split_stake_account,
],
nonce_account,
&nonce_authority.pubkey(),
recent_blockhash,
)
} else {
Transaction::new_signed_with_payer(
ixs,
Some(&config.keypair.pubkey()),
&[&config.keypair, stake_authority, split_stake_account],
recent_blockhash,
)
};
if let Some(signers) = signers {
replace_signatures(&mut tx, &signers)?;
}
if sign_only {
return_signers(&tx)
} else {
if let Some(nonce_account) = &nonce_account {
let nonce_account = rpc_client.get_account(nonce_account)?;
check_nonce_account(&nonce_account, &nonce_authority_pubkey, &recent_blockhash)?;
}
check_account_for_fee(
rpc_client,
&tx.message.account_keys[0],
&fee_calculator,
&tx.message,
)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
log_instruction_custom_error::<StakeError>(result)
}
}
pub fn print_stake_state(stake_lamports: u64, stake_state: &StakeState, use_lamports_unit: bool) {
fn show_authorized(authorized: &Authorized) {
println!("authorized staker: {}", authorized.staker);
@ -1710,5 +1934,40 @@ mod tests {
require_keypair: false
}
);
// Test SplitStake SubCommand
let (keypair_file, mut tmp_file) = make_tmp_file();
let stake_account_keypair = Keypair::new();
write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
let (split_stake_account_keypair_file, mut tmp_file) = make_tmp_file();
let split_stake_account_keypair = Keypair::new();
write_keypair(&split_stake_account_keypair, tmp_file.as_file_mut()).unwrap();
let test_split_stake_account = test_commands.clone().get_matches_from(vec![
"test",
"split-stake",
&keypair_file,
&split_stake_account_keypair_file,
"50",
"lamports",
]);
assert_eq!(
parse_command(&test_split_stake_account).unwrap(),
CliCommandInfo {
command: CliCommand::SplitStake {
stake_account_pubkey: stake_account_keypair.pubkey(),
stake_authority: None,
sign_only: false,
signers: None,
blockhash_query: BlockhashQuery::default(),
nonce_account: None,
nonce_authority: None,
split_stake_account: split_stake_account_keypair.into(),
seed: None,
lamports: 50
},
require_keypair: true
}
);
}
}