Add `merge-stake` subcommmand
This commit is contained in:
parent
ee007e0d31
commit
0510b6e336
|
@ -327,6 +327,16 @@ pub enum CliCommand {
|
||||||
lamports: u64,
|
lamports: u64,
|
||||||
fee_payer: SignerIndex,
|
fee_payer: SignerIndex,
|
||||||
},
|
},
|
||||||
|
MergeStake {
|
||||||
|
stake_account_pubkey: Pubkey,
|
||||||
|
source_stake_account_pubkey: Pubkey,
|
||||||
|
stake_authority: SignerIndex,
|
||||||
|
sign_only: bool,
|
||||||
|
blockhash_query: BlockhashQuery,
|
||||||
|
nonce_account: Option<Pubkey>,
|
||||||
|
nonce_authority: SignerIndex,
|
||||||
|
fee_payer: SignerIndex,
|
||||||
|
},
|
||||||
ShowStakeHistory {
|
ShowStakeHistory {
|
||||||
use_lamports_unit: bool,
|
use_lamports_unit: bool,
|
||||||
},
|
},
|
||||||
|
@ -706,6 +716,9 @@ pub fn parse_command(
|
||||||
("split-stake", Some(matches)) => {
|
("split-stake", Some(matches)) => {
|
||||||
parse_split_stake(matches, default_signer_path, wallet_manager)
|
parse_split_stake(matches, default_signer_path, wallet_manager)
|
||||||
}
|
}
|
||||||
|
("merge-stake", Some(matches)) => {
|
||||||
|
parse_merge_stake(matches, default_signer_path, wallet_manager)
|
||||||
|
}
|
||||||
("stake-authorize", Some(matches)) => {
|
("stake-authorize", Some(matches)) => {
|
||||||
parse_stake_authorize(matches, default_signer_path, wallet_manager)
|
parse_stake_authorize(matches, default_signer_path, wallet_manager)
|
||||||
}
|
}
|
||||||
|
@ -2007,6 +2020,27 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||||
*lamports,
|
*lamports,
|
||||||
*fee_payer,
|
*fee_payer,
|
||||||
),
|
),
|
||||||
|
CliCommand::MergeStake {
|
||||||
|
stake_account_pubkey,
|
||||||
|
source_stake_account_pubkey,
|
||||||
|
stake_authority,
|
||||||
|
sign_only,
|
||||||
|
blockhash_query,
|
||||||
|
nonce_account,
|
||||||
|
nonce_authority,
|
||||||
|
fee_payer,
|
||||||
|
} => process_merge_stake(
|
||||||
|
&rpc_client,
|
||||||
|
config,
|
||||||
|
&stake_account_pubkey,
|
||||||
|
&source_stake_account_pubkey,
|
||||||
|
*stake_authority,
|
||||||
|
*sign_only,
|
||||||
|
blockhash_query,
|
||||||
|
*nonce_account,
|
||||||
|
*nonce_authority,
|
||||||
|
*fee_payer,
|
||||||
|
),
|
||||||
CliCommand::ShowStakeAccount {
|
CliCommand::ShowStakeAccount {
|
||||||
pubkey: stake_account_pubkey,
|
pubkey: stake_account_pubkey,
|
||||||
use_lamports_unit,
|
use_lamports_unit,
|
||||||
|
@ -3485,10 +3519,10 @@ mod tests {
|
||||||
let result = process_command(&config);
|
let result = process_command(&config);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
let stake_pubkey = Pubkey::new_rand();
|
let stake_account_pubkey = Pubkey::new_rand();
|
||||||
let to_pubkey = Pubkey::new_rand();
|
let to_pubkey = Pubkey::new_rand();
|
||||||
config.command = CliCommand::WithdrawStake {
|
config.command = CliCommand::WithdrawStake {
|
||||||
stake_account_pubkey: stake_pubkey,
|
stake_account_pubkey,
|
||||||
destination_account_pubkey: to_pubkey,
|
destination_account_pubkey: to_pubkey,
|
||||||
lamports: 100,
|
lamports: 100,
|
||||||
withdraw_authority: 0,
|
withdraw_authority: 0,
|
||||||
|
@ -3503,9 +3537,9 @@ mod tests {
|
||||||
let result = process_command(&config);
|
let result = process_command(&config);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
let stake_pubkey = Pubkey::new_rand();
|
let stake_account_pubkey = Pubkey::new_rand();
|
||||||
config.command = CliCommand::DeactivateStake {
|
config.command = CliCommand::DeactivateStake {
|
||||||
stake_account_pubkey: stake_pubkey,
|
stake_account_pubkey,
|
||||||
stake_authority: 0,
|
stake_authority: 0,
|
||||||
sign_only: false,
|
sign_only: false,
|
||||||
blockhash_query: BlockhashQuery::default(),
|
blockhash_query: BlockhashQuery::default(),
|
||||||
|
@ -3516,10 +3550,10 @@ mod tests {
|
||||||
let result = process_command(&config);
|
let result = process_command(&config);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
let stake_pubkey = Pubkey::new_rand();
|
let stake_account_pubkey = Pubkey::new_rand();
|
||||||
let split_stake_account = Keypair::new();
|
let split_stake_account = Keypair::new();
|
||||||
config.command = CliCommand::SplitStake {
|
config.command = CliCommand::SplitStake {
|
||||||
stake_account_pubkey: stake_pubkey,
|
stake_account_pubkey,
|
||||||
stake_authority: 0,
|
stake_authority: 0,
|
||||||
sign_only: false,
|
sign_only: false,
|
||||||
blockhash_query: BlockhashQuery::default(),
|
blockhash_query: BlockhashQuery::default(),
|
||||||
|
@ -3534,6 +3568,23 @@ mod tests {
|
||||||
let result = process_command(&config);
|
let result = process_command(&config);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let stake_account_pubkey = Pubkey::new_rand();
|
||||||
|
let source_stake_account_pubkey = Pubkey::new_rand();
|
||||||
|
let merge_stake_account = Keypair::new();
|
||||||
|
config.command = CliCommand::MergeStake {
|
||||||
|
stake_account_pubkey,
|
||||||
|
source_stake_account_pubkey,
|
||||||
|
stake_authority: 1,
|
||||||
|
sign_only: false,
|
||||||
|
blockhash_query: BlockhashQuery::default(),
|
||||||
|
nonce_account: None,
|
||||||
|
nonce_authority: 0,
|
||||||
|
fee_payer: 0,
|
||||||
|
};
|
||||||
|
config.signers = vec![&keypair, &merge_stake_account];
|
||||||
|
let result = process_command(&config);
|
||||||
|
assert!(dbg!(result).is_ok());
|
||||||
|
|
||||||
config.command = CliCommand::GetSlot {
|
config.command = CliCommand::GetSlot {
|
||||||
commitment_config: CommitmentConfig::default(),
|
commitment_config: CommitmentConfig::default(),
|
||||||
};
|
};
|
||||||
|
|
186
cli/src/stake.rs
186
cli/src/stake.rs
|
@ -266,6 +266,29 @@ impl StakeSubCommands for App<'_, '_> {
|
||||||
.arg(nonce_authority_arg())
|
.arg(nonce_authority_arg())
|
||||||
.arg(fee_payer_arg())
|
.arg(fee_payer_arg())
|
||||||
)
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("merge-stake")
|
||||||
|
.about("Merges one stake account into another")
|
||||||
|
.arg(
|
||||||
|
pubkey!(Arg::with_name("stake_account_pubkey")
|
||||||
|
.index(1)
|
||||||
|
.value_name("STAKE_ACCOUNT_ADDRESS")
|
||||||
|
.required(true),
|
||||||
|
"Stake account to merge into")
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
pubkey!(Arg::with_name("source_stake_account_pubkey")
|
||||||
|
.index(2)
|
||||||
|
.value_name("SOURCE_STAKE_ACCOUNT_ADDRESS")
|
||||||
|
.required(true),
|
||||||
|
"Source stake account for the merge. If successful, this stake account will no longer exist after the merge")
|
||||||
|
)
|
||||||
|
.arg(stake_authority_arg())
|
||||||
|
.offline_args()
|
||||||
|
.arg(nonce_arg())
|
||||||
|
.arg(nonce_authority_arg())
|
||||||
|
.arg(fee_payer_arg())
|
||||||
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("withdraw-stake")
|
SubCommand::with_name("withdraw-stake")
|
||||||
.about("Withdraw the unstaked SOL from the stake account")
|
.about("Withdraw the unstaked SOL from the stake account")
|
||||||
|
@ -608,6 +631,47 @@ pub fn parse_split_stake(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_merge_stake(
|
||||||
|
matches: &ArgMatches<'_>,
|
||||||
|
default_signer_path: &str,
|
||||||
|
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||||
|
) -> Result<CliCommandInfo, CliError> {
|
||||||
|
let stake_account_pubkey =
|
||||||
|
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
|
||||||
|
|
||||||
|
let source_stake_account_pubkey = pubkey_of(matches, "source_stake_account_pubkey").unwrap();
|
||||||
|
|
||||||
|
let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
|
||||||
|
let blockhash_query = BlockhashQuery::new_from_matches(matches);
|
||||||
|
let nonce_account = pubkey_of(matches, NONCE_ARG.name);
|
||||||
|
let (stake_authority, stake_authority_pubkey) =
|
||||||
|
signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
|
||||||
|
let (nonce_authority, nonce_authority_pubkey) =
|
||||||
|
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];
|
||||||
|
if nonce_account.is_some() {
|
||||||
|
bulk_signers.push(nonce_authority);
|
||||||
|
}
|
||||||
|
let signer_info =
|
||||||
|
generate_unique_signers(bulk_signers, matches, default_signer_path, wallet_manager)?;
|
||||||
|
|
||||||
|
Ok(CliCommandInfo {
|
||||||
|
command: CliCommand::MergeStake {
|
||||||
|
stake_account_pubkey,
|
||||||
|
source_stake_account_pubkey,
|
||||||
|
stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(),
|
||||||
|
sign_only,
|
||||||
|
blockhash_query,
|
||||||
|
nonce_account,
|
||||||
|
nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
|
||||||
|
fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
|
||||||
|
},
|
||||||
|
signers: signer_info.signers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_stake_deactivate_stake(
|
pub fn parse_stake_deactivate_stake(
|
||||||
matches: &ArgMatches<'_>,
|
matches: &ArgMatches<'_>,
|
||||||
default_signer_path: &str,
|
default_signer_path: &str,
|
||||||
|
@ -1201,6 +1265,99 @@ pub fn process_split_stake(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn process_merge_stake(
|
||||||
|
rpc_client: &RpcClient,
|
||||||
|
config: &CliConfig,
|
||||||
|
stake_account_pubkey: &Pubkey,
|
||||||
|
source_stake_account_pubkey: &Pubkey,
|
||||||
|
stake_authority: SignerIndex,
|
||||||
|
sign_only: bool,
|
||||||
|
blockhash_query: &BlockhashQuery,
|
||||||
|
nonce_account: Option<Pubkey>,
|
||||||
|
nonce_authority: SignerIndex,
|
||||||
|
fee_payer: SignerIndex,
|
||||||
|
) -> ProcessResult {
|
||||||
|
let fee_payer = config.signers[fee_payer];
|
||||||
|
|
||||||
|
check_unique_pubkeys(
|
||||||
|
(&fee_payer.pubkey(), "fee-payer keypair".to_string()),
|
||||||
|
(&stake_account_pubkey, "stake_account".to_string()),
|
||||||
|
)?;
|
||||||
|
check_unique_pubkeys(
|
||||||
|
(&fee_payer.pubkey(), "fee-payer keypair".to_string()),
|
||||||
|
(
|
||||||
|
&source_stake_account_pubkey,
|
||||||
|
"source_stake_account".to_string(),
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
check_unique_pubkeys(
|
||||||
|
(&stake_account_pubkey, "stake_account".to_string()),
|
||||||
|
(
|
||||||
|
&source_stake_account_pubkey,
|
||||||
|
"source_stake_account".to_string(),
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let stake_authority = config.signers[stake_authority];
|
||||||
|
|
||||||
|
if !sign_only {
|
||||||
|
for stake_account_address in &[stake_account_pubkey, source_stake_account_pubkey] {
|
||||||
|
if let Ok(stake_account) = rpc_client.get_account(stake_account_address) {
|
||||||
|
if stake_account.owner != solana_stake_program::id() {
|
||||||
|
return Err(CliError::BadParameter(format!(
|
||||||
|
"Account {} is not a stake account",
|
||||||
|
stake_account_address
|
||||||
|
))
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (recent_blockhash, fee_calculator) =
|
||||||
|
blockhash_query.get_blockhash_and_fee_calculator(rpc_client)?;
|
||||||
|
|
||||||
|
let ixs = stake_instruction::merge(
|
||||||
|
&stake_account_pubkey,
|
||||||
|
&source_stake_account_pubkey,
|
||||||
|
&stake_authority.pubkey(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let nonce_authority = config.signers[nonce_authority];
|
||||||
|
|
||||||
|
let message = if let Some(nonce_account) = &nonce_account {
|
||||||
|
Message::new_with_nonce(
|
||||||
|
ixs,
|
||||||
|
Some(&fee_payer.pubkey()),
|
||||||
|
nonce_account,
|
||||||
|
&nonce_authority.pubkey(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Message::new_with_payer(&ixs, Some(&fee_payer.pubkey()))
|
||||||
|
};
|
||||||
|
let mut tx = Transaction::new_unsigned(message);
|
||||||
|
|
||||||
|
if sign_only {
|
||||||
|
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||||
|
return_signers(&tx, &config)
|
||||||
|
} else {
|
||||||
|
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||||
|
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_with_spinner(&tx);
|
||||||
|
log_instruction_custom_error::<StakeError>(result, &config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn process_stake_set_lockup(
|
pub fn process_stake_set_lockup(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
|
@ -2942,5 +3099,34 @@ mod tests {
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Test MergeStake 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 source_stake_account_pubkey = Pubkey::new_rand();
|
||||||
|
let test_merge_stake_account = test_commands.clone().get_matches_from(vec![
|
||||||
|
"test",
|
||||||
|
"merge-stake",
|
||||||
|
&keypair_file,
|
||||||
|
&source_stake_account_pubkey.to_string(),
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
parse_command(&test_merge_stake_account, &default_keypair_file, &mut None).unwrap(),
|
||||||
|
CliCommandInfo {
|
||||||
|
command: CliCommand::MergeStake {
|
||||||
|
stake_account_pubkey: stake_account_keypair.pubkey(),
|
||||||
|
source_stake_account_pubkey,
|
||||||
|
stake_authority: 0,
|
||||||
|
sign_only: false,
|
||||||
|
blockhash_query: BlockhashQuery::default(),
|
||||||
|
nonce_account: None,
|
||||||
|
nonce_authority: 0,
|
||||||
|
fee_payer: 0,
|
||||||
|
},
|
||||||
|
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into(),],
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue