1596 lines
57 KiB
Rust
1596 lines
57 KiB
Rust
use crate::{
|
|
cli::{
|
|
build_balance_message, check_account_for_fee, check_unique_pubkeys,
|
|
get_blockhash_fee_calculator, log_instruction_custom_error, replace_signatures,
|
|
required_lamports_from, return_signers, CliCommand, CliCommandInfo, CliConfig, CliError,
|
|
ProcessResult,
|
|
},
|
|
nonce::{check_nonce_account, NONCE_ARG, NONCE_AUTHORITY_ARG},
|
|
};
|
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
|
use console::style;
|
|
use solana_clap_utils::{input_parsers::*, input_validators::*, ArgConstant};
|
|
use solana_client::rpc_client::RpcClient;
|
|
use solana_sdk::signature::{Keypair, Signature};
|
|
use solana_sdk::{
|
|
account_utils::State,
|
|
hash::Hash,
|
|
pubkey::Pubkey,
|
|
signature::KeypairUtil,
|
|
system_instruction::SystemError,
|
|
sysvar::{
|
|
stake_history::{self, StakeHistory},
|
|
Sysvar,
|
|
},
|
|
transaction::Transaction,
|
|
};
|
|
use solana_stake_program::stake_state::Meta;
|
|
use solana_stake_program::{
|
|
stake_instruction::{self, StakeError},
|
|
stake_state::{Authorized, Lockup, StakeAuthorize, StakeState},
|
|
};
|
|
use solana_vote_program::vote_state::VoteState;
|
|
use std::ops::Deref;
|
|
|
|
pub const STAKE_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant {
|
|
name: "stake_authority",
|
|
long: "stake-authority",
|
|
help: "Public key of authorized staker (defaults to cli config pubkey)",
|
|
};
|
|
|
|
pub const WITHDRAW_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant {
|
|
name: "withdraw_authority",
|
|
long: "withdraw-authority",
|
|
help: "Public key of authorized withdrawer (defaults to cli config pubkey)",
|
|
};
|
|
|
|
fn stake_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
|
|
Arg::with_name(STAKE_AUTHORITY_ARG.name)
|
|
.long(STAKE_AUTHORITY_ARG.long)
|
|
.takes_value(true)
|
|
.value_name("KEYPAIR")
|
|
.validator(is_keypair_or_ask_keyword)
|
|
.help(STAKE_AUTHORITY_ARG.help)
|
|
}
|
|
|
|
fn withdraw_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
|
|
Arg::with_name(WITHDRAW_AUTHORITY_ARG.name)
|
|
.long(WITHDRAW_AUTHORITY_ARG.long)
|
|
.takes_value(true)
|
|
.value_name("KEYPAIR")
|
|
.validator(is_keypair_or_ask_keyword)
|
|
.help(WITHDRAW_AUTHORITY_ARG.help)
|
|
}
|
|
|
|
pub trait StakeSubCommands {
|
|
fn stake_subcommands(self) -> Self;
|
|
}
|
|
|
|
impl StakeSubCommands for App<'_, '_> {
|
|
fn stake_subcommands(self) -> Self {
|
|
self.subcommand(
|
|
SubCommand::with_name("create-stake-account")
|
|
.about("Create a stake account")
|
|
.arg(
|
|
Arg::with_name("stake_account")
|
|
.index(1)
|
|
.value_name("STAKE ACCOUNT")
|
|
.takes_value(true)
|
|
.required(true)
|
|
.validator(is_keypair_or_ask_keyword)
|
|
.help("Keypair of the stake account to fund")
|
|
)
|
|
.arg(
|
|
Arg::with_name("amount")
|
|
.index(2)
|
|
.value_name("AMOUNT")
|
|
.takes_value(true)
|
|
.validator(is_amount)
|
|
.required(true)
|
|
.help("The amount of send to the vote account (default unit SOL)")
|
|
)
|
|
.arg(
|
|
Arg::with_name("unit")
|
|
.index(3)
|
|
.value_name("UNIT")
|
|
.takes_value(true)
|
|
.possible_values(&["SOL", "lamports"])
|
|
.help("Specify unit to use for request")
|
|
)
|
|
.arg(
|
|
Arg::with_name("custodian")
|
|
.long("custodian")
|
|
.value_name("PUBKEY")
|
|
.takes_value(true)
|
|
.validator(is_pubkey_or_keypair)
|
|
.help("Identity of the custodian (can withdraw before lockup expires)")
|
|
)
|
|
.arg(
|
|
Arg::with_name("lockup_epoch")
|
|
.long("lockup-epoch")
|
|
.value_name("EPOCH")
|
|
.takes_value(true)
|
|
.help("The epoch height at which this account will be available for withdrawal")
|
|
)
|
|
.arg(
|
|
Arg::with_name("lockup_date")
|
|
.long("lockup-date")
|
|
.value_name("RFC3339 DATE TIME")
|
|
.validator(is_rfc3339_datetime)
|
|
.takes_value(true)
|
|
.help("The date and time at which this account will be available for withdrawal")
|
|
)
|
|
.arg(
|
|
Arg::with_name(STAKE_AUTHORITY_ARG.name)
|
|
.long(STAKE_AUTHORITY_ARG.long)
|
|
.value_name("PUBKEY")
|
|
.takes_value(true)
|
|
.validator(is_pubkey_or_keypair)
|
|
.help(STAKE_AUTHORITY_ARG.help)
|
|
)
|
|
.arg(
|
|
Arg::with_name(WITHDRAW_AUTHORITY_ARG.name)
|
|
.long(WITHDRAW_AUTHORITY_ARG.long)
|
|
.value_name("PUBKEY")
|
|
.takes_value(true)
|
|
.validator(is_pubkey_or_keypair)
|
|
.help(WITHDRAW_AUTHORITY_ARG.help)
|
|
)
|
|
)
|
|
.subcommand(
|
|
SubCommand::with_name("delegate-stake")
|
|
.about("Delegate stake to a 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(
|
|
Arg::with_name("stake_account_pubkey")
|
|
.index(1)
|
|
.value_name("STAKE ACCOUNT")
|
|
.takes_value(true)
|
|
.required(true)
|
|
.validator(is_pubkey_or_keypair)
|
|
.help("Stake account to delegate")
|
|
)
|
|
.arg(
|
|
Arg::with_name("vote_account_pubkey")
|
|
.index(2)
|
|
.value_name("VOTE ACCOUNT")
|
|
.takes_value(true)
|
|
.required(true)
|
|
.validator(is_pubkey_or_keypair)
|
|
.help("The vote account to which the stake will be delegated")
|
|
)
|
|
.arg(stake_authority_arg())
|
|
.arg(
|
|
Arg::with_name("sign_only")
|
|
.long("sign-only")
|
|
.takes_value(false)
|
|
.help("Sign the transaction offline"),
|
|
)
|
|
.arg(
|
|
Arg::with_name("signer")
|
|
.long("signer")
|
|
.value_name("PUBKEY=BASE58_SIG")
|
|
.takes_value(true)
|
|
.validator(is_pubkey_sig)
|
|
.multiple(true)
|
|
.help("Provide a public-key/signature pair for the transaction"),
|
|
)
|
|
.arg(
|
|
Arg::with_name("blockhash")
|
|
.long("blockhash")
|
|
.value_name("BLOCKHASH")
|
|
.takes_value(true)
|
|
.validator(is_hash)
|
|
.help("Use the supplied blockhash"),
|
|
)
|
|
.arg(
|
|
Arg::with_name(NONCE_ARG.name)
|
|
.long(NONCE_ARG.long)
|
|
.takes_value(true)
|
|
.value_name("PUBKEY")
|
|
.requires("blockhash")
|
|
.validator(is_pubkey)
|
|
.help(NONCE_ARG.help)
|
|
)
|
|
.arg(
|
|
Arg::with_name(NONCE_AUTHORITY_ARG.name)
|
|
.long(NONCE_AUTHORITY_ARG.long)
|
|
.takes_value(true)
|
|
.requires(NONCE_ARG.name)
|
|
.validator(is_keypair_or_ask_keyword)
|
|
.help(NONCE_AUTHORITY_ARG.help)
|
|
),
|
|
)
|
|
.subcommand(
|
|
SubCommand::with_name("stake-authorize-staker")
|
|
.about("Authorize a new stake signing keypair for the given stake account")
|
|
.arg(
|
|
Arg::with_name("stake_account_pubkey")
|
|
.index(1)
|
|
.value_name("STAKE ACCOUNT")
|
|
.takes_value(true)
|
|
.required(true)
|
|
.validator(is_pubkey_or_keypair)
|
|
.help("Stake account in which to set the authorized staker")
|
|
)
|
|
.arg(
|
|
Arg::with_name("authorized_pubkey")
|
|
.index(2)
|
|
.value_name("AUTHORIZE PUBKEY")
|
|
.takes_value(true)
|
|
.required(true)
|
|
.validator(is_pubkey_or_keypair)
|
|
.help("New authorized staker")
|
|
)
|
|
.arg(stake_authority_arg())
|
|
)
|
|
.subcommand(
|
|
SubCommand::with_name("stake-authorize-withdrawer")
|
|
.about("Authorize a new withdraw signing keypair for the given stake account")
|
|
.arg(
|
|
Arg::with_name("stake_account_pubkey")
|
|
.index(1)
|
|
.value_name("STAKE ACCOUNT")
|
|
.takes_value(true)
|
|
.required(true)
|
|
.validator(is_pubkey_or_keypair)
|
|
.help("Stake account in which to set the authorized withdrawer")
|
|
)
|
|
.arg(
|
|
Arg::with_name("authorized_pubkey")
|
|
.index(2)
|
|
.value_name("AUTHORIZE PUBKEY")
|
|
.takes_value(true)
|
|
.required(true)
|
|
.validator(is_pubkey_or_keypair)
|
|
.help("New authorized withdrawer")
|
|
)
|
|
.arg(withdraw_authority_arg())
|
|
)
|
|
.subcommand(
|
|
SubCommand::with_name("deactivate-stake")
|
|
.about("Deactivate the delegated stake from the 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 deactivated.")
|
|
)
|
|
.arg(stake_authority_arg())
|
|
.arg(
|
|
Arg::with_name("sign_only")
|
|
.long("sign-only")
|
|
.takes_value(false)
|
|
.help("Sign the transaction offline"),
|
|
)
|
|
.arg(
|
|
Arg::with_name("signer")
|
|
.long("signer")
|
|
.value_name("PUBKEY=BASE58_SIG")
|
|
.takes_value(true)
|
|
.validator(is_pubkey_sig)
|
|
.multiple(true)
|
|
.help("Provide a public-key/signature pair for the transaction"),
|
|
)
|
|
.arg(
|
|
Arg::with_name("blockhash")
|
|
.long("blockhash")
|
|
.value_name("BLOCKHASH")
|
|
.takes_value(true)
|
|
.validator(is_hash)
|
|
.help("Use the supplied blockhash"),
|
|
)
|
|
.arg(
|
|
Arg::with_name(NONCE_ARG.name)
|
|
.long(NONCE_ARG.long)
|
|
.takes_value(true)
|
|
.value_name("PUBKEY")
|
|
.requires("blockhash")
|
|
.validator(is_pubkey)
|
|
.help(NONCE_ARG.help)
|
|
)
|
|
.arg(
|
|
Arg::with_name(NONCE_AUTHORITY_ARG.name)
|
|
.long(NONCE_AUTHORITY_ARG.long)
|
|
.takes_value(true)
|
|
.requires(NONCE_ARG.name)
|
|
.validator(is_keypair_or_ask_keyword)
|
|
.help(NONCE_AUTHORITY_ARG.help)
|
|
),
|
|
)
|
|
.subcommand(
|
|
SubCommand::with_name("withdraw-stake")
|
|
.about("Withdraw the unstaked lamports from the stake account")
|
|
.arg(
|
|
Arg::with_name("stake_account_pubkey")
|
|
.index(1)
|
|
.value_name("STAKE ACCOUNT")
|
|
.takes_value(true)
|
|
.required(true)
|
|
.validator(is_pubkey_or_keypair)
|
|
.help("Stake account from which to withdraw")
|
|
)
|
|
.arg(
|
|
Arg::with_name("destination_account_pubkey")
|
|
.index(2)
|
|
.value_name("DESTINATION ACCOUNT")
|
|
.takes_value(true)
|
|
.required(true)
|
|
.validator(is_pubkey_or_keypair)
|
|
.help("The account to which the lamports should be transferred")
|
|
)
|
|
.arg(
|
|
Arg::with_name("amount")
|
|
.index(3)
|
|
.value_name("AMOUNT")
|
|
.takes_value(true)
|
|
.validator(is_amount)
|
|
.required(true)
|
|
.help("The amount to withdraw from the 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(withdraw_authority_arg())
|
|
)
|
|
.subcommand(
|
|
SubCommand::with_name("redeem-vote-credits")
|
|
.about("Redeem credits in the stake account")
|
|
.arg(
|
|
Arg::with_name("stake_account_pubkey")
|
|
.index(1)
|
|
.value_name("STAKE ACCOUNT")
|
|
.takes_value(true)
|
|
.required(true)
|
|
.validator(is_pubkey_or_keypair)
|
|
.help("Address of the stake account in which to redeem credits")
|
|
)
|
|
.arg(
|
|
Arg::with_name("vote_account_pubkey")
|
|
.index(2)
|
|
.value_name("VOTE ACCOUNT")
|
|
.takes_value(true)
|
|
.required(true)
|
|
.validator(is_pubkey_or_keypair)
|
|
.help("The vote account to which the stake is currently delegated.")
|
|
)
|
|
)
|
|
.subcommand(
|
|
SubCommand::with_name("show-stake-account")
|
|
.about("Show the contents of a stake account")
|
|
.arg(
|
|
Arg::with_name("stake_account_pubkey")
|
|
.index(1)
|
|
.value_name("STAKE ACCOUNT")
|
|
.takes_value(true)
|
|
.required(true)
|
|
.validator(is_pubkey_or_keypair)
|
|
.help("Address of the stake account to display")
|
|
)
|
|
.arg(
|
|
Arg::with_name("lamports")
|
|
.long("lamports")
|
|
.takes_value(false)
|
|
.help("Display balance in lamports instead of SOL")
|
|
)
|
|
)
|
|
.subcommand(
|
|
SubCommand::with_name("show-stake-history")
|
|
.about("Show the stake history")
|
|
.arg(
|
|
Arg::with_name("lamports")
|
|
.long("lamports")
|
|
.takes_value(false)
|
|
.help("Display balance in lamports instead of SOL")
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
pub fn parse_stake_create_account(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
|
let stake_account = keypair_of(matches, "stake_account").unwrap();
|
|
let epoch = value_of(&matches, "lockup_epoch").unwrap_or(0);
|
|
let unix_timestamp = unix_timestamp_from_rfc3339_datetime(&matches, "lockup_date").unwrap_or(0);
|
|
let custodian = pubkey_of(matches, "custodian").unwrap_or_default();
|
|
let staker = pubkey_of(matches, STAKE_AUTHORITY_ARG.name);
|
|
let withdrawer = pubkey_of(matches, WITHDRAW_AUTHORITY_ARG.name);
|
|
let lamports = required_lamports_from(matches, "amount", "unit")?;
|
|
|
|
Ok(CliCommandInfo {
|
|
command: CliCommand::CreateStakeAccount {
|
|
stake_account: stake_account.into(),
|
|
staker,
|
|
withdrawer,
|
|
lockup: Lockup {
|
|
custodian,
|
|
epoch,
|
|
unix_timestamp,
|
|
},
|
|
lamports,
|
|
},
|
|
require_keypair: true,
|
|
})
|
|
}
|
|
|
|
pub fn parse_stake_delegate_stake(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
|
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
|
|
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
|
|
let stake_authority = if matches.is_present(STAKE_AUTHORITY_ARG.name) {
|
|
let authority = keypair_of(&matches, STAKE_AUTHORITY_ARG.name)
|
|
.ok_or_else(|| CliError::BadParameter("Invalid keypair for stake-authority".into()))?;
|
|
Some(authority.into())
|
|
} else {
|
|
None
|
|
};
|
|
let force = matches.is_present("force");
|
|
let sign_only = matches.is_present("sign_only");
|
|
let signers = pubkeys_sigs_of(&matches, "signer");
|
|
let blockhash = value_of(matches, "blockhash");
|
|
let require_keypair = signers.is_none();
|
|
let nonce_account = pubkey_of(&matches, NONCE_ARG.name);
|
|
let nonce_authority = if matches.is_present(NONCE_AUTHORITY_ARG.name) {
|
|
let authority = keypair_of(&matches, NONCE_AUTHORITY_ARG.name)
|
|
.ok_or_else(|| CliError::BadParameter("Invalid keypair for nonce-authority".into()))?;
|
|
Some(authority.into())
|
|
} else {
|
|
None
|
|
};
|
|
|
|
Ok(CliCommandInfo {
|
|
command: CliCommand::DelegateStake {
|
|
stake_account_pubkey,
|
|
vote_account_pubkey,
|
|
stake_authority,
|
|
force,
|
|
sign_only,
|
|
signers,
|
|
blockhash,
|
|
nonce_account,
|
|
nonce_authority,
|
|
},
|
|
require_keypair,
|
|
})
|
|
}
|
|
|
|
pub fn parse_stake_authorize(
|
|
matches: &ArgMatches<'_>,
|
|
stake_authorize: StakeAuthorize,
|
|
) -> Result<CliCommandInfo, CliError> {
|
|
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
|
|
let new_authorized_pubkey = pubkey_of(matches, "authorized_pubkey").unwrap();
|
|
let authority_flag = match stake_authorize {
|
|
StakeAuthorize::Staker => STAKE_AUTHORITY_ARG.name,
|
|
StakeAuthorize::Withdrawer => WITHDRAW_AUTHORITY_ARG.name,
|
|
};
|
|
let authority = if matches.is_present(authority_flag) {
|
|
let authority = keypair_of(&matches, authority_flag).ok_or_else(|| {
|
|
CliError::BadParameter(format!("Invalid keypair for {}", authority_flag))
|
|
})?;
|
|
Some(authority.into())
|
|
} else {
|
|
None
|
|
};
|
|
|
|
Ok(CliCommandInfo {
|
|
command: CliCommand::StakeAuthorize {
|
|
stake_account_pubkey,
|
|
new_authorized_pubkey,
|
|
stake_authorize,
|
|
authority,
|
|
},
|
|
require_keypair: true,
|
|
})
|
|
}
|
|
|
|
pub fn parse_redeem_vote_credits(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
|
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
|
|
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
|
|
|
|
Ok(CliCommandInfo {
|
|
command: CliCommand::RedeemVoteCredits(stake_account_pubkey, vote_account_pubkey),
|
|
require_keypair: true,
|
|
})
|
|
}
|
|
|
|
pub fn parse_stake_deactivate_stake(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
|
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
|
|
let stake_authority = if matches.is_present(STAKE_AUTHORITY_ARG.name) {
|
|
let authority = keypair_of(&matches, STAKE_AUTHORITY_ARG.name)
|
|
.ok_or_else(|| CliError::BadParameter("Invalid keypair for stake-authority".into()))?;
|
|
Some(authority.into())
|
|
} else {
|
|
None
|
|
};
|
|
let sign_only = matches.is_present("sign_only");
|
|
let signers = pubkeys_sigs_of(&matches, "signer");
|
|
let blockhash = value_of(matches, "blockhash");
|
|
let require_keypair = signers.is_none();
|
|
let nonce_account = pubkey_of(&matches, NONCE_ARG.name);
|
|
let nonce_authority = if matches.is_present(NONCE_AUTHORITY_ARG.name) {
|
|
let authority = keypair_of(&matches, NONCE_AUTHORITY_ARG.name)
|
|
.ok_or_else(|| CliError::BadParameter("Invalid keypair for nonce-authority".into()))?;
|
|
Some(authority.into())
|
|
} else {
|
|
None
|
|
};
|
|
|
|
Ok(CliCommandInfo {
|
|
command: CliCommand::DeactivateStake {
|
|
stake_account_pubkey,
|
|
stake_authority,
|
|
sign_only,
|
|
signers,
|
|
blockhash,
|
|
nonce_account,
|
|
nonce_authority,
|
|
},
|
|
require_keypair,
|
|
})
|
|
}
|
|
|
|
pub fn parse_stake_withdraw_stake(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
|
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
|
|
let destination_account_pubkey = pubkey_of(matches, "destination_account_pubkey").unwrap();
|
|
let lamports = required_lamports_from(matches, "amount", "unit")?;
|
|
let withdraw_authority = if matches.is_present(WITHDRAW_AUTHORITY_ARG.name) {
|
|
let authority = keypair_of(&matches, WITHDRAW_AUTHORITY_ARG.name).ok_or_else(|| {
|
|
CliError::BadParameter("Invalid keypair for withdraw-authority".into())
|
|
})?;
|
|
Some(authority.into())
|
|
} else {
|
|
None
|
|
};
|
|
|
|
Ok(CliCommandInfo {
|
|
command: CliCommand::WithdrawStake {
|
|
stake_account_pubkey,
|
|
destination_account_pubkey,
|
|
lamports,
|
|
withdraw_authority,
|
|
},
|
|
require_keypair: true,
|
|
})
|
|
}
|
|
|
|
pub fn parse_show_stake_account(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
|
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
|
|
let use_lamports_unit = matches.is_present("lamports");
|
|
Ok(CliCommandInfo {
|
|
command: CliCommand::ShowStakeAccount {
|
|
pubkey: stake_account_pubkey,
|
|
use_lamports_unit,
|
|
},
|
|
require_keypair: false,
|
|
})
|
|
}
|
|
|
|
pub fn parse_show_stake_history(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
|
let use_lamports_unit = matches.is_present("lamports");
|
|
Ok(CliCommandInfo {
|
|
command: CliCommand::ShowStakeHistory { use_lamports_unit },
|
|
require_keypair: false,
|
|
})
|
|
}
|
|
|
|
pub fn process_create_stake_account(
|
|
rpc_client: &RpcClient,
|
|
config: &CliConfig,
|
|
stake_account: &Keypair,
|
|
staker: &Option<Pubkey>,
|
|
withdrawer: &Option<Pubkey>,
|
|
lockup: &Lockup,
|
|
lamports: u64,
|
|
) -> ProcessResult {
|
|
let stake_account_pubkey = stake_account.pubkey();
|
|
check_unique_pubkeys(
|
|
(&config.keypair.pubkey(), "cli keypair".to_string()),
|
|
(&stake_account_pubkey, "stake_account_pubkey".to_string()),
|
|
)?;
|
|
|
|
if let Ok(stake_account) = rpc_client.get_account(&stake_account_pubkey) {
|
|
let err_msg = if stake_account.owner == solana_stake_program::id() {
|
|
format!("Stake account {} already exists", stake_account_pubkey)
|
|
} else {
|
|
format!(
|
|
"Account {} already exists and is not a stake account",
|
|
stake_account_pubkey
|
|
)
|
|
};
|
|
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 atleast {} lamports for stake account to be rent exempt, provided lamports: {}",
|
|
minimum_balance, lamports
|
|
))
|
|
.into());
|
|
}
|
|
|
|
let authorized = Authorized {
|
|
staker: staker.unwrap_or(config.keypair.pubkey()),
|
|
withdrawer: withdrawer.unwrap_or(config.keypair.pubkey()),
|
|
};
|
|
|
|
let ixs = stake_instruction::create_account(
|
|
&config.keypair.pubkey(),
|
|
&stake_account_pubkey,
|
|
&authorized,
|
|
lockup,
|
|
lamports,
|
|
);
|
|
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
|
let mut tx = Transaction::new_signed_with_payer(
|
|
ixs,
|
|
Some(&config.keypair.pubkey()),
|
|
&[&config.keypair, stake_account],
|
|
recent_blockhash,
|
|
);
|
|
check_account_for_fee(
|
|
rpc_client,
|
|
&config.keypair.pubkey(),
|
|
&fee_calculator,
|
|
&tx.message,
|
|
)?;
|
|
let result =
|
|
rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, stake_account]);
|
|
log_instruction_custom_error::<SystemError>(result)
|
|
}
|
|
|
|
pub fn process_stake_authorize(
|
|
rpc_client: &RpcClient,
|
|
config: &CliConfig,
|
|
stake_account_pubkey: &Pubkey,
|
|
authorized_pubkey: &Pubkey,
|
|
stake_authorize: StakeAuthorize,
|
|
authority: Option<&Keypair>,
|
|
) -> ProcessResult {
|
|
check_unique_pubkeys(
|
|
(stake_account_pubkey, "stake_account_pubkey".to_string()),
|
|
(authorized_pubkey, "new_authorized_pubkey".to_string()),
|
|
)?;
|
|
let authority = authority.unwrap_or(&config.keypair);
|
|
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
|
let ixs = vec![stake_instruction::authorize(
|
|
stake_account_pubkey, // stake account to update
|
|
&authority.pubkey(), // currently authorized
|
|
authorized_pubkey, // new stake signer
|
|
stake_authorize, // stake or withdraw
|
|
)];
|
|
|
|
let mut tx = Transaction::new_signed_with_payer(
|
|
ixs,
|
|
Some(&config.keypair.pubkey()),
|
|
&[&config.keypair, authority],
|
|
recent_blockhash,
|
|
);
|
|
check_account_for_fee(
|
|
rpc_client,
|
|
&config.keypair.pubkey(),
|
|
&fee_calculator,
|
|
&tx.message,
|
|
)?;
|
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
|
log_instruction_custom_error::<StakeError>(result)
|
|
}
|
|
|
|
pub fn process_deactivate_stake_account(
|
|
rpc_client: &RpcClient,
|
|
config: &CliConfig,
|
|
stake_account_pubkey: &Pubkey,
|
|
stake_authority: Option<&Keypair>,
|
|
sign_only: bool,
|
|
signers: &Option<Vec<(Pubkey, Signature)>>,
|
|
blockhash: Option<Hash>,
|
|
nonce_account: Option<Pubkey>,
|
|
nonce_authority: Option<&Keypair>,
|
|
) -> ProcessResult {
|
|
let (recent_blockhash, fee_calculator) =
|
|
get_blockhash_fee_calculator(rpc_client, sign_only, blockhash)?;
|
|
let stake_authority = stake_authority.unwrap_or(&config.keypair);
|
|
let ixs = vec![stake_instruction::deactivate_stake(
|
|
stake_account_pubkey,
|
|
&stake_authority.pubkey(),
|
|
)];
|
|
let mut tx = if let Some(nonce_account) = &nonce_account {
|
|
let nonce_authority: &Keypair = nonce_authority.unwrap_or(&config.keypair);
|
|
Transaction::new_signed_with_nonce(
|
|
ixs,
|
|
Some(&config.keypair.pubkey()),
|
|
&[&config.keypair, nonce_authority, stake_authority],
|
|
nonce_account,
|
|
&nonce_authority.pubkey(),
|
|
recent_blockhash,
|
|
)
|
|
} else {
|
|
Transaction::new_signed_with_payer(
|
|
ixs,
|
|
Some(&config.keypair.pubkey()),
|
|
&[&config.keypair, stake_authority],
|
|
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_authority: &Keypair = nonce_authority.unwrap_or(&config.keypair);
|
|
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 process_withdraw_stake(
|
|
rpc_client: &RpcClient,
|
|
config: &CliConfig,
|
|
stake_account_pubkey: &Pubkey,
|
|
destination_account_pubkey: &Pubkey,
|
|
lamports: u64,
|
|
withdraw_authority: Option<&Keypair>,
|
|
) -> ProcessResult {
|
|
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
|
let withdraw_authority = withdraw_authority.unwrap_or(&config.keypair);
|
|
|
|
let ixs = vec![stake_instruction::withdraw(
|
|
stake_account_pubkey,
|
|
&withdraw_authority.pubkey(),
|
|
destination_account_pubkey,
|
|
lamports,
|
|
)];
|
|
|
|
let mut tx = Transaction::new_signed_with_payer(
|
|
ixs,
|
|
Some(&config.keypair.pubkey()),
|
|
&[&config.keypair, withdraw_authority],
|
|
recent_blockhash,
|
|
);
|
|
check_account_for_fee(
|
|
rpc_client,
|
|
&config.keypair.pubkey(),
|
|
&fee_calculator,
|
|
&tx.message,
|
|
)?;
|
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
|
log_instruction_custom_error::<StakeError>(result)
|
|
}
|
|
|
|
pub fn process_redeem_vote_credits(
|
|
rpc_client: &RpcClient,
|
|
config: &CliConfig,
|
|
stake_account_pubkey: &Pubkey,
|
|
vote_account_pubkey: &Pubkey,
|
|
) -> ProcessResult {
|
|
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
|
let ixs = vec![stake_instruction::redeem_vote_credits(
|
|
stake_account_pubkey,
|
|
vote_account_pubkey,
|
|
)];
|
|
let mut tx = Transaction::new_signed_with_payer(
|
|
ixs,
|
|
Some(&config.keypair.pubkey()),
|
|
&[&config.keypair],
|
|
recent_blockhash,
|
|
);
|
|
check_account_for_fee(
|
|
rpc_client,
|
|
&config.keypair.pubkey(),
|
|
&fee_calculator,
|
|
&tx.message,
|
|
)?;
|
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
|
log_instruction_custom_error::<StakeError>(result)
|
|
}
|
|
|
|
pub fn process_show_stake_account(
|
|
rpc_client: &RpcClient,
|
|
_config: &CliConfig,
|
|
stake_account_pubkey: &Pubkey,
|
|
use_lamports_unit: bool,
|
|
) -> ProcessResult {
|
|
let stake_account = rpc_client.get_account(stake_account_pubkey)?;
|
|
if stake_account.owner != solana_stake_program::id() {
|
|
return Err(CliError::RpcRequestError(format!(
|
|
"{:?} is not a stake account",
|
|
stake_account_pubkey
|
|
))
|
|
.into());
|
|
}
|
|
fn show_authorized(authorized: &Authorized) {
|
|
println!("authorized staker: {}", authorized.staker);
|
|
println!("authorized withdrawer: {}", authorized.staker);
|
|
}
|
|
fn show_lockup(lockup: &Lockup) {
|
|
println!("lockup epoch: {}", lockup.epoch);
|
|
println!("lockup custodian: {}", lockup.custodian);
|
|
}
|
|
match stake_account.state() {
|
|
Ok(StakeState::Stake(
|
|
Meta {
|
|
authorized, lockup, ..
|
|
},
|
|
stake,
|
|
)) => {
|
|
println!(
|
|
"total stake: {}",
|
|
build_balance_message(stake_account.lamports, use_lamports_unit, true)
|
|
);
|
|
println!("credits observed: {}", stake.credits_observed);
|
|
println!(
|
|
"delegated stake: {}",
|
|
build_balance_message(stake.delegation.stake, use_lamports_unit, true)
|
|
);
|
|
if stake.delegation.voter_pubkey != Pubkey::default() {
|
|
println!("delegated voter pubkey: {}", stake.delegation.voter_pubkey);
|
|
}
|
|
println!(
|
|
"stake activates starting from epoch: {}",
|
|
if stake.delegation.activation_epoch < std::u64::MAX {
|
|
stake.delegation.activation_epoch
|
|
} else {
|
|
0
|
|
}
|
|
);
|
|
if stake.delegation.deactivation_epoch < std::u64::MAX {
|
|
println!(
|
|
"stake deactivates starting from epoch: {}",
|
|
stake.delegation.deactivation_epoch
|
|
);
|
|
}
|
|
show_authorized(&authorized);
|
|
show_lockup(&lockup);
|
|
Ok("".to_string())
|
|
}
|
|
Ok(StakeState::RewardsPool) => Ok("Stake account is a rewards pool".to_string()),
|
|
Ok(StakeState::Uninitialized) => Ok("Stake account is uninitialized".to_string()),
|
|
Ok(StakeState::Initialized(Meta {
|
|
authorized, lockup, ..
|
|
})) => {
|
|
println!("Stake account is undelegated");
|
|
show_authorized(&authorized);
|
|
show_lockup(&lockup);
|
|
Ok("".to_string())
|
|
}
|
|
Err(err) => Err(CliError::RpcRequestError(format!(
|
|
"Account data could not be deserialized to stake state: {:?}",
|
|
err
|
|
))
|
|
.into()),
|
|
}
|
|
}
|
|
|
|
pub fn process_show_stake_history(
|
|
rpc_client: &RpcClient,
|
|
_config: &CliConfig,
|
|
use_lamports_unit: bool,
|
|
) -> ProcessResult {
|
|
let stake_history_account = rpc_client.get_account(&stake_history::id())?;
|
|
let stake_history = StakeHistory::from_account(&stake_history_account).ok_or_else(|| {
|
|
CliError::RpcRequestError("Failed to deserialize stake history".to_string())
|
|
})?;
|
|
|
|
println!();
|
|
println!(
|
|
"{}",
|
|
style(format!(
|
|
" {:<5} {:>20} {:>20} {:>20}",
|
|
"Epoch", "Effective Stake", "Activating Stake", "Deactivating Stake",
|
|
))
|
|
.bold()
|
|
);
|
|
|
|
for (epoch, entry) in stake_history.deref() {
|
|
println!(
|
|
" {:>5} {:>20} {:>20} {:>20} {}",
|
|
epoch,
|
|
build_balance_message(entry.effective, use_lamports_unit, false),
|
|
build_balance_message(entry.activating, use_lamports_unit, false),
|
|
build_balance_message(entry.deactivating, use_lamports_unit, false),
|
|
if use_lamports_unit { "lamports" } else { "SOL" }
|
|
);
|
|
}
|
|
Ok("".to_string())
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn process_delegate_stake(
|
|
rpc_client: &RpcClient,
|
|
config: &CliConfig,
|
|
stake_account_pubkey: &Pubkey,
|
|
vote_account_pubkey: &Pubkey,
|
|
stake_authority: Option<&Keypair>,
|
|
force: bool,
|
|
sign_only: bool,
|
|
signers: &Option<Vec<(Pubkey, Signature)>>,
|
|
blockhash: Option<Hash>,
|
|
nonce_account: Option<Pubkey>,
|
|
nonce_authority: Option<&Keypair>,
|
|
) -> ProcessResult {
|
|
check_unique_pubkeys(
|
|
(&config.keypair.pubkey(), "cli keypair".to_string()),
|
|
(stake_account_pubkey, "stake_account_pubkey".to_string()),
|
|
)?;
|
|
let stake_authority = stake_authority.unwrap_or(&config.keypair);
|
|
|
|
// Sanity check the vote account to ensure it is attached to a validator that has recently
|
|
// voted at the tip of the ledger
|
|
let vote_account_data = rpc_client
|
|
.get_account_data(vote_account_pubkey)
|
|
.map_err(|_| {
|
|
CliError::RpcRequestError(format!("Vote account not found: {}", vote_account_pubkey))
|
|
})?;
|
|
|
|
let vote_state = VoteState::deserialize(&vote_account_data).map_err(|_| {
|
|
CliError::RpcRequestError(
|
|
"Account data could not be deserialized to vote state".to_string(),
|
|
)
|
|
})?;
|
|
|
|
let sanity_check_result = match vote_state.root_slot {
|
|
None => Err(CliError::BadParameter(
|
|
"Unable to delegate. Vote account has no root slot".to_string(),
|
|
)),
|
|
Some(root_slot) => {
|
|
let slot = rpc_client.get_slot()?;
|
|
if root_slot + solana_sdk::clock::DEFAULT_SLOTS_PER_TURN < slot {
|
|
Err(CliError::BadParameter(
|
|
format!(
|
|
"Unable to delegate. Vote account root slot ({}) is too old, the current slot is {}", root_slot, slot
|
|
)
|
|
))
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
};
|
|
|
|
if sanity_check_result.is_err() {
|
|
if !force {
|
|
sanity_check_result?;
|
|
} else {
|
|
println!("--force supplied, ignoring: {:?}", sanity_check_result);
|
|
}
|
|
}
|
|
|
|
let (recent_blockhash, fee_calculator) =
|
|
get_blockhash_fee_calculator(rpc_client, sign_only, blockhash)?;
|
|
|
|
let ixs = vec![stake_instruction::delegate_stake(
|
|
stake_account_pubkey,
|
|
&stake_authority.pubkey(),
|
|
vote_account_pubkey,
|
|
)];
|
|
let mut tx = if let Some(nonce_account) = &nonce_account {
|
|
let nonce_authority: &Keypair = nonce_authority.unwrap_or(&config.keypair);
|
|
Transaction::new_signed_with_nonce(
|
|
ixs,
|
|
Some(&config.keypair.pubkey()),
|
|
&[&config.keypair, nonce_authority, stake_authority],
|
|
nonce_account,
|
|
&nonce_authority.pubkey(),
|
|
recent_blockhash,
|
|
)
|
|
} else {
|
|
Transaction::new_signed_with_payer(
|
|
ixs,
|
|
Some(&config.keypair.pubkey()),
|
|
&[&config.keypair, stake_authority],
|
|
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_authority: &Keypair = nonce_authority.unwrap_or(&config.keypair);
|
|
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)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::cli::{app, parse_command};
|
|
use solana_sdk::signature::{read_keypair_file, write_keypair};
|
|
use tempfile::NamedTempFile;
|
|
|
|
fn make_tmp_file() -> (String, NamedTempFile) {
|
|
let tmp_file = NamedTempFile::new().unwrap();
|
|
(String::from(tmp_file.path().to_str().unwrap()), tmp_file)
|
|
}
|
|
|
|
fn parse_authorize_tests(
|
|
test_commands: &App,
|
|
stake_account_pubkey: Pubkey,
|
|
authority_keypair_file: &str,
|
|
stake_authorize: StakeAuthorize,
|
|
) {
|
|
let stake_account_string = stake_account_pubkey.to_string();
|
|
|
|
let (subcommand, authority_flag) = match stake_authorize {
|
|
StakeAuthorize::Staker => ("stake-authorize-staker", "--stake-authority"),
|
|
StakeAuthorize::Withdrawer => ("stake-authorize-withdrawer", "--withdraw-authority"),
|
|
};
|
|
|
|
// Test Staker Subcommand
|
|
let test_authorize = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
&subcommand,
|
|
&stake_account_string,
|
|
&stake_account_string,
|
|
]);
|
|
assert_eq!(
|
|
parse_command(&test_authorize).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::StakeAuthorize {
|
|
stake_account_pubkey,
|
|
new_authorized_pubkey: stake_account_pubkey,
|
|
stake_authorize,
|
|
authority: None,
|
|
},
|
|
require_keypair: true
|
|
}
|
|
);
|
|
// Test Staker Subcommand w/ authority
|
|
let test_authorize = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
&subcommand,
|
|
&stake_account_string,
|
|
&stake_account_string,
|
|
&authority_flag,
|
|
&authority_keypair_file,
|
|
]);
|
|
assert_eq!(
|
|
parse_command(&test_authorize).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::StakeAuthorize {
|
|
stake_account_pubkey,
|
|
new_authorized_pubkey: stake_account_pubkey,
|
|
stake_authorize,
|
|
authority: Some(read_keypair_file(&authority_keypair_file).unwrap().into()),
|
|
},
|
|
require_keypair: true
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_command() {
|
|
let test_commands = app("test", "desc", "version");
|
|
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 stake_account_pubkey = stake_account_keypair.pubkey();
|
|
let (stake_authority_keypair_file, mut tmp_file) = make_tmp_file();
|
|
let stake_authority_keypair = Keypair::new();
|
|
write_keypair(&stake_authority_keypair, tmp_file.as_file_mut()).unwrap();
|
|
|
|
parse_authorize_tests(
|
|
&test_commands,
|
|
stake_account_pubkey,
|
|
&stake_authority_keypair_file,
|
|
StakeAuthorize::Staker,
|
|
);
|
|
parse_authorize_tests(
|
|
&test_commands,
|
|
stake_account_pubkey,
|
|
&stake_authority_keypair_file,
|
|
StakeAuthorize::Withdrawer,
|
|
);
|
|
|
|
// Test CreateStakeAccount SubCommand
|
|
let custodian = Pubkey::new_rand();
|
|
let custodian_string = format!("{}", custodian);
|
|
let authorized = Pubkey::new_rand();
|
|
let authorized_string = format!("{}", authorized);
|
|
let test_create_stake_account = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
"create-stake-account",
|
|
&keypair_file,
|
|
"50",
|
|
"--stake-authority",
|
|
&authorized_string,
|
|
"--withdraw-authority",
|
|
&authorized_string,
|
|
"--custodian",
|
|
&custodian_string,
|
|
"--lockup-epoch",
|
|
"43",
|
|
"lamports",
|
|
]);
|
|
assert_eq!(
|
|
parse_command(&test_create_stake_account).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::CreateStakeAccount {
|
|
stake_account: stake_account_keypair.into(),
|
|
staker: Some(authorized),
|
|
withdrawer: Some(authorized),
|
|
lockup: Lockup {
|
|
epoch: 43,
|
|
unix_timestamp: 0,
|
|
custodian,
|
|
},
|
|
lamports: 50
|
|
},
|
|
require_keypair: true
|
|
}
|
|
);
|
|
|
|
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 stake_account_pubkey = stake_account_keypair.pubkey();
|
|
let stake_account_string = stake_account_pubkey.to_string();
|
|
|
|
let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
"create-stake-account",
|
|
&keypair_file,
|
|
"50",
|
|
"lamports",
|
|
]);
|
|
|
|
assert_eq!(
|
|
parse_command(&test_create_stake_account2).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::CreateStakeAccount {
|
|
stake_account: stake_account_keypair.into(),
|
|
staker: None,
|
|
withdrawer: None,
|
|
lockup: Lockup::default(),
|
|
lamports: 50
|
|
},
|
|
require_keypair: true
|
|
}
|
|
);
|
|
|
|
// Test DelegateStake Subcommand
|
|
let vote_account_pubkey = Pubkey::new_rand();
|
|
let vote_account_string = vote_account_pubkey.to_string();
|
|
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
"delegate-stake",
|
|
&stake_account_string,
|
|
&vote_account_string,
|
|
]);
|
|
assert_eq!(
|
|
parse_command(&test_delegate_stake).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::DelegateStake {
|
|
stake_account_pubkey,
|
|
vote_account_pubkey,
|
|
stake_authority: None,
|
|
force: false,
|
|
sign_only: false,
|
|
signers: None,
|
|
blockhash: None,
|
|
nonce_account: None,
|
|
nonce_authority: None,
|
|
},
|
|
require_keypair: true
|
|
}
|
|
);
|
|
|
|
// Test DelegateStake Subcommand w/ authority
|
|
let vote_account_pubkey = Pubkey::new_rand();
|
|
let vote_account_string = vote_account_pubkey.to_string();
|
|
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
"delegate-stake",
|
|
&stake_account_string,
|
|
&vote_account_string,
|
|
"--stake-authority",
|
|
&stake_authority_keypair_file,
|
|
]);
|
|
assert_eq!(
|
|
parse_command(&test_delegate_stake).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::DelegateStake {
|
|
stake_account_pubkey,
|
|
vote_account_pubkey,
|
|
stake_authority: Some(
|
|
read_keypair_file(&stake_authority_keypair_file)
|
|
.unwrap()
|
|
.into()
|
|
),
|
|
force: false,
|
|
sign_only: false,
|
|
signers: None,
|
|
blockhash: None,
|
|
nonce_account: None,
|
|
nonce_authority: None,
|
|
},
|
|
require_keypair: true
|
|
}
|
|
);
|
|
|
|
// Test DelegateStake Subcommand w/ force
|
|
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
"delegate-stake",
|
|
"--force",
|
|
&stake_account_string,
|
|
&vote_account_string,
|
|
]);
|
|
assert_eq!(
|
|
parse_command(&test_delegate_stake).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::DelegateStake {
|
|
stake_account_pubkey,
|
|
vote_account_pubkey,
|
|
stake_authority: None,
|
|
force: true,
|
|
sign_only: false,
|
|
signers: None,
|
|
blockhash: None,
|
|
nonce_account: None,
|
|
nonce_authority: None,
|
|
},
|
|
require_keypair: true
|
|
}
|
|
);
|
|
|
|
// Test Delegate Subcommand w/ Blockhash
|
|
let blockhash = Hash::default();
|
|
let blockhash_string = format!("{}", blockhash);
|
|
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
"delegate-stake",
|
|
&stake_account_string,
|
|
&vote_account_string,
|
|
"--blockhash",
|
|
&blockhash_string,
|
|
]);
|
|
assert_eq!(
|
|
parse_command(&test_delegate_stake).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::DelegateStake {
|
|
stake_account_pubkey,
|
|
vote_account_pubkey,
|
|
stake_authority: None,
|
|
force: false,
|
|
sign_only: false,
|
|
signers: None,
|
|
blockhash: Some(blockhash),
|
|
nonce_account: None,
|
|
nonce_authority: None,
|
|
},
|
|
require_keypair: true
|
|
}
|
|
);
|
|
|
|
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
"delegate-stake",
|
|
&stake_account_string,
|
|
&vote_account_string,
|
|
"--sign-only",
|
|
]);
|
|
assert_eq!(
|
|
parse_command(&test_delegate_stake).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::DelegateStake {
|
|
stake_account_pubkey,
|
|
vote_account_pubkey,
|
|
stake_authority: None,
|
|
force: false,
|
|
sign_only: true,
|
|
signers: None,
|
|
blockhash: None,
|
|
nonce_account: None,
|
|
nonce_authority: None,
|
|
},
|
|
require_keypair: true
|
|
}
|
|
);
|
|
|
|
// Test Delegate Subcommand w/ signer
|
|
let key1 = Pubkey::new_rand();
|
|
let sig1 = Keypair::new().sign_message(&[0u8]);
|
|
let signer1 = format!("{}={}", key1, sig1);
|
|
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
"delegate-stake",
|
|
&stake_account_string,
|
|
&vote_account_string,
|
|
"--signer",
|
|
&signer1,
|
|
]);
|
|
assert_eq!(
|
|
parse_command(&test_delegate_stake).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::DelegateStake {
|
|
stake_account_pubkey,
|
|
vote_account_pubkey,
|
|
stake_authority: None,
|
|
force: false,
|
|
sign_only: false,
|
|
signers: Some(vec![(key1, sig1)]),
|
|
blockhash: None,
|
|
nonce_account: None,
|
|
nonce_authority: None,
|
|
},
|
|
require_keypair: false
|
|
}
|
|
);
|
|
|
|
// Test Delegate Subcommand w/ signers
|
|
let key2 = Pubkey::new_rand();
|
|
let sig2 = Keypair::new().sign_message(&[0u8]);
|
|
let signer2 = format!("{}={}", key2, sig2);
|
|
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
"delegate-stake",
|
|
&stake_account_string,
|
|
&vote_account_string,
|
|
"--signer",
|
|
&signer1,
|
|
"--signer",
|
|
&signer2,
|
|
]);
|
|
assert_eq!(
|
|
parse_command(&test_delegate_stake).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::DelegateStake {
|
|
stake_account_pubkey,
|
|
vote_account_pubkey,
|
|
stake_authority: None,
|
|
force: false,
|
|
sign_only: false,
|
|
signers: Some(vec![(key1, sig1), (key2, sig2)]),
|
|
blockhash: None,
|
|
nonce_account: None,
|
|
nonce_authority: None,
|
|
},
|
|
require_keypair: false
|
|
}
|
|
);
|
|
|
|
// Test WithdrawStake Subcommand
|
|
let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
"withdraw-stake",
|
|
&stake_account_string,
|
|
&stake_account_string,
|
|
"42",
|
|
"lamports",
|
|
]);
|
|
|
|
assert_eq!(
|
|
parse_command(&test_withdraw_stake).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::WithdrawStake {
|
|
stake_account_pubkey,
|
|
destination_account_pubkey: stake_account_pubkey,
|
|
lamports: 42,
|
|
withdraw_authority: None,
|
|
},
|
|
require_keypair: true
|
|
}
|
|
);
|
|
|
|
// Test WithdrawStake Subcommand w/ authority
|
|
let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
"withdraw-stake",
|
|
&stake_account_string,
|
|
&stake_account_string,
|
|
"42",
|
|
"lamports",
|
|
"--withdraw-authority",
|
|
&stake_authority_keypair_file,
|
|
]);
|
|
|
|
assert_eq!(
|
|
parse_command(&test_withdraw_stake).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::WithdrawStake {
|
|
stake_account_pubkey,
|
|
destination_account_pubkey: stake_account_pubkey,
|
|
lamports: 42,
|
|
withdraw_authority: Some(
|
|
read_keypair_file(&stake_authority_keypair_file)
|
|
.unwrap()
|
|
.into()
|
|
),
|
|
},
|
|
require_keypair: true
|
|
}
|
|
);
|
|
|
|
// Test DeactivateStake Subcommand
|
|
let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
"deactivate-stake",
|
|
&stake_account_string,
|
|
]);
|
|
assert_eq!(
|
|
parse_command(&test_deactivate_stake).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::DeactivateStake {
|
|
stake_account_pubkey,
|
|
stake_authority: None,
|
|
sign_only: false,
|
|
signers: None,
|
|
blockhash: None,
|
|
nonce_account: None,
|
|
nonce_authority: None,
|
|
},
|
|
require_keypair: true
|
|
}
|
|
);
|
|
|
|
// Test DeactivateStake Subcommand w/ authority
|
|
let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
"deactivate-stake",
|
|
&stake_account_string,
|
|
"--stake-authority",
|
|
&stake_authority_keypair_file,
|
|
]);
|
|
assert_eq!(
|
|
parse_command(&test_deactivate_stake).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::DeactivateStake {
|
|
stake_account_pubkey,
|
|
stake_authority: Some(
|
|
read_keypair_file(&stake_authority_keypair_file)
|
|
.unwrap()
|
|
.into()
|
|
),
|
|
sign_only: false,
|
|
signers: None,
|
|
blockhash: None,
|
|
nonce_account: None,
|
|
nonce_authority: None,
|
|
},
|
|
require_keypair: true
|
|
}
|
|
);
|
|
|
|
// Test Deactivate Subcommand w/ Blockhash
|
|
let blockhash = Hash::default();
|
|
let blockhash_string = format!("{}", blockhash);
|
|
let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
"deactivate-stake",
|
|
&stake_account_string,
|
|
"--blockhash",
|
|
&blockhash_string,
|
|
]);
|
|
assert_eq!(
|
|
parse_command(&test_deactivate_stake).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::DeactivateStake {
|
|
stake_account_pubkey,
|
|
stake_authority: None,
|
|
sign_only: false,
|
|
signers: None,
|
|
blockhash: Some(blockhash),
|
|
nonce_account: None,
|
|
nonce_authority: None,
|
|
},
|
|
require_keypair: true
|
|
}
|
|
);
|
|
|
|
let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
"deactivate-stake",
|
|
&stake_account_string,
|
|
"--sign-only",
|
|
]);
|
|
assert_eq!(
|
|
parse_command(&test_deactivate_stake).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::DeactivateStake {
|
|
stake_account_pubkey,
|
|
stake_authority: None,
|
|
sign_only: true,
|
|
signers: None,
|
|
blockhash: None,
|
|
nonce_account: None,
|
|
nonce_authority: None,
|
|
},
|
|
require_keypair: true
|
|
}
|
|
);
|
|
|
|
// Test Deactivate Subcommand w/ signers
|
|
let key1 = Pubkey::new_rand();
|
|
let sig1 = Keypair::new().sign_message(&[0u8]);
|
|
let signer1 = format!("{}={}", key1, sig1);
|
|
let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
"deactivate-stake",
|
|
&stake_account_string,
|
|
"--signer",
|
|
&signer1,
|
|
]);
|
|
assert_eq!(
|
|
parse_command(&test_deactivate_stake).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::DeactivateStake {
|
|
stake_account_pubkey,
|
|
stake_authority: None,
|
|
sign_only: false,
|
|
signers: Some(vec![(key1, sig1)]),
|
|
blockhash: None,
|
|
nonce_account: None,
|
|
nonce_authority: None,
|
|
},
|
|
require_keypair: false
|
|
}
|
|
);
|
|
|
|
// Test Deactivate Subcommand w/ signers
|
|
let key2 = Pubkey::new_rand();
|
|
let sig2 = Keypair::new().sign_message(&[0u8]);
|
|
let signer2 = format!("{}={}", key2, sig2);
|
|
let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
|
|
"test",
|
|
"deactivate-stake",
|
|
&stake_account_string,
|
|
"--signer",
|
|
&signer1,
|
|
"--signer",
|
|
&signer2,
|
|
]);
|
|
assert_eq!(
|
|
parse_command(&test_deactivate_stake).unwrap(),
|
|
CliCommandInfo {
|
|
command: CliCommand::DeactivateStake {
|
|
stake_account_pubkey,
|
|
stake_authority: None,
|
|
sign_only: false,
|
|
signers: Some(vec![(key1, sig1), (key2, sig2)]),
|
|
blockhash: None,
|
|
nonce_account: None,
|
|
nonce_authority: None,
|
|
},
|
|
require_keypair: false
|
|
}
|
|
);
|
|
}
|
|
}
|