Add split-stake command
This commit is contained in:
parent
d314e0395a
commit
4f85481a2b
|
@ -276,6 +276,18 @@ pub enum CliCommand {
|
||||||
nonce_account: Option<Pubkey>,
|
nonce_account: Option<Pubkey>,
|
||||||
nonce_authority: Option<SigningAuthority>,
|
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 {
|
ShowStakeHistory {
|
||||||
use_lamports_unit: bool,
|
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),
|
("delegate-stake", Some(matches)) => parse_stake_delegate_stake(matches),
|
||||||
("withdraw-stake", Some(matches)) => parse_stake_withdraw_stake(matches),
|
("withdraw-stake", Some(matches)) => parse_stake_withdraw_stake(matches),
|
||||||
("deactivate-stake", Some(matches)) => parse_stake_deactivate_stake(matches),
|
("deactivate-stake", Some(matches)) => parse_stake_deactivate_stake(matches),
|
||||||
|
("split-stake", Some(matches)) => parse_split_stake(matches),
|
||||||
("stake-authorize-staker", Some(matches)) => {
|
("stake-authorize-staker", Some(matches)) => {
|
||||||
parse_stake_authorize(matches, StakeAuthorize::Staker)
|
parse_stake_authorize(matches, StakeAuthorize::Staker)
|
||||||
}
|
}
|
||||||
|
@ -1382,7 +1395,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||||
lockup,
|
lockup,
|
||||||
*lamports,
|
*lamports,
|
||||||
),
|
),
|
||||||
// Deactivate stake account
|
|
||||||
CliCommand::DeactivateStake {
|
CliCommand::DeactivateStake {
|
||||||
stake_account_pubkey,
|
stake_account_pubkey,
|
||||||
ref stake_authority,
|
ref stake_authority,
|
||||||
|
@ -1425,6 +1437,31 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||||
*nonce_account,
|
*nonce_account,
|
||||||
nonce_authority.as_ref(),
|
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 {
|
CliCommand::ShowStakeAccount {
|
||||||
pubkey: stake_account_pubkey,
|
pubkey: stake_account_pubkey,
|
||||||
use_lamports_unit,
|
use_lamports_unit,
|
||||||
|
@ -2775,6 +2812,23 @@ mod tests {
|
||||||
let signature = process_command(&config);
|
let signature = process_command(&config);
|
||||||
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
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 {
|
config.command = CliCommand::GetSlot {
|
||||||
commitment_config: CommitmentConfig::default(),
|
commitment_config: CommitmentConfig::default(),
|
||||||
};
|
};
|
||||||
|
|
261
cli/src/stake.rs
261
cli/src/stake.rs
|
@ -244,6 +244,55 @@ impl StakeSubCommands for App<'_, '_> {
|
||||||
.arg(nonce_arg())
|
.arg(nonce_arg())
|
||||||
.arg(nonce_authority_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(
|
||||||
SubCommand::with_name("withdraw-stake")
|
SubCommand::with_name("withdraw-stake")
|
||||||
.about("Withdraw the unstaked lamports from the stake account")
|
.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> {
|
pub fn parse_stake_deactivate_stake(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
||||||
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
|
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
|
||||||
let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
|
let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
|
||||||
|
@ -512,7 +594,7 @@ pub fn process_create_stake_account(
|
||||||
|
|
||||||
if lamports < minimum_balance {
|
if lamports < minimum_balance {
|
||||||
return Err(CliError::BadParameter(format!(
|
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
|
minimum_balance, lamports
|
||||||
))
|
))
|
||||||
.into());
|
.into());
|
||||||
|
@ -732,6 +814,148 @@ pub fn process_withdraw_stake(
|
||||||
log_instruction_custom_error::<StakeError>(result)
|
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) {
|
pub fn print_stake_state(stake_lamports: u64, stake_state: &StakeState, use_lamports_unit: bool) {
|
||||||
fn show_authorized(authorized: &Authorized) {
|
fn show_authorized(authorized: &Authorized) {
|
||||||
println!("authorized staker: {}", authorized.staker);
|
println!("authorized staker: {}", authorized.staker);
|
||||||
|
@ -1710,5 +1934,40 @@ mod tests {
|
||||||
require_keypair: false
|
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
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue