From 12a9b5f35ea5311daa95c00814921a5bc4fca1ae Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Mon, 24 Feb 2020 17:03:30 -0700 Subject: [PATCH] CLI: collect and deduplicate signers (#8398) * Rename (keypair util is not a thing) * Add method to generate_unique_signers * Cli: refactor signer handling and remote-wallet init * Fixup unit tests * Fixup intergation tests * Update keypair path print statement * Remove &None * Use deterministic key in test * Retain storage-account as index * Make signer index-handling less brittle * Cache pubkey on RemoteKeypair::new * Make signer_of consistent + return pubkey * Remove &matches double references * Nonce authorities need special handling --- clap-utils/src/input_parsers.rs | 18 +- clap-utils/src/input_validators.rs | 4 - clap-utils/src/keypair.rs | 24 +- cli/src/cli.rs | 921 +++++++++++++++++---------- cli/src/cluster_query.rs | 84 ++- cli/src/main.rs | 73 +-- cli/src/nonce.rs | 283 +++++---- cli/src/stake.rs | 949 +++++++++++++++------------- cli/src/storage.rs | 103 ++- cli/src/validator_info.rs | 41 +- cli/src/vote.rs | 168 +++-- cli/tests/deploy.rs | 4 +- cli/tests/nonce.rs | 174 ++--- cli/tests/pay.rs | 114 ++-- cli/tests/request_airdrop.rs | 8 +- cli/tests/stake.rs | 623 +++++++++--------- cli/tests/transfer.rs | 74 +-- keygen/src/keygen.rs | 12 +- remote-wallet/src/ledger.rs | 5 +- remote-wallet/src/remote_keypair.rs | 33 +- remote-wallet/src/remote_wallet.rs | 11 + sdk/src/signature.rs | 4 +- 22 files changed, 2095 insertions(+), 1635 deletions(-) diff --git a/clap-utils/src/input_parsers.rs b/clap-utils/src/input_parsers.rs index b81767af14..ecd7739869 100644 --- a/clap-utils/src/input_parsers.rs +++ b/clap-utils/src/input_parsers.rs @@ -1,16 +1,16 @@ use crate::keypair::{ - keypair_from_seed_phrase, keypair_util_from_path, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG, + keypair_from_seed_phrase, signer_from_path, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG, }; use chrono::DateTime; use clap::ArgMatches; -use solana_remote_wallet::remote_wallet::DerivationPath; +use solana_remote_wallet::remote_wallet::{DerivationPath, RemoteWalletManager}; use solana_sdk::{ clock::UnixTimestamp, native_token::sol_to_lamports, pubkey::Pubkey, signature::{read_keypair_file, Keypair, Signature, Signer}, }; -use std::str::FromStr; +use std::{str::FromStr, sync::Arc}; // Return parsed values from matches at `name` pub fn values_of(matches: &ArgMatches<'_>, name: &str) -> Option> @@ -96,14 +96,18 @@ pub fn pubkeys_sigs_of(matches: &ArgMatches<'_>, name: &str) -> Option, -) -> Result>, Box> { + name: &str, + wallet_manager: Option<&Arc>, +) -> Result<(Option>, Option), Box> { if let Some(location) = matches.value_of(name) { - keypair_util_from_path(matches, location, name).map(Some) + let signer = signer_from_path(matches, location, name, wallet_manager)?; + let signer_pubkey = signer.pubkey(); + Ok((Some(signer), Some(signer_pubkey))) } else { - Ok(None) + Ok((None, None)) } } diff --git a/clap-utils/src/input_validators.rs b/clap-utils/src/input_validators.rs index 1b13b88da1..5864d95c68 100644 --- a/clap-utils/src/input_validators.rs +++ b/clap-utils/src/input_validators.rs @@ -1,6 +1,5 @@ use crate::keypair::{parse_keypair_path, KeypairUrl, ASK_KEYWORD}; use chrono::DateTime; -use solana_remote_wallet::remote_keypair::generate_remote_keypair; use solana_sdk::{ hash::Hash, pubkey::Pubkey, @@ -53,9 +52,6 @@ pub fn is_pubkey_or_keypair_or_ask_keyword(string: String) -> Result<(), String> pub fn is_valid_signer(string: String) -> Result<(), String> { match parse_keypair_path(&string) { - KeypairUrl::Usb(path) => generate_remote_keypair(path, None) - .map(|_| ()) - .map_err(|err| format!("{:?}", err)), KeypairUrl::Filepath(path) => is_keypair(path), _ => Ok(()), } diff --git a/clap-utils/src/keypair.rs b/clap-utils/src/keypair.rs index 180d4aaa92..24db6c29e1 100644 --- a/clap-utils/src/keypair.rs +++ b/clap-utils/src/keypair.rs @@ -6,7 +6,10 @@ use crate::{ use bip39::{Language, Mnemonic, Seed}; use clap::{values_t, ArgMatches, Error, ErrorKind}; use rpassword::prompt_password_stderr; -use solana_remote_wallet::remote_keypair::generate_remote_keypair; +use solana_remote_wallet::{ + remote_keypair::generate_remote_keypair, + remote_wallet::{RemoteWalletError, RemoteWalletManager}, +}; use solana_sdk::{ pubkey::Pubkey, signature::{ @@ -19,6 +22,7 @@ use std::{ io::{stdin, stdout, Write}, process::exit, str::FromStr, + sync::Arc, }; pub enum KeypairUrl { @@ -56,10 +60,11 @@ pub fn presigner_from_pubkey_sigs( }) } -pub fn keypair_util_from_path( +pub fn signer_from_path( matches: &ArgMatches, path: &str, keypair_name: &str, + wallet_manager: Option<&Arc>, ) -> Result, Box> { match parse_keypair_path(path) { KeypairUrl::Ask => { @@ -75,10 +80,17 @@ pub fn keypair_util_from_path( let mut stdin = std::io::stdin(); Ok(Box::new(read_keypair(&mut stdin)?)) } - KeypairUrl::Usb(path) => Ok(Box::new(generate_remote_keypair( - path, - derivation_of(matches, "derivation_path"), - )?)), + KeypairUrl::Usb(path) => { + if let Some(wallet_manager) = wallet_manager { + Ok(Box::new(generate_remote_keypair( + path, + derivation_of(matches, "derivation_path"), + wallet_manager, + )?)) + } else { + Err(RemoteWalletError::NoDeviceFound.into()) + } + } KeypairUrl::Pubkey(pubkey) => { let presigner = pubkeys_sigs_of(matches, SIGNER_ARG.name) .as_ref() diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 44d6b7167a..a36bedebb5 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -15,14 +15,15 @@ use num_traits::FromPrimitive; use serde_json::{self, json, Value}; use solana_budget_program::budget_instruction::{self, BudgetError}; use solana_clap_utils::{ - input_parsers::*, input_validators::*, offline::SIGN_ONLY_ARG, ArgConstant, + input_parsers::*, input_validators::*, keypair::signer_from_path, offline::SIGN_ONLY_ARG, + ArgConstant, }; use solana_client::{client_error::ClientError, rpc_client::RpcClient}; #[cfg(not(test))] use solana_faucet::faucet::request_airdrop_transaction; #[cfg(test)] use solana_faucet::faucet_mock::request_airdrop_transaction; -use solana_remote_wallet::remote_wallet::DerivationPath; +use solana_remote_wallet::remote_wallet::{DerivationPath, RemoteWalletManager}; use solana_sdk::{ bpf_loader, clock::{Epoch, Slot}, @@ -46,12 +47,57 @@ use std::{ fs::File, io::{Read, Write}, net::{IpAddr, SocketAddr}, - rc::Rc, + sync::Arc, thread::sleep, time::Duration, {error, fmt}, }; +pub type CliSigners = Vec>; +pub type SignerIndex = usize; +pub(crate) struct CliSignerInfo { + pub signers: CliSigners, +} + +impl CliSignerInfo { + pub(crate) fn index_of(&self, pubkey: Option) -> Option { + if let Some(pubkey) = pubkey { + self.signers + .iter() + .position(|signer| signer.pubkey() == pubkey) + } else { + Some(0) + } + } +} + +pub(crate) fn generate_unique_signers( + bulk_signers: Vec>>, + matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, +) -> Result> { + let mut unique_signers = vec![]; + + // Determine if the default signer is needed + if bulk_signers.iter().any(|signer| signer.is_none()) { + let default_signer = + signer_from_path(matches, default_signer_path, "keypair", wallet_manager)?; + unique_signers.push(default_signer); + } + + for signer in bulk_signers.into_iter() { + if let Some(signer) = signer { + if !unique_signers.iter().any(|s| s == &signer) { + unique_signers.push(signer); + } + } + } + Ok(CliSignerInfo { + signers: unique_signers, + }) +} + const DATA_CHUNK_SIZE: usize = 229; // Keep program chunks under PACKET_DATA_SIZE pub const FEE_PAYER_ARG: ArgConstant<'static> = ArgConstant { @@ -108,7 +154,7 @@ pub struct PayCommand { pub sign_only: bool, pub blockhash_query: BlockhashQuery, pub nonce_account: Option, - pub nonce_authority: Option>, + pub nonce_authority: SignerIndex, } #[derive(Debug, PartialEq)] @@ -164,11 +210,11 @@ pub enum CliCommand { // Nonce commands AuthorizeNonceAccount { nonce_account: Pubkey, - nonce_authority: Option>, + nonce_authority: SignerIndex, new_authority: Pubkey, }, CreateNonceAccount { - nonce_account: Rc>, + nonce_account: SignerIndex, seed: Option, nonce_authority: Option, lamports: u64, @@ -176,7 +222,7 @@ pub enum CliCommand { GetNonce(Pubkey), NewNonce { nonce_account: Pubkey, - nonce_authority: Option>, + nonce_authority: SignerIndex, }, ShowNonceAccount { nonce_account_pubkey: Pubkey, @@ -184,7 +230,7 @@ pub enum CliCommand { }, WithdrawFromNonceAccount { nonce_account: Pubkey, - nonce_authority: Option>, + nonce_authority: SignerIndex, destination_account_pubkey: Pubkey, lamports: u64, }, @@ -192,7 +238,7 @@ pub enum CliCommand { Deploy(String), // Stake Commands CreateStakeAccount { - stake_account: Rc>, + stake_account: SignerIndex, seed: Option, staker: Option, withdrawer: Option, @@ -201,41 +247,41 @@ pub enum CliCommand { sign_only: bool, blockhash_query: BlockhashQuery, nonce_account: Option, - nonce_authority: Option>, - fee_payer: Option>, - from: Option>, + nonce_authority: SignerIndex, + fee_payer: SignerIndex, + from: SignerIndex, }, DeactivateStake { stake_account_pubkey: Pubkey, - stake_authority: Option>, + stake_authority: SignerIndex, sign_only: bool, blockhash_query: BlockhashQuery, nonce_account: Option, - nonce_authority: Option>, - fee_payer: Option>, + nonce_authority: SignerIndex, + fee_payer: SignerIndex, }, DelegateStake { stake_account_pubkey: Pubkey, vote_account_pubkey: Pubkey, - stake_authority: Option>, + stake_authority: SignerIndex, force: bool, sign_only: bool, blockhash_query: BlockhashQuery, nonce_account: Option, - nonce_authority: Option>, - fee_payer: Option>, + nonce_authority: SignerIndex, + fee_payer: SignerIndex, }, SplitStake { stake_account_pubkey: Pubkey, - stake_authority: Option>, + stake_authority: SignerIndex, sign_only: bool, blockhash_query: BlockhashQuery, nonce_account: Option, - nonce_authority: Option>, - split_stake_account: Rc>, + nonce_authority: SignerIndex, + split_stake_account: SignerIndex, seed: Option, lamports: u64, - fee_payer: Option>, + fee_payer: SignerIndex, }, ShowStakeHistory { use_lamports_unit: bool, @@ -248,38 +294,38 @@ pub enum CliCommand { stake_account_pubkey: Pubkey, new_authorized_pubkey: Pubkey, stake_authorize: StakeAuthorize, - authority: Option>, + authority: SignerIndex, sign_only: bool, blockhash_query: BlockhashQuery, nonce_account: Option, - nonce_authority: Option>, - fee_payer: Option>, + nonce_authority: SignerIndex, + fee_payer: SignerIndex, }, StakeSetLockup { stake_account_pubkey: Pubkey, lockup: Lockup, - custodian: Option>, + custodian: SignerIndex, sign_only: bool, blockhash_query: BlockhashQuery, nonce_account: Option, - nonce_authority: Option>, - fee_payer: Option>, + nonce_authority: SignerIndex, + fee_payer: SignerIndex, }, WithdrawStake { stake_account_pubkey: Pubkey, destination_account_pubkey: Pubkey, lamports: u64, - withdraw_authority: Option>, + withdraw_authority: SignerIndex, sign_only: bool, blockhash_query: BlockhashQuery, nonce_account: Option, - nonce_authority: Option>, - fee_payer: Option>, + nonce_authority: SignerIndex, + fee_payer: SignerIndex, }, // Storage Commands CreateStorageAccount { account_owner: Pubkey, - storage_account: KeypairEq, + storage_account: SignerIndex, account_type: StorageAccountType, }, ClaimStorageReward { @@ -296,7 +342,6 @@ pub enum CliCommand { }, // Vote Commands CreateVoteAccount { - vote_account: KeypairEq, seed: Option, node_pubkey: Pubkey, authorized_voter: Option, @@ -315,7 +360,6 @@ pub enum CliCommand { VoteUpdateValidator { vote_account_pubkey: Pubkey, new_identity_pubkey: Pubkey, - authorized_voter: KeypairEq, }, // Wallet Commands Address, @@ -341,12 +385,12 @@ pub enum CliCommand { Transfer { lamports: u64, to: Pubkey, - from: Option>, + from: SignerIndex, sign_only: bool, blockhash_query: BlockhashQuery, nonce_account: Option, - nonce_authority: Option>, - fee_payer: Option>, + nonce_authority: SignerIndex, + fee_payer: SignerIndex, }, Witness(Pubkey, Pubkey), // Witness(to, process_id) } @@ -354,7 +398,7 @@ pub enum CliCommand { #[derive(Debug, PartialEq)] pub struct CliCommandInfo { pub command: CliCommand, - pub require_keypair: bool, + pub signers: CliSigners, } #[derive(Debug, Clone, PartialEq)] @@ -391,17 +435,17 @@ impl From> for CliError { } } -pub struct CliConfig { +pub struct CliConfig<'a> { pub command: CliCommand, pub json_rpc_url: String, - pub keypair: Box, - pub keypair_path: Option, + pub signers: Vec<&'a dyn Signer>, + pub keypair_path: String, pub derivation_path: Option, pub rpc_client: Option, pub verbose: bool, } -impl CliConfig { +impl CliConfig<'_> { pub fn default_keypair_path() -> String { let mut keypair_path = dirs::home_dir().expect("home directory"); keypair_path.extend(&[".config", "solana", "id.json"]); @@ -413,20 +457,26 @@ impl CliConfig { } pub(crate) fn pubkey(&self) -> Result { - self.keypair.try_pubkey() + if !self.signers.is_empty() { + self.signers[0].try_pubkey() + } else { + Err(SignerError::CustomError( + "Default keypair must be set if pubkey arg not provided".to_string(), + )) + } } } -impl Default for CliConfig { - fn default() -> CliConfig { +impl Default for CliConfig<'_> { + fn default() -> CliConfig<'static> { CliConfig { command: CliCommand::Balance { pubkey: Some(Pubkey::default()), use_lamports_unit: false, }, json_rpc_url: Self::default_json_rpc_url(), - keypair: Box::new(Keypair::new()), - keypair_path: Some(Self::default_keypair_path()), + signers: Vec::new(), + keypair_path: Self::default_keypair_path(), derivation_path: None, rpc_client: None, verbose: false, @@ -434,98 +484,150 @@ impl Default for CliConfig { } } -pub fn parse_command(matches: &ArgMatches<'_>) -> Result> { +pub fn parse_command( + matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, +) -> Result> { let response = match matches.subcommand() { // Cluster Query Commands ("catchup", Some(matches)) => parse_catchup(matches), ("cluster-version", Some(_matches)) => Ok(CliCommandInfo { command: CliCommand::ClusterVersion, - require_keypair: false, + signers: vec![], }), ("create-address-with-seed", Some(matches)) => parse_create_address_with_seed(matches), ("fees", Some(_matches)) => Ok(CliCommandInfo { command: CliCommand::Fees, - require_keypair: false, + signers: vec![], }), ("block-time", Some(matches)) => parse_get_block_time(matches), ("epoch-info", Some(matches)) => parse_get_epoch_info(matches), ("genesis-hash", Some(_matches)) => Ok(CliCommandInfo { command: CliCommand::GetGenesisHash, - require_keypair: false, + signers: vec![], }), ("slot", Some(matches)) => parse_get_slot(matches), ("transaction-count", Some(matches)) => parse_get_transaction_count(matches), ("leader-schedule", Some(_matches)) => Ok(CliCommandInfo { command: CliCommand::LeaderSchedule, - require_keypair: false, + signers: vec![], }), - ("ping", Some(matches)) => parse_cluster_ping(matches), + ("ping", Some(matches)) => parse_cluster_ping(matches, default_signer_path, wallet_manager), ("live-slots", Some(matches)) => parse_live_slots(matches), ("block-production", Some(matches)) => parse_show_block_production(matches), ("gossip", Some(_matches)) => Ok(CliCommandInfo { command: CliCommand::ShowGossip, - require_keypair: false, + signers: vec![], }), ("stakes", Some(matches)) => parse_show_stakes(matches), ("validators", Some(matches)) => parse_show_validators(matches), // Nonce Commands - ("authorize-nonce-account", Some(matches)) => parse_authorize_nonce_account(matches), - ("create-nonce-account", Some(matches)) => parse_nonce_create_account(matches), + ("authorize-nonce-account", Some(matches)) => { + parse_authorize_nonce_account(matches, default_signer_path, wallet_manager) + } + ("create-nonce-account", Some(matches)) => { + parse_nonce_create_account(matches, default_signer_path, wallet_manager) + } ("nonce", Some(matches)) => parse_get_nonce(matches), - ("new-nonce", Some(matches)) => parse_new_nonce(matches), + ("new-nonce", Some(matches)) => { + parse_new_nonce(matches, default_signer_path, wallet_manager) + } ("nonce-account", Some(matches)) => parse_show_nonce_account(matches), ("withdraw-from-nonce-account", Some(matches)) => { - parse_withdraw_from_nonce_account(matches) + parse_withdraw_from_nonce_account(matches, default_signer_path, wallet_manager) } // Program Deployment ("deploy", Some(matches)) => Ok(CliCommandInfo { command: CliCommand::Deploy(matches.value_of("program_location").unwrap().to_string()), - require_keypair: true, + signers: vec![signer_from_path( + matches, + default_signer_path, + "keypair", + wallet_manager, + )?], }), // Stake Commands - ("create-stake-account", Some(matches)) => parse_stake_create_account(matches), - ("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) + ("create-stake-account", Some(matches)) => { + parse_stake_create_account(matches, default_signer_path, wallet_manager) } - ("stake-authorize-withdrawer", Some(matches)) => { - parse_stake_authorize(matches, StakeAuthorize::Withdrawer) + ("delegate-stake", Some(matches)) => { + parse_stake_delegate_stake(matches, default_signer_path, wallet_manager) + } + ("withdraw-stake", Some(matches)) => { + parse_stake_withdraw_stake(matches, default_signer_path, wallet_manager) + } + ("deactivate-stake", Some(matches)) => { + parse_stake_deactivate_stake(matches, default_signer_path, wallet_manager) + } + ("split-stake", Some(matches)) => { + parse_split_stake(matches, default_signer_path, wallet_manager) + } + ("stake-authorize-staker", Some(matches)) => parse_stake_authorize( + matches, + default_signer_path, + wallet_manager, + StakeAuthorize::Staker, + ), + ("stake-authorize-withdrawer", Some(matches)) => parse_stake_authorize( + matches, + default_signer_path, + wallet_manager, + StakeAuthorize::Withdrawer, + ), + ("stake-set-lockup", Some(matches)) => { + parse_stake_set_lockup(matches, default_signer_path, wallet_manager) } - ("stake-set-lockup", Some(matches)) => parse_stake_set_lockup(matches), ("stake-account", Some(matches)) => parse_show_stake_account(matches), ("stake-history", Some(matches)) => parse_show_stake_history(matches), // Storage Commands ("create-archiver-storage-account", Some(matches)) => { - parse_storage_create_archiver_account(matches) + parse_storage_create_archiver_account(matches, default_signer_path, wallet_manager) } ("create-validator-storage-account", Some(matches)) => { - parse_storage_create_validator_account(matches) + parse_storage_create_validator_account(matches, default_signer_path, wallet_manager) + } + ("claim-storage-reward", Some(matches)) => { + parse_storage_claim_reward(matches, default_signer_path, wallet_manager) } - ("claim-storage-reward", Some(matches)) => parse_storage_claim_reward(matches), ("storage-account", Some(matches)) => parse_storage_get_account_command(matches), // Validator Info Commands ("validator-info", Some(matches)) => match matches.subcommand() { - ("publish", Some(matches)) => parse_validator_info_command(matches), + ("publish", Some(matches)) => { + parse_validator_info_command(matches, default_signer_path, wallet_manager) + } ("get", Some(matches)) => parse_get_validator_info_command(matches), _ => unreachable!(), }, // Vote Commands - ("create-vote-account", Some(matches)) => parse_vote_create_account(matches), - ("vote-update-validator", Some(matches)) => parse_vote_update_validator(matches), - ("vote-authorize-voter", Some(matches)) => { - parse_vote_authorize(matches, VoteAuthorize::Voter) + ("create-vote-account", Some(matches)) => { + parse_vote_create_account(matches, default_signer_path, wallet_manager) } - ("vote-authorize-withdrawer", Some(matches)) => { - parse_vote_authorize(matches, VoteAuthorize::Withdrawer) + ("vote-update-validator", Some(matches)) => { + parse_vote_update_validator(matches, default_signer_path, wallet_manager) } + ("vote-authorize-voter", Some(matches)) => parse_vote_authorize( + matches, + default_signer_path, + wallet_manager, + VoteAuthorize::Voter, + ), + ("vote-authorize-withdrawer", Some(matches)) => parse_vote_authorize( + matches, + default_signer_path, + wallet_manager, + VoteAuthorize::Withdrawer, + ), ("vote-account", Some(matches)) => parse_vote_get_account_command(matches), // Wallet Commands ("address", Some(_matches)) => Ok(CliCommandInfo { command: CliCommand::Address, - require_keypair: true, + signers: vec![signer_from_path( + matches, + default_signer_path, + "keypair", + wallet_manager, + )?], }), ("airdrop", Some(matches)) => { let faucet_port = matches @@ -549,7 +651,17 @@ pub fn parse_command(matches: &ArgMatches<'_>) -> Result) -> Result { - let pubkey = pubkey_of(&matches, "pubkey"); + let pubkey = pubkey_of(matches, "pubkey"); + let signers = if pubkey.is_some() { + vec![] + } else { + vec![signer_from_path( + matches, + default_signer_path, + "keypair", + wallet_manager, + )?] + }; Ok(CliCommandInfo { command: CliCommand::Balance { pubkey, use_lamports_unit: matches.is_present("lamports"), }, - require_keypair: pubkey.is_none(), + signers, }) } ("cancel", Some(matches)) => { let process_id = value_of(matches, "process_id").unwrap(); + let default_signer = + signer_from_path(matches, default_signer_path, "keypair", wallet_manager)?; + Ok(CliCommandInfo { command: CliCommand::Cancel(process_id), - require_keypair: true, + signers: vec![default_signer], }) } ("confirm", Some(matches)) => match matches.value_of("signature").unwrap().parse() { Ok(signature) => Ok(CliCommandInfo { command: CliCommand::Confirm(signature), - require_keypair: false, + signers: vec![], }), _ => { eprintln!("{}", matches.usage()); @@ -590,7 +715,7 @@ pub fn parse_command(matches: &ArgMatches<'_>) -> Result { let lamports = lamports_of_sol(matches, "amount").unwrap(); - let to = pubkey_of(&matches, "to").unwrap(); + let to = pubkey_of(matches, "to").unwrap(); let timestamp = if matches.is_present("timestamp") { // Parse input for serde_json let date_string = if !matches.value_of("timestamp").unwrap().contains('Z') { @@ -602,13 +727,26 @@ pub fn parse_command(matches: &ArgMatches<'_>) -> Result) -> Result { @@ -636,20 +774,24 @@ pub fn parse_command(matches: &ArgMatches<'_>) -> Result { - let to = value_of(&matches, "to").unwrap(); - let process_id = value_of(&matches, "process_id").unwrap(); + let to = value_of(matches, "to").unwrap(); + let process_id = value_of(matches, "process_id").unwrap(); + + let default_signer = + signer_from_path(matches, default_signer_path, "keypair", wallet_manager)?; + Ok(CliCommandInfo { command: CliCommand::Witness(to, process_id), - require_keypair: true, + signers: vec![default_signer], }) } ("send-timestamp", Some(matches)) => { - let to = value_of(&matches, "to").unwrap(); - let process_id = value_of(&matches, "process_id").unwrap(); + let to = value_of(matches, "to").unwrap(); + let process_id = value_of(matches, "process_id").unwrap(); let dt = if matches.is_present("datetime") { // Parse input for serde_json let date_string = if !matches.value_of("datetime").unwrap().contains('Z') { @@ -661,32 +803,50 @@ pub fn parse_command(matches: &ArgMatches<'_>) -> Result { let lamports = lamports_of_sol(matches, "amount").unwrap(); - let to = pubkey_of(&matches, "to").unwrap(); + let to = pubkey_of(matches, "to").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 nonce_authority = signer_of(NONCE_AUTHORITY_ARG.name, &matches)?; - let fee_payer = signer_of(FEE_PAYER_ARG.name, &matches)?; - let from = signer_of("from", &matches)?; + let nonce_account = pubkey_of(matches, NONCE_ARG.name); + 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 (from, from_pubkey) = signer_of(matches, "from", wallet_manager)?; + + let mut bulk_signers = vec![fee_payer, from]; + 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::Transfer { lamports, to, - from, sign_only, blockhash_query, nonce_account, - nonce_authority, - fee_payer, + nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), + fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), + from: signer_info.index_of(from_pubkey).unwrap(), }, - require_keypair: true, + signers: signer_info.signers, }) } // @@ -782,8 +942,6 @@ pub fn parse_create_address_with_seed( ) -> Result { let from_pubkey = pubkey_of(matches, "from"); - let require_keypair = from_pubkey.is_none(); - let program_id = match matches.value_of("program_id").unwrap() { "STAKE" => solana_stake_program::id(), "VOTE" => solana_vote_program::id(), @@ -805,7 +963,7 @@ pub fn parse_create_address_with_seed( seed, program_id, }, - require_keypair, + signers: vec![], }) } @@ -828,7 +986,11 @@ fn process_airdrop( pubkey: &Option, lamports: u64, ) -> ProcessResult { - let pubkey = pubkey.unwrap_or(config.pubkey()?); + let pubkey = if let Some(pubkey) = pubkey { + *pubkey + } else { + config.pubkey()? + }; println!( "Requesting airdrop of {} from {}", build_balance_message(lamports, false, true), @@ -859,7 +1021,11 @@ fn process_balance( pubkey: &Option, use_lamports_unit: bool, ) -> ProcessResult { - let pubkey = pubkey.unwrap_or(config.pubkey()?); + let pubkey = if let Some(pubkey) = pubkey { + *pubkey + } else { + config.pubkey()? + }; let balance = rpc_client.retry_get_balance(&pubkey, 5)?; match balance { Some(lamports) => Ok(build_balance_message(lamports, use_lamports_unit, true)), @@ -935,7 +1101,7 @@ fn process_deploy( let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let minimum_balance = rpc_client.get_minimum_balance_for_rent_exemption(program_data.len())?; let ix = system_instruction::create_account( - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &program_id.pubkey(), minimum_balance.max(1), program_data.len() as u64, @@ -943,9 +1109,9 @@ fn process_deploy( ); let message = Message::new(vec![ix]); let mut create_account_tx = Transaction::new_unsigned(message); - create_account_tx.try_sign(&[config.keypair.as_ref(), &program_id], blockhash)?; + create_account_tx.try_sign(&[config.signers[0], &program_id], blockhash)?; messages.push(&create_account_tx.message); - let signers = [config.keypair.as_ref(), &program_id]; + let signers = [config.signers[0], &program_id]; let mut write_transactions = vec![]; for (chunk, i) in program_data.chunks(DATA_CHUNK_SIZE).zip(0..) { let instruction = loader_instruction::write( @@ -971,7 +1137,7 @@ fn process_deploy( check_account_for_multiple_fees( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &messages, )?; @@ -1010,36 +1176,31 @@ fn process_pay( sign_only: bool, blockhash_query: &BlockhashQuery, nonce_account: Option, - nonce_authority: Option<&dyn Signer>, + nonce_authority: SignerIndex, ) -> ProcessResult { check_unique_pubkeys( - (&config.keypair.pubkey(), "cli keypair".to_string()), + (&config.signers[0].pubkey(), "cli keypair".to_string()), (to, "to".to_string()), )?; let (blockhash, fee_calculator) = blockhash_query.get_blockhash_fee_calculator(rpc_client)?; let cancelable = if cancelable { - Some(config.keypair.pubkey()) + Some(config.signers[0].pubkey()) } else { None }; if timestamp == None && *witnesses == None { - let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); - let ix = system_instruction::transfer(&config.keypair.pubkey(), to, lamports); - let mut tx = if let Some(nonce_account) = &nonce_account { - let message = - Message::new_with_nonce(vec![ix], None, nonce_account, &nonce_authority.pubkey()); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&[config.keypair.as_ref(), nonce_authority], blockhash)?; - tx + let nonce_authority = config.signers[nonce_authority]; + let ix = system_instruction::transfer(&config.signers[0].pubkey(), to, lamports); + let message = if let Some(nonce_account) = &nonce_account { + Message::new_with_nonce(vec![ix], None, nonce_account, &nonce_authority.pubkey()) } else { - let message = Message::new(vec![ix]); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&[config.keypair.as_ref()], blockhash)?; - tx + Message::new(vec![ix]) }; + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&config.signers, blockhash)?; if sign_only { return_signers(&tx) @@ -1050,26 +1211,25 @@ fn process_pay( } check_account_for_fee( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &tx.message, )?; - let result = - rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); log_instruction_custom_error::(result) } } else if *witnesses == None { let dt = timestamp.unwrap(); let dt_pubkey = match timestamp_pubkey { Some(pubkey) => pubkey, - None => config.keypair.pubkey(), + None => config.signers[0].pubkey(), }; let contract_state = Keypair::new(); // Initializing contract let ixs = budget_instruction::on_date( - &config.keypair.pubkey(), + &config.signers[0].pubkey(), to, &contract_state.pubkey(), dt, @@ -1079,18 +1239,18 @@ fn process_pay( ); let message = Message::new(ixs); let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&[config.keypair.as_ref(), &contract_state], blockhash)?; + tx.try_sign(&[config.signers[0], &contract_state], blockhash)?; if sign_only { return_signers(&tx) } else { check_account_for_fee( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &tx.message, )?; let result = rpc_client - .send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref(), &contract_state]); + .send_and_confirm_transaction(&mut tx, &[config.signers[0], &contract_state]); let signature_str = log_instruction_custom_error::(result)?; Ok(json!({ @@ -1113,7 +1273,7 @@ fn process_pay( // Initializing contract let ixs = budget_instruction::when_signed( - &config.keypair.pubkey(), + &config.signers[0].pubkey(), to, &contract_state.pubkey(), &witness, @@ -1122,15 +1282,15 @@ fn process_pay( ); let message = Message::new(ixs); let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&[config.keypair.as_ref(), &contract_state], blockhash)?; + tx.try_sign(&[config.signers[0], &contract_state], blockhash)?; if sign_only { return_signers(&tx) } else { let result = rpc_client - .send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref(), &contract_state]); + .send_and_confirm_transaction(&mut tx, &[config.signers[0], &contract_state]); check_account_for_fee( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &tx.message, )?; @@ -1150,20 +1310,20 @@ fn process_pay( fn process_cancel(rpc_client: &RpcClient, config: &CliConfig, pubkey: &Pubkey) -> ProcessResult { let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let ix = budget_instruction::apply_signature( - &config.keypair.pubkey(), + &config.signers[0].pubkey(), pubkey, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), ); let message = Message::new(vec![ix]); let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&[config.keypair.as_ref()], blockhash)?; + tx.try_sign(&config.signers, blockhash)?; check_account_for_fee( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.signers[0]]); log_instruction_custom_error::(result) } @@ -1176,17 +1336,17 @@ fn process_time_elapsed( ) -> ProcessResult { let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; - let ix = budget_instruction::apply_timestamp(&config.keypair.pubkey(), pubkey, to, dt); + let ix = budget_instruction::apply_timestamp(&config.signers[0].pubkey(), pubkey, to, dt); let message = Message::new(vec![ix]); let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&[config.keypair.as_ref()], blockhash)?; + tx.try_sign(&config.signers, blockhash)?; check_account_for_fee( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.signers[0]]); log_instruction_custom_error::(result) } @@ -1196,14 +1356,14 @@ fn process_transfer( config: &CliConfig, lamports: u64, to: &Pubkey, - from: Option<&(dyn Signer + 'static)>, + from: SignerIndex, sign_only: bool, blockhash_query: &BlockhashQuery, nonce_account: Option<&Pubkey>, - nonce_authority: Option<&(dyn Signer + 'static)>, - fee_payer: Option<&(dyn Signer + 'static)>, + nonce_authority: SignerIndex, + fee_payer: SignerIndex, ) -> ProcessResult { - let from = from.unwrap_or_else(|| config.keypair.as_ref()); + let from = config.signers[from]; check_unique_pubkeys( (&from.pubkey(), "cli keypair".to_string()), @@ -1214,29 +1374,21 @@ fn process_transfer( blockhash_query.get_blockhash_fee_calculator(rpc_client)?; let ixs = vec![system_instruction::transfer(&from.pubkey(), to, lamports)]; - let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); - let fee_payer = fee_payer.unwrap_or_else(|| config.keypair.as_ref()); - let mut signers = vec![fee_payer]; - if *fee_payer != *from { - signers.push(from) - } - let mut tx = if let Some(nonce_account) = &nonce_account { - signers.push(nonce_authority); - let message = Message::new_with_nonce( + let nonce_authority = config.signers[nonce_authority]; + let fee_payer = config.signers[fee_payer]; + + let message = if let Some(nonce_account) = &nonce_account { + Message::new_with_nonce( ixs, Some(&fee_payer.pubkey()), nonce_account, &nonce_authority.pubkey(), - ); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&signers, recent_blockhash)?; - tx + ) } else { - let message = Message::new_with_payer(ixs, Some(&fee_payer.pubkey())); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&signers, recent_blockhash)?; - tx + Message::new_with_payer(ixs, Some(&fee_payer.pubkey())) }; + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&config.signers, recent_blockhash)?; if sign_only { return_signers(&tx) @@ -1251,7 +1403,7 @@ fn process_transfer( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); log_instruction_custom_error::(result) } } @@ -1264,28 +1416,26 @@ fn process_witness( ) -> ProcessResult { let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; - let ix = budget_instruction::apply_signature(&config.keypair.pubkey(), pubkey, to); + let ix = budget_instruction::apply_signature(&config.signers[0].pubkey(), pubkey, to); let message = Message::new(vec![ix]); let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&[config.keypair.as_ref()], blockhash)?; + tx.try_sign(&config.signers, blockhash)?; check_account_for_fee( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.signers[0]]); log_instruction_custom_error::(result) } pub fn process_command(config: &CliConfig) -> ProcessResult { if config.verbose { println_name_value("RPC URL:", &config.json_rpc_url); - if let Some(keypair_path) = &config.keypair_path { - println_name_value("Keypair Path:", keypair_path); - if keypair_path.starts_with("usb://") { - println_name_value("Pubkey:", &format!("{:?}", config.pubkey()?)); - } + println_name_value("Default Signer Path:", &config.keypair_path); + if config.keypair_path.starts_with("usb://") { + println_name_value("Pubkey:", &format!("{:?}", config.pubkey()?)); } } @@ -1361,13 +1511,13 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { // Assign authority to nonce account CliCommand::AuthorizeNonceAccount { nonce_account, - ref nonce_authority, + nonce_authority, new_authority, } => process_authorize_nonce_account( &rpc_client, config, nonce_account, - nonce_authority.as_deref(), + *nonce_authority, new_authority, ), // Create nonce account @@ -1379,7 +1529,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { } => process_create_nonce_account( &rpc_client, config, - nonce_account.as_ref().as_ref(), + *nonce_account, seed.clone(), *nonce_authority, *lamports, @@ -1391,13 +1541,8 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { // Get a new nonce CliCommand::NewNonce { nonce_account, - ref nonce_authority, - } => process_new_nonce( - &rpc_client, - config, - nonce_account, - nonce_authority.as_deref(), - ), + nonce_authority, + } => process_new_nonce(&rpc_client, config, nonce_account, *nonce_authority), // Show the contents of a nonce account CliCommand::ShowNonceAccount { nonce_account_pubkey, @@ -1406,14 +1551,14 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { // Withdraw lamports from a nonce account CliCommand::WithdrawFromNonceAccount { nonce_account, - ref nonce_authority, + nonce_authority, destination_account_pubkey, lamports, } => process_withdraw_from_nonce_account( &rpc_client, config, &nonce_account, - nonce_authority.as_deref(), + *nonce_authority, &destination_account_pubkey, *lamports, ), @@ -1429,7 +1574,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { // Create stake account CliCommand::CreateStakeAccount { - ref stake_account, + stake_account, seed, staker, withdrawer, @@ -1438,13 +1583,13 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { sign_only, blockhash_query, ref nonce_account, - ref nonce_authority, - ref fee_payer, - ref from, + nonce_authority, + fee_payer, + from, } => process_create_stake_account( &rpc_client, config, - stake_account.as_ref().as_ref(), + *stake_account, seed, staker, withdrawer, @@ -1453,76 +1598,76 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { *sign_only, blockhash_query, nonce_account.as_ref(), - nonce_authority.as_deref(), - fee_payer.as_deref(), - from.as_deref(), + *nonce_authority, + *fee_payer, + *from, ), CliCommand::DeactivateStake { stake_account_pubkey, - ref stake_authority, + stake_authority, sign_only, blockhash_query, nonce_account, - ref nonce_authority, - ref fee_payer, + nonce_authority, + fee_payer, } => process_deactivate_stake_account( &rpc_client, config, &stake_account_pubkey, - stake_authority.as_deref(), + *stake_authority, *sign_only, blockhash_query, *nonce_account, - nonce_authority.as_deref(), - fee_payer.as_deref(), + *nonce_authority, + *fee_payer, ), CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, - ref stake_authority, + stake_authority, force, sign_only, blockhash_query, nonce_account, - ref nonce_authority, - ref fee_payer, + nonce_authority, + fee_payer, } => process_delegate_stake( &rpc_client, config, &stake_account_pubkey, &vote_account_pubkey, - stake_authority.as_deref(), + *stake_authority, *force, *sign_only, blockhash_query, *nonce_account, - nonce_authority.as_deref(), - fee_payer.as_deref(), + *nonce_authority, + *fee_payer, ), CliCommand::SplitStake { stake_account_pubkey, - ref stake_authority, + stake_authority, sign_only, blockhash_query, nonce_account, - ref nonce_authority, + nonce_authority, split_stake_account, seed, lamports, - ref fee_payer, + fee_payer, } => process_split_stake( &rpc_client, config, &stake_account_pubkey, - stake_authority.as_deref(), + *stake_authority, *sign_only, blockhash_query, *nonce_account, - nonce_authority.as_deref(), - split_stake_account.as_ref().as_ref(), + *nonce_authority, + *split_stake_account, seed, *lamports, - fee_payer.as_deref(), + *fee_payer, ), CliCommand::ShowStakeAccount { pubkey: stake_account_pubkey, @@ -1540,68 +1685,68 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { stake_account_pubkey, new_authorized_pubkey, stake_authorize, - ref authority, + authority, sign_only, blockhash_query, nonce_account, - ref nonce_authority, - ref fee_payer, + nonce_authority, + fee_payer, } => process_stake_authorize( &rpc_client, config, &stake_account_pubkey, &new_authorized_pubkey, *stake_authorize, - authority.as_deref(), + *authority, *sign_only, blockhash_query, *nonce_account, - nonce_authority.as_deref(), - fee_payer.as_deref(), + *nonce_authority, + *fee_payer, ), CliCommand::StakeSetLockup { stake_account_pubkey, mut lockup, - ref custodian, + custodian, sign_only, blockhash_query, nonce_account, - ref nonce_authority, - ref fee_payer, + nonce_authority, + fee_payer, } => process_stake_set_lockup( &rpc_client, config, &stake_account_pubkey, &mut lockup, - custodian.as_deref(), + *custodian, *sign_only, blockhash_query, *nonce_account, - nonce_authority.as_deref(), - fee_payer.as_deref(), + *nonce_authority, + *fee_payer, ), CliCommand::WithdrawStake { stake_account_pubkey, destination_account_pubkey, lamports, - ref withdraw_authority, + withdraw_authority, sign_only, blockhash_query, ref nonce_account, - ref nonce_authority, - ref fee_payer, + nonce_authority, + fee_payer, } => process_withdraw_stake( &rpc_client, config, &stake_account_pubkey, &destination_account_pubkey, *lamports, - withdraw_authority.as_deref(), + *withdraw_authority, *sign_only, blockhash_query, nonce_account.as_ref(), - nonce_authority.as_deref(), - fee_payer.as_deref(), + *nonce_authority, + *fee_payer, ), // Storage Commands @@ -1614,8 +1759,8 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { } => process_create_storage_account( &rpc_client, config, + *storage_account, &account_owner, - storage_account, *account_type, ), CliCommand::ClaimStorageReward { @@ -1654,7 +1799,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { // Create vote account CliCommand::CreateVoteAccount { - vote_account, seed, node_pubkey, authorized_voter, @@ -1663,7 +1807,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { } => process_create_vote_account( &rpc_client, config, - vote_account, seed, &node_pubkey, authorized_voter, @@ -1693,13 +1836,11 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { CliCommand::VoteUpdateValidator { vote_account_pubkey, new_identity_pubkey, - authorized_voter, } => process_vote_update_validator( &rpc_client, config, &vote_account_pubkey, &new_identity_pubkey, - authorized_voter, ), // Wallet Commands @@ -1747,7 +1888,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { sign_only, blockhash_query, nonce_account, - ref nonce_authority, + nonce_authority, }) => process_pay( &rpc_client, config, @@ -1760,7 +1901,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { *sign_only, blockhash_query, *nonce_account, - nonce_authority.as_deref(), + *nonce_authority, ), CliCommand::ShowAccount { pubkey, @@ -1780,23 +1921,23 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { CliCommand::Transfer { lamports, to, - ref from, + from, sign_only, ref blockhash_query, ref nonce_account, - ref nonce_authority, - ref fee_payer, + nonce_authority, + fee_payer, } => process_transfer( &rpc_client, config, *lamports, to, - from.as_deref(), + *from, *sign_only, blockhash_query, nonce_account.as_ref(), - nonce_authority.as_deref(), - fee_payer.as_deref(), + *nonce_authority, + *fee_payer, ), // Apply witness signature to contract CliCommand::Witness(to, pubkey) => process_witness(&rpc_client, config, &to, &pubkey), @@ -2239,7 +2380,7 @@ mod tests { system_program, transaction::TransactionError, }; - use std::{collections::HashMap, path::PathBuf, rc::Rc}; + use std::{collections::HashMap, path::PathBuf}; fn make_tmp_path(name: &str) -> String { let out_dir = std::env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string()); @@ -2255,6 +2396,71 @@ mod tests { path } + #[test] + fn test_generate_unique_signers() { + let matches = ArgMatches::default(); + + let default_keypair = Keypair::new(); + let default_keypair_file = make_tmp_path("keypair_file"); + write_keypair_file(&default_keypair, &default_keypair_file).unwrap(); + + let signer_info = + generate_unique_signers(vec![], &matches, &default_keypair_file, None).unwrap(); + assert_eq!(signer_info.signers.len(), 0); + + let signer_info = + generate_unique_signers(vec![None, None], &matches, &default_keypair_file, None) + .unwrap(); + assert_eq!(signer_info.signers.len(), 1); + assert_eq!(signer_info.index_of(None), Some(0)); + assert_eq!(signer_info.index_of(Some(Pubkey::new_rand())), None); + + let keypair0 = keypair_from_seed(&[1u8; 32]).unwrap(); + let keypair0_pubkey = keypair0.pubkey(); + let keypair0_clone = keypair_from_seed(&[1u8; 32]).unwrap(); + let keypair0_clone_pubkey = keypair0.pubkey(); + let signers = vec![None, Some(keypair0.into()), Some(keypair0_clone.into())]; + let signer_info = + generate_unique_signers(signers, &matches, &default_keypair_file, None).unwrap(); + assert_eq!(signer_info.signers.len(), 2); + assert_eq!(signer_info.index_of(None), Some(0)); + assert_eq!(signer_info.index_of(Some(keypair0_pubkey)), Some(1)); + assert_eq!(signer_info.index_of(Some(keypair0_clone_pubkey)), Some(1)); + + let keypair0 = keypair_from_seed(&[1u8; 32]).unwrap(); + let keypair0_pubkey = keypair0.pubkey(); + let keypair0_clone = keypair_from_seed(&[1u8; 32]).unwrap(); + let signers = vec![Some(keypair0.into()), Some(keypair0_clone.into())]; + let signer_info = + generate_unique_signers(signers, &matches, &default_keypair_file, None).unwrap(); + assert_eq!(signer_info.signers.len(), 1); + assert_eq!(signer_info.index_of(Some(keypair0_pubkey)), Some(0)); + + // Signers with the same pubkey are not distinct + let keypair0 = keypair_from_seed(&[2u8; 32]).unwrap(); + let keypair0_pubkey = keypair0.pubkey(); + let keypair1 = keypair_from_seed(&[3u8; 32]).unwrap(); + let keypair1_pubkey = keypair1.pubkey(); + let message = vec![0, 1, 2, 3]; + let presigner0 = Presigner::new(&keypair0.pubkey(), &keypair0.sign_message(&message)); + let presigner0_pubkey = presigner0.pubkey(); + let presigner1 = Presigner::new(&keypair1.pubkey(), &keypair1.sign_message(&message)); + let presigner1_pubkey = presigner1.pubkey(); + let signers = vec![ + Some(keypair0.into()), + Some(presigner0.into()), + Some(presigner1.into()), + Some(keypair1.into()), + ]; + let signer_info = + generate_unique_signers(signers, &matches, &default_keypair_file, None).unwrap(); + assert_eq!(signer_info.signers.len(), 2); + assert_eq!(signer_info.index_of(Some(keypair0_pubkey)), Some(0)); + assert_eq!(signer_info.index_of(Some(keypair1_pubkey)), Some(1)); + assert_eq!(signer_info.index_of(Some(presigner0_pubkey)), Some(0)); + assert_eq!(signer_info.index_of(Some(presigner1_pubkey)), Some(1)); + } + #[test] fn test_cli_parse_command() { let test_commands = app("test", "desc", "version"); @@ -2273,7 +2479,7 @@ mod tests { .clone() .get_matches_from(vec!["test", "airdrop", "50", &pubkey_string]); assert_eq!( - parse_command(&test_airdrop).unwrap(), + parse_command(&test_airdrop, "", None).unwrap(), CliCommandInfo { command: CliCommand::Airdrop { faucet_host: None, @@ -2281,13 +2487,14 @@ mod tests { pubkey: Some(pubkey), lamports: 50_000_000_000, }, - require_keypair: true, + signers: vec![], } ); // Test Balance Subcommand, incl pubkey and keypair-file inputs + let default_keypair = Keypair::new(); let keypair_file = make_tmp_path("keypair_file"); - write_keypair_file(&Keypair::new(), &keypair_file).unwrap(); + write_keypair_file(&default_keypair, &keypair_file).unwrap(); let keypair = read_keypair_file(&keypair_file).unwrap(); let test_balance = test_commands.clone().get_matches_from(vec![ "test", @@ -2295,13 +2502,13 @@ mod tests { &keypair.pubkey().to_string(), ]); assert_eq!( - parse_command(&test_balance).unwrap(), + parse_command(&test_balance, "", None).unwrap(), CliCommandInfo { command: CliCommand::Balance { pubkey: Some(keypair.pubkey()), use_lamports_unit: false }, - require_keypair: false + signers: vec![], } ); let test_balance = test_commands.clone().get_matches_from(vec![ @@ -2311,13 +2518,13 @@ mod tests { "--lamports", ]); assert_eq!( - parse_command(&test_balance).unwrap(), + parse_command(&test_balance, "", None).unwrap(), CliCommandInfo { command: CliCommand::Balance { pubkey: Some(keypair.pubkey()), use_lamports_unit: true }, - require_keypair: false + signers: vec![], } ); let test_balance = @@ -2325,13 +2532,13 @@ mod tests { .clone() .get_matches_from(vec!["test", "balance", "--lamports"]); assert_eq!( - parse_command(&test_balance).unwrap(), + parse_command(&test_balance, &keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Balance { pubkey: None, use_lamports_unit: true }, - require_keypair: true + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } ); @@ -2341,10 +2548,10 @@ mod tests { .clone() .get_matches_from(vec!["test", "cancel", &pubkey_string]); assert_eq!( - parse_command(&test_cancel).unwrap(), + parse_command(&test_cancel, &keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Cancel(pubkey), - require_keypair: true + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } ); @@ -2356,16 +2563,16 @@ mod tests { .clone() .get_matches_from(vec!["test", "confirm", &signature_string]); assert_eq!( - parse_command(&test_confirm).unwrap(), + parse_command(&test_confirm, "", None).unwrap(), CliCommandInfo { command: CliCommand::Confirm(signature), - require_keypair: false + signers: vec![], } ); let test_bad_signature = test_commands .clone() .get_matches_from(vec!["test", "confirm", "deadbeef"]); - assert!(parse_command(&test_bad_signature).is_err()); + assert!(parse_command(&test_bad_signature, "", None).is_err()); // Test CreateAddressWithSeed let from_pubkey = Some(Pubkey::new_rand()); @@ -2384,14 +2591,14 @@ mod tests { &from_str, ]); assert_eq!( - parse_command(&test_create_address_with_seed).unwrap(), + parse_command(&test_create_address_with_seed, "", None).unwrap(), CliCommandInfo { command: CliCommand::CreateAddressWithSeed { from_pubkey, seed: "seed".to_string(), program_id: *program_id }, - require_keypair: false + signers: vec![], } ); } @@ -2402,14 +2609,14 @@ mod tests { "STAKE", ]); assert_eq!( - parse_command(&test_create_address_with_seed).unwrap(), + parse_command(&test_create_address_with_seed, "", None).unwrap(), CliCommandInfo { command: CliCommand::CreateAddressWithSeed { from_pubkey: None, seed: "seed".to_string(), program_id: solana_stake_program::id(), }, - require_keypair: true + signers: vec![], } ); @@ -2419,10 +2626,10 @@ mod tests { .clone() .get_matches_from(vec!["test", "deploy", "/Users/test/program.o"]); assert_eq!( - parse_command(&test_deploy).unwrap(), + parse_command(&test_deploy, &keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Deploy("/Users/test/program.o".to_string()), - require_keypair: true + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } ); @@ -2432,14 +2639,14 @@ mod tests { .clone() .get_matches_from(vec!["test", "pay", &pubkey_string, "50"]); assert_eq!( - parse_command(&test_pay).unwrap(), + parse_command(&test_pay, &keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Pay(PayCommand { lamports: 50_000_000_000, to: pubkey, ..PayCommand::default() }), - require_keypair: true + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } ); @@ -2455,7 +2662,7 @@ mod tests { &witness1_string, ]); assert_eq!( - parse_command(&test_pay_multiple_witnesses).unwrap(), + parse_command(&test_pay_multiple_witnesses, &keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Pay(PayCommand { lamports: 50_000_000_000, @@ -2463,7 +2670,7 @@ mod tests { witnesses: Some(vec![witness0, witness1]), ..PayCommand::default() }), - require_keypair: true + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } ); let test_pay_single_witness = test_commands.clone().get_matches_from(vec![ @@ -2475,7 +2682,7 @@ mod tests { &witness0_string, ]); assert_eq!( - parse_command(&test_pay_single_witness).unwrap(), + parse_command(&test_pay_single_witness, &keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Pay(PayCommand { lamports: 50_000_000_000, @@ -2483,7 +2690,7 @@ mod tests { witnesses: Some(vec![witness0]), ..PayCommand::default() }), - require_keypair: true + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } ); @@ -2499,7 +2706,7 @@ mod tests { &witness0_string, ]); assert_eq!( - parse_command(&test_pay_timestamp).unwrap(), + parse_command(&test_pay_timestamp, &keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Pay(PayCommand { lamports: 50_000_000_000, @@ -2508,7 +2715,7 @@ mod tests { timestamp_pubkey: Some(witness0), ..PayCommand::default() }), - require_keypair: true + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } ); @@ -2525,7 +2732,7 @@ mod tests { "--sign-only", ]); assert_eq!( - parse_command(&test_pay).unwrap(), + parse_command(&test_pay, &keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Pay(PayCommand { lamports: 50_000_000_000, @@ -2534,7 +2741,7 @@ mod tests { sign_only: true, ..PayCommand::default() }), - require_keypair: true, + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } ); @@ -2548,7 +2755,7 @@ mod tests { &blockhash_string, ]); assert_eq!( - parse_command(&test_pay).unwrap(), + parse_command(&test_pay, &keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Pay(PayCommand { lamports: 50_000_000_000, @@ -2556,7 +2763,7 @@ mod tests { blockhash_query: BlockhashQuery::FeeCalculator(blockhash), ..PayCommand::default() }), - require_keypair: true + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } ); @@ -2574,7 +2781,7 @@ mod tests { &pubkey_string, ]); assert_eq!( - parse_command(&test_pay).unwrap(), + parse_command(&test_pay, &keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Pay(PayCommand { lamports: 50_000_000_000, @@ -2583,7 +2790,7 @@ mod tests { nonce_account: Some(pubkey), ..PayCommand::default() }), - require_keypair: true + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } ); @@ -2604,17 +2811,17 @@ mod tests { &keypair_file, ]); assert_eq!( - parse_command(&test_pay).unwrap(), + parse_command(&test_pay, &keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Pay(PayCommand { lamports: 50_000_000_000, to: pubkey, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(pubkey), - nonce_authority: Some(keypair.into()), + nonce_authority: 0, ..PayCommand::default() }), - require_keypair: true + signers: vec![keypair.into()], } ); @@ -2639,17 +2846,17 @@ mod tests { &signer_arg, ]); assert_eq!( - parse_command(&test_pay).unwrap(), + parse_command(&test_pay, &keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Pay(PayCommand { lamports: 50_000_000_000, to: pubkey, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(pubkey), - nonce_authority: Some(Presigner::new(&authority_pubkey, &sig).into()), + nonce_authority: 0, ..PayCommand::default() }), - require_keypair: true + signers: vec![keypair.into()], } ); @@ -2674,7 +2881,7 @@ mod tests { "--signer", &signer_arg, ]); - assert!(parse_command(&test_pay).is_err()); + assert!(parse_command(&test_pay, &keypair_file, None).is_err()); // Test Send-Signature Subcommand let test_send_signature = test_commands.clone().get_matches_from(vec![ @@ -2684,10 +2891,10 @@ mod tests { &pubkey_string, ]); assert_eq!( - parse_command(&test_send_signature).unwrap(), + parse_command(&test_send_signature, &keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Witness(pubkey, pubkey), - require_keypair: true + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } ); let test_pay_multiple_witnesses = test_commands.clone().get_matches_from(vec![ @@ -2705,7 +2912,7 @@ mod tests { &witness1_string, ]); assert_eq!( - parse_command(&test_pay_multiple_witnesses).unwrap(), + parse_command(&test_pay_multiple_witnesses, &keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Pay(PayCommand { lamports: 50_000_000_000, @@ -2715,7 +2922,7 @@ mod tests { witnesses: Some(vec![witness0, witness1]), ..PayCommand::default() }), - require_keypair: true + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } ); @@ -2729,10 +2936,10 @@ mod tests { "2018-09-19T17:30:59", ]); assert_eq!( - parse_command(&test_send_timestamp).unwrap(), + parse_command(&test_send_timestamp, &keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::TimeElapsed(pubkey, pubkey, dt), - require_keypair: true + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } ); let test_bad_timestamp = test_commands.clone().get_matches_from(vec![ @@ -2743,7 +2950,7 @@ mod tests { "--date", "20180919T17:30:59", ]); - assert!(parse_command(&test_bad_timestamp).is_err()); + assert!(parse_command(&test_bad_timestamp, &keypair_file, None).is_err()); } #[test] @@ -2754,7 +2961,7 @@ mod tests { let keypair = Keypair::new(); let pubkey = keypair.pubkey().to_string(); - config.keypair = keypair.into(); + config.signers = vec![&keypair]; config.command = CliCommand::Address; assert_eq!(process_command(&config).unwrap(), pubkey); @@ -2782,17 +2989,18 @@ mod tests { let bob_pubkey = bob_keypair.pubkey(); let node_pubkey = Pubkey::new_rand(); config.command = CliCommand::CreateVoteAccount { - vote_account: bob_keypair.into(), seed: None, node_pubkey, authorized_voter: Some(bob_pubkey), authorized_withdrawer: Some(bob_pubkey), commission: 0, }; + config.signers = vec![&keypair, &bob_keypair]; let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); let new_authorized_pubkey = Pubkey::new_rand(); + config.signers = vec![&bob_keypair]; config.command = CliCommand::VoteAuthorize { vote_account_pubkey: bob_pubkey, new_authorized_pubkey, @@ -2802,10 +3010,10 @@ mod tests { assert_eq!(signature.unwrap(), SIGNATURE.to_string()); let new_identity_pubkey = Pubkey::new_rand(); + config.signers = vec![&keypair, &bob_keypair]; config.command = CliCommand::VoteUpdateValidator { vote_account_pubkey: bob_pubkey, new_identity_pubkey, - authorized_voter: Keypair::new().into(), }; let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); @@ -2814,7 +3022,7 @@ mod tests { let bob_pubkey = bob_keypair.pubkey(); let custodian = Pubkey::new_rand(); config.command = CliCommand::CreateStakeAccount { - stake_account: Rc::new(bob_keypair.into()), + stake_account: 1, seed: None, staker: None, withdrawer: None, @@ -2827,10 +3035,11 @@ mod tests { sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: None, - from: None, + nonce_authority: 0, + fee_payer: 0, + from: 0, }; + config.signers = vec![&keypair, &bob_keypair]; let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); @@ -2840,25 +3049,26 @@ mod tests { stake_account_pubkey: stake_pubkey, destination_account_pubkey: to_pubkey, lamports: 100, - withdraw_authority: None, + withdraw_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; + config.signers = vec![&keypair]; let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); let stake_pubkey = Pubkey::new_rand(); config.command = CliCommand::DeactivateStake { stake_account_pubkey: stake_pubkey, - stake_authority: None, + stake_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); @@ -2867,16 +3077,17 @@ mod tests { let split_stake_account = Keypair::new(); config.command = CliCommand::SplitStake { stake_account_pubkey: stake_pubkey, - stake_authority: None, + stake_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - split_stake_account: Rc::new(split_stake_account.into()), + nonce_authority: 0, + split_stake_account: 1, seed: None, lamports: 1234, - fee_payer: None, + fee_payer: 0, }; + config.signers = vec![&keypair, &split_stake_account]; let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); @@ -2890,6 +3101,7 @@ mod tests { }; assert_eq!(process_command(&config).unwrap(), "1234"); + config.signers = vec![&keypair]; config.command = CliCommand::Pay(PayCommand { lamports: 10, to: bob_pubkey, @@ -2904,7 +3116,7 @@ mod tests { lamports: 10, to: bob_pubkey, timestamp: Some(dt), - timestamp_pubkey: Some(config.keypair.pubkey()), + timestamp_pubkey: Some(config.signers[0].pubkey()), ..PayCommand::default() }); let result = process_command(&config); @@ -2946,7 +3158,10 @@ mod tests { value: json!(RpcAccount::encode( Account::new_data( 1, - &NonceState::Initialized(NonceMeta::new(&config.keypair.pubkey()), blockhash), + &NonceState::Initialized( + NonceMeta::new(&config.signers[0].pubkey()), + blockhash + ), &system_program::ID, ) .unwrap() @@ -2988,14 +3203,16 @@ mod tests { to: bob_pubkey, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(bob_pubkey), - nonce_authority: Some(bob_keypair.into()), + nonce_authority: 1, ..PayCommand::default() }); + config.signers = vec![&keypair, &bob_keypair]; let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); let process_id = Pubkey::new_rand(); config.command = CliCommand::TimeElapsed(bob_pubkey, process_id, dt); + config.signers = vec![&keypair]; let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); @@ -3060,13 +3277,13 @@ mod tests { let bob_keypair = Keypair::new(); config.command = CliCommand::CreateVoteAccount { - vote_account: bob_keypair.into(), seed: None, node_pubkey, authorized_voter: Some(bob_pubkey), authorized_withdrawer: Some(bob_pubkey), commission: 0, }; + config.signers = vec![&keypair, &bob_keypair]; assert!(process_command(&config).is_err()); config.command = CliCommand::VoteAuthorize { @@ -3079,7 +3296,6 @@ mod tests { config.command = CliCommand::VoteUpdateValidator { vote_account_pubkey: bob_pubkey, new_identity_pubkey: bob_pubkey, - authorized_voter: Keypair::new().into(), }; assert!(process_command(&config).is_err()); @@ -3104,7 +3320,7 @@ mod tests { lamports: 10, to: bob_pubkey, timestamp: Some(dt), - timestamp_pubkey: Some(config.keypair.pubkey()), + timestamp_pubkey: Some(config.signers[0].pubkey()), ..PayCommand::default() }); assert!(process_command(&config).is_err()); @@ -3134,6 +3350,8 @@ mod tests { // Success case let mut config = CliConfig::default(); config.rpc_client = Some(RpcClient::new_mock("deploy_succeeds".to_string())); + let default_keypair = Keypair::new(); + config.signers = vec![&default_keypair]; config.command = CliCommand::Deploy(pathbuf.to_str().unwrap().to_string()); let result = process_command(&config); @@ -3157,6 +3375,10 @@ mod tests { fn test_parse_transfer_subcommand() { let test_commands = app("test", "desc", "version"); + let default_keypair = Keypair::new(); + let default_keypair_file = make_tmp_path("keypair_file"); + write_keypair_file(&default_keypair, &default_keypair_file).unwrap(); + //Test Transfer Subcommand, SOL let from_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); let from_pubkey = from_keypair.pubkey(); @@ -3168,19 +3390,19 @@ mod tests { .clone() .get_matches_from(vec!["test", "transfer", &to_string, "42"]); assert_eq!( - parse_command(&test_transfer).unwrap(), + parse_command(&test_transfer, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Transfer { lamports: 42_000_000_000, to: to_pubkey, - from: None, + from: 0, sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true, + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); @@ -3197,19 +3419,19 @@ mod tests { "--sign-only", ]); assert_eq!( - parse_command(&test_transfer).unwrap(), + parse_command(&test_transfer, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Transfer { lamports: 42_000_000_000, to: to_pubkey, - from: None, + from: 0, sign_only: true, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true, + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); @@ -3231,19 +3453,19 @@ mod tests { &blockhash_string, ]); assert_eq!( - parse_command(&test_transfer).unwrap(), + parse_command(&test_transfer, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Transfer { lamports: 42_000_000_000, to: to_pubkey, - from: Some(Presigner::new(&from_pubkey, &from_sig).into()), + from: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, - nonce_authority: None, - fee_payer: Some(Presigner::new(&from_pubkey, &from_sig).into()), + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true, + signers: vec![Presigner::new(&from_pubkey, &from_sig).into()], } ); @@ -3266,19 +3488,22 @@ mod tests { &nonce_authority_file, ]); assert_eq!( - parse_command(&test_transfer).unwrap(), + parse_command(&test_transfer, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Transfer { lamports: 42_000_000_000, to: to_pubkey, - from: None, + from: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_address.into()), - nonce_authority: Some(read_keypair_file(&nonce_authority_file).unwrap().into()), - fee_payer: None, + nonce_authority: 1, + fee_payer: 0, }, - require_keypair: true, + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&nonce_authority_file).unwrap().into() + ], } ); } diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index 0f536b2d24..9916a0a700 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -8,12 +8,13 @@ use crate::{ use clap::{value_t, value_t_or_exit, App, Arg, ArgMatches, SubCommand}; use console::{style, Emoji}; use indicatif::{ProgressBar, ProgressStyle}; -use solana_clap_utils::{input_parsers::*, input_validators::*}; +use solana_clap_utils::{input_parsers::*, input_validators::*, keypair::signer_from_path}; use solana_client::{ pubsub_client::{PubsubClient, SlotInfoMessage}, rpc_client::RpcClient, rpc_response::RpcVoteAccountInfo, }; +use solana_remote_wallet::remote_wallet::RemoteWalletManager; use solana_sdk::{ account_utils::StateMut, clock::{self, Slot}, @@ -239,11 +240,15 @@ pub fn parse_catchup(matches: &ArgMatches<'_>) -> Result) -> Result { +pub fn parse_cluster_ping( + matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, +) -> Result { let lamports = value_t_or_exit!(matches, "lamports", u64); let interval = Duration::from_secs(value_t_or_exit!(matches, "interval", u64)); let count = if matches.is_present("count") { @@ -265,7 +270,12 @@ pub fn parse_cluster_ping(matches: &ArgMatches<'_>) -> Result) -> Result) -> Result) -> Result) -> Result) -> Result) -> Result) -> Result) -> Result ProcessResult { let to = Keypair::new().pubkey(); - println_name_value("Source Account:", &config.keypair.pubkey().to_string()); + println_name_value("Source Account:", &config.signers[0].pubkey().to_string()); println_name_value("Destination Account:", &to.to_string()); println!(); @@ -745,13 +755,13 @@ pub fn process_ping( let (recent_blockhash, fee_calculator) = rpc_client.get_new_blockhash(&last_blockhash)?; last_blockhash = recent_blockhash; - let ix = system_instruction::transfer(&config.keypair.pubkey(), &to, lamports); + let ix = system_instruction::transfer(&config.signers[0].pubkey(), &to, lamports); let message = Message::new(vec![ix]); let mut transaction = Transaction::new_unsigned(message); - transaction.try_sign(&[config.keypair.as_ref()], recent_blockhash)?; + transaction.try_sign(&config.signers, recent_blockhash)?; check_account_for_fee( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &transaction.message, )?; @@ -1105,28 +1115,38 @@ pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool) mod tests { use super::*; use crate::cli::{app, parse_command}; + use solana_sdk::signature::{write_keypair, 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) + } #[test] fn test_parse_command() { let test_commands = app("test", "desc", "version"); + let default_keypair = Keypair::new(); + let (default_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); let test_cluster_version = test_commands .clone() .get_matches_from(vec!["test", "cluster-version"]); assert_eq!( - parse_command(&test_cluster_version).unwrap(), + parse_command(&test_cluster_version, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::ClusterVersion, - require_keypair: false + signers: vec![], } ); let test_fees = test_commands.clone().get_matches_from(vec!["test", "fees"]); assert_eq!( - parse_command(&test_fees).unwrap(), + parse_command(&test_fees, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Fees, - require_keypair: false + signers: vec![], } ); @@ -1136,10 +1156,10 @@ mod tests { .clone() .get_matches_from(vec!["test", "block-time", &slot.to_string()]); assert_eq!( - parse_command(&test_get_block_time).unwrap(), + parse_command(&test_get_block_time, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::GetBlockTime { slot }, - require_keypair: false + signers: vec![], } ); @@ -1147,12 +1167,12 @@ mod tests { .clone() .get_matches_from(vec!["test", "epoch-info"]); assert_eq!( - parse_command(&test_get_epoch_info).unwrap(), + parse_command(&test_get_epoch_info, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::GetEpochInfo { commitment_config: CommitmentConfig::recent(), }, - require_keypair: false + signers: vec![], } ); @@ -1160,21 +1180,21 @@ mod tests { .clone() .get_matches_from(vec!["test", "genesis-hash"]); assert_eq!( - parse_command(&test_get_genesis_hash).unwrap(), + parse_command(&test_get_genesis_hash, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::GetGenesisHash, - require_keypair: false + signers: vec![], } ); let test_get_slot = test_commands.clone().get_matches_from(vec!["test", "slot"]); assert_eq!( - parse_command(&test_get_slot).unwrap(), + parse_command(&test_get_slot, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::GetSlot { commitment_config: CommitmentConfig::recent(), }, - require_keypair: false + signers: vec![], } ); @@ -1182,12 +1202,12 @@ mod tests { .clone() .get_matches_from(vec!["test", "transaction-count"]); assert_eq!( - parse_command(&test_transaction_count).unwrap(), + parse_command(&test_transaction_count, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::GetTransactionCount { commitment_config: CommitmentConfig::recent(), }, - require_keypair: false + signers: vec![], } ); @@ -1203,7 +1223,7 @@ mod tests { "--confirmed", ]); assert_eq!( - parse_command(&test_ping).unwrap(), + parse_command(&test_ping, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::Ping { lamports: 1, @@ -1212,7 +1232,7 @@ mod tests { timeout: Duration::from_secs(3), commitment_config: CommitmentConfig::default(), }, - require_keypair: true + signers: vec![default_keypair.into()], } ); } diff --git a/cli/src/main.rs b/cli/src/main.rs index 8047cf8b91..2131b38f3a 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -4,15 +4,15 @@ use console::style; use solana_clap_utils::{ input_parsers::derivation_of, input_validators::{is_derivation, is_url}, - keypair::{keypair_util_from_path, SKIP_SEED_PHRASE_VALIDATION_ARG}, + keypair::SKIP_SEED_PHRASE_VALIDATION_ARG, }; use solana_cli::{ - cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliError}, + cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliSigners}, display::{println_name_value, println_name_value_or}, }; use solana_cli_config::config::{Config, CONFIG_FILE}; - -use std::error; +use solana_remote_wallet::remote_wallet::{maybe_wallet_manager, RemoteWalletManager}; +use std::{error, sync::Arc}; fn parse_settings(matches: &ArgMatches<'_>) -> Result> { let parse_args = match matches.subcommand() { @@ -80,7 +80,10 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result) -> Result> { +pub fn parse_args<'a>( + matches: &ArgMatches<'_>, + wallet_manager: Option>, +) -> Result<(CliConfig<'a>, CliSigners), Box> { let config = if let Some(config_file) = matches.value_of("config_file") { Config::load(config_file).unwrap_or_default() } else { @@ -95,44 +98,29 @@ pub fn parse_args(matches: &ArgMatches<'_>) -> Result Result<(), Box> { @@ -228,7 +216,10 @@ fn main() -> Result<(), Box> { .get_matches(); if parse_settings(&matches)? { - let config = parse_args(&matches)?; + let wallet_manager = maybe_wallet_manager()?; + + let (mut config, signers) = parse_args(&matches, wallet_manager)?; + config.signers = signers.iter().map(|s| s.as_ref()).collect(); let result = process_command(&config)?; println!("{}", result); } diff --git a/cli/src/nonce.rs b/cli/src/nonce.rs index bce7794b04..6927560bf2 100644 --- a/cli/src/nonce.rs +++ b/cli/src/nonce.rs @@ -1,12 +1,14 @@ use crate::cli::{ - build_balance_message, check_account_for_fee, check_unique_pubkeys, + build_balance_message, check_account_for_fee, check_unique_pubkeys, generate_unique_signers, log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult, + SignerIndex, }; use clap::{App, Arg, ArgMatches, SubCommand}; use solana_clap_utils::{ input_parsers::*, input_validators::*, offline::BLOCKHASH_ARG, ArgConstant, }; use solana_client::rpc_client::RpcClient; +use solana_remote_wallet::remote_wallet::RemoteWalletManager; use solana_sdk::{ account::Account, account_utils::StateMut, @@ -14,7 +16,6 @@ use solana_sdk::{ message::Message, nonce_state::{Meta, NonceState}, pubkey::Pubkey, - signature::Signer, system_instruction::{ advance_nonce_account, authorize_nonce_account, create_address_with_seed, create_nonce_account, create_nonce_account_with_seed, withdraw_nonce_account, NonceError, @@ -23,6 +24,7 @@ use solana_sdk::{ system_program, transaction::Transaction, }; +use std::sync::Arc; #[derive(Debug, Clone, PartialEq)] pub enum CliNonceError { @@ -216,35 +218,61 @@ impl NonceSubCommands for App<'_, '_> { } } -pub fn parse_authorize_nonce_account(matches: &ArgMatches<'_>) -> Result { +pub fn parse_authorize_nonce_account( + matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, +) -> Result { let nonce_account = pubkey_of(matches, "nonce_account_keypair").unwrap(); let new_authority = pubkey_of(matches, "new_authority").unwrap(); - let nonce_authority = signer_of(NONCE_AUTHORITY_ARG.name, matches)?; + let (nonce_authority, nonce_authority_pubkey) = + signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?; + + let payer_provided = None; + let signer_info = generate_unique_signers( + vec![payer_provided, nonce_authority], + matches, + default_signer_path, + wallet_manager, + )?; Ok(CliCommandInfo { command: CliCommand::AuthorizeNonceAccount { nonce_account, - nonce_authority, + nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), new_authority, }, - require_keypair: true, + signers: signer_info.signers, }) } -pub fn parse_nonce_create_account(matches: &ArgMatches<'_>) -> Result { - let nonce_account = signer_of("nonce_account_keypair", matches)?.unwrap(); +pub fn parse_nonce_create_account( + matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, +) -> Result { + let (nonce_account, nonce_account_pubkey) = + signer_of(matches, "nonce_account_keypair", wallet_manager)?; let seed = matches.value_of("seed").map(|s| s.to_string()); let lamports = lamports_of_sol(matches, "amount").unwrap(); let nonce_authority = pubkey_of(matches, NONCE_AUTHORITY_ARG.name); + let payer_provided = None; + let signer_info = generate_unique_signers( + vec![payer_provided, nonce_account], + matches, + default_signer_path, + wallet_manager, + )?; + Ok(CliCommandInfo { command: CliCommand::CreateNonceAccount { - nonce_account: nonce_account.into(), + nonce_account: signer_info.index_of(nonce_account_pubkey).unwrap(), seed, nonce_authority, lamports, }, - require_keypair: true, + signers: signer_info.signers, }) } @@ -253,20 +281,33 @@ pub fn parse_get_nonce(matches: &ArgMatches<'_>) -> Result) -> Result { +pub fn parse_new_nonce( + matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, +) -> Result { let nonce_account = pubkey_of(matches, "nonce_account_keypair").unwrap(); - let nonce_authority = signer_of(NONCE_AUTHORITY_ARG.name, matches)?; + let (nonce_authority, nonce_authority_pubkey) = + signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?; + + let payer_provided = None; + let signer_info = generate_unique_signers( + vec![payer_provided, nonce_authority], + matches, + default_signer_path, + wallet_manager, + )?; Ok(CliCommandInfo { command: CliCommand::NewNonce { nonce_account, - nonce_authority, + nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), }, - require_keypair: true, + signers: signer_info.signers, }) } @@ -279,26 +320,37 @@ pub fn parse_show_nonce_account(matches: &ArgMatches<'_>) -> Result, + default_signer_path: &str, + wallet_manager: Option<&Arc>, ) -> Result { let nonce_account = pubkey_of(matches, "nonce_account_keypair").unwrap(); let destination_account_pubkey = pubkey_of(matches, "destination_account_pubkey").unwrap(); let lamports = lamports_of_sol(matches, "amount").unwrap(); - let nonce_authority = signer_of(NONCE_AUTHORITY_ARG.name, matches)?; + let (nonce_authority, nonce_authority_pubkey) = + signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?; + + let payer_provided = None; + let signer_info = generate_unique_signers( + vec![payer_provided, nonce_authority], + matches, + default_signer_path, + wallet_manager, + )?; Ok(CliCommandInfo { command: CliCommand::WithdrawFromNonceAccount { nonce_account, - nonce_authority, + nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), destination_account_pubkey, lamports, }, - require_keypair: true, + signers: signer_info.signers, }) } @@ -334,40 +386,36 @@ pub fn process_authorize_nonce_account( rpc_client: &RpcClient, config: &CliConfig, nonce_account: &Pubkey, - nonce_authority: Option<&dyn Signer>, + nonce_authority: SignerIndex, new_authority: &Pubkey, ) -> ProcessResult { let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; - let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); + let nonce_authority = config.signers[nonce_authority]; let ix = authorize_nonce_account(nonce_account, &nonce_authority.pubkey(), new_authority); - let message = Message::new_with_payer(vec![ix], Some(&config.keypair.pubkey())); + let message = Message::new_with_payer(vec![ix], Some(&config.signers[0].pubkey())); let mut tx = Transaction::new_unsigned(message); - tx.try_sign( - &[config.keypair.as_ref(), nonce_authority], - recent_blockhash, - )?; + tx.try_sign(&config.signers, recent_blockhash)?; check_account_for_fee( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &tx.message, )?; - let result = rpc_client - .send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref(), nonce_authority]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); log_instruction_custom_error::(result) } pub fn process_create_nonce_account( rpc_client: &RpcClient, config: &CliConfig, - nonce_account: &dyn Signer, + nonce_account: SignerIndex, seed: Option, nonce_authority: Option, lamports: u64, ) -> ProcessResult { - let nonce_account_pubkey = nonce_account.pubkey(); + let nonce_account_pubkey = config.signers[nonce_account].pubkey(); let nonce_account_address = if let Some(seed) = seed.clone() { create_address_with_seed(&nonce_account_pubkey, &seed, &system_program::id())? } else { @@ -375,7 +423,7 @@ pub fn process_create_nonce_account( }; check_unique_pubkeys( - (&config.keypair.pubkey(), "cli keypair".to_string()), + (&config.signers[0].pubkey(), "cli keypair".to_string()), (&nonce_account_address, "nonce_account".to_string()), )?; @@ -402,20 +450,20 @@ pub fn process_create_nonce_account( .into()); } - let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.pubkey()); + let nonce_authority = nonce_authority.unwrap_or_else(|| config.signers[0].pubkey()); let ixs = if let Some(seed) = seed { create_nonce_account_with_seed( - &config.keypair.pubkey(), // from - &nonce_account_address, // to - &nonce_account_pubkey, // base - &seed, // seed + &config.signers[0].pubkey(), // from + &nonce_account_address, // to + &nonce_account_pubkey, // base + &seed, // seed &nonce_authority, lamports, ) } else { create_nonce_account( - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &nonce_account_pubkey, &nonce_authority, lamports, @@ -424,23 +472,17 @@ pub fn process_create_nonce_account( let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; - let signers = if nonce_account_pubkey != config.keypair.pubkey() { - vec![config.keypair.as_ref(), nonce_account] // both must sign if `from` and `to` differ - } else { - vec![config.keypair.as_ref()] // when stake_account == config.keypair and there's a seed, we only need one signature - }; - - let message = Message::new_with_payer(ixs, Some(&config.keypair.pubkey())); + let message = Message::new_with_payer(ixs, Some(&config.signers[0].pubkey())); let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&signers, recent_blockhash)?; + tx.try_sign(&config.signers, recent_blockhash)?; check_account_for_fee( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &signers); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); log_instruction_custom_error::(result) } @@ -468,10 +510,10 @@ pub fn process_new_nonce( rpc_client: &RpcClient, config: &CliConfig, nonce_account: &Pubkey, - nonce_authority: Option<&dyn Signer>, + nonce_authority: SignerIndex, ) -> ProcessResult { check_unique_pubkeys( - (&config.keypair.pubkey(), "cli keypair".to_string()), + (&config.signers[0].pubkey(), "cli keypair".to_string()), (&nonce_account, "nonce_account_pubkey".to_string()), )?; @@ -482,23 +524,20 @@ pub fn process_new_nonce( .into()); } - let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); + let nonce_authority = config.signers[nonce_authority]; let ix = advance_nonce_account(&nonce_account, &nonce_authority.pubkey()); let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; - let message = Message::new_with_payer(vec![ix], Some(&config.keypair.pubkey())); + let message = Message::new_with_payer(vec![ix], Some(&config.signers[0].pubkey())); let mut tx = Transaction::new_unsigned(message); - tx.try_sign( - &[config.keypair.as_ref(), nonce_authority], - recent_blockhash, - )?; + tx.try_sign(&config.signers, recent_blockhash)?; check_account_for_fee( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &tx.message, )?; - let result = rpc_client - .send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref(), nonce_authority]); + let result = + rpc_client.send_and_confirm_transaction(&mut tx, &[config.signers[0], nonce_authority]); log_instruction_custom_error::(result) } @@ -555,33 +594,29 @@ pub fn process_withdraw_from_nonce_account( rpc_client: &RpcClient, config: &CliConfig, nonce_account: &Pubkey, - nonce_authority: Option<&dyn Signer>, + nonce_authority: SignerIndex, destination_account_pubkey: &Pubkey, lamports: u64, ) -> ProcessResult { let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; - let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); + let nonce_authority = config.signers[nonce_authority]; let ix = withdraw_nonce_account( nonce_account, &nonce_authority.pubkey(), destination_account_pubkey, lamports, ); - let message = Message::new_with_payer(vec![ix], Some(&config.keypair.pubkey())); + let message = Message::new_with_payer(vec![ix], Some(&config.signers[0].pubkey())); let mut tx = Transaction::new_unsigned(message); - tx.try_sign( - &[config.keypair.as_ref(), nonce_authority], - recent_blockhash, - )?; + tx.try_sign(&config.signers, recent_blockhash)?; check_account_for_fee( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &tx.message, )?; - let result = rpc_client - .send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref(), nonce_authority]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); log_instruction_custom_error::(result) } @@ -593,10 +628,9 @@ mod tests { account::Account, hash::hash, nonce_state::{Meta as NonceMeta, NonceState}, - signature::{read_keypair_file, write_keypair, Keypair}, + signature::{read_keypair_file, write_keypair, Keypair, Signer}, system_program, }; - use std::rc::Rc; use tempfile::NamedTempFile; fn make_tmp_file() -> (String, NamedTempFile) { @@ -607,6 +641,9 @@ mod tests { #[test] fn test_parse_command() { let test_commands = app("test", "desc", "version"); + let default_keypair = Keypair::new(); + let (default_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); let (keypair_file, mut tmp_file) = make_tmp_file(); let nonce_account_keypair = Keypair::new(); write_keypair(&nonce_account_keypair, tmp_file.as_file_mut()).unwrap(); @@ -625,14 +662,14 @@ mod tests { &Pubkey::default().to_string(), ]); assert_eq!( - parse_command(&test_authorize_nonce_account).unwrap(), + parse_command(&test_authorize_nonce_account, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::AuthorizeNonceAccount { nonce_account: nonce_account_pubkey, - nonce_authority: None, + nonce_authority: 0, new_authority: Pubkey::default(), }, - require_keypair: true, + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); @@ -646,16 +683,17 @@ mod tests { &authority_keypair_file, ]); assert_eq!( - parse_command(&test_authorize_nonce_account).unwrap(), + parse_command(&test_authorize_nonce_account, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::AuthorizeNonceAccount { nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(), - nonce_authority: Some( - read_keypair_file(&authority_keypair_file).unwrap().into() - ), + nonce_authority: 1, new_authority: Pubkey::default(), }, - require_keypair: true, + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&authority_keypair_file).unwrap().into() + ], } ); @@ -667,15 +705,18 @@ mod tests { "50", ]); assert_eq!( - parse_command(&test_create_nonce_account).unwrap(), + parse_command(&test_create_nonce_account, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::CreateNonceAccount { - nonce_account: Rc::new(read_keypair_file(&keypair_file).unwrap().into()), + nonce_account: 1, seed: None, nonce_authority: None, lamports: 50_000_000_000, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&keypair_file).unwrap().into() + ], } ); @@ -689,17 +730,18 @@ mod tests { &authority_keypair_file, ]); assert_eq!( - parse_command(&test_create_nonce_account).unwrap(), + parse_command(&test_create_nonce_account, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::CreateNonceAccount { - nonce_account: Rc::new(read_keypair_file(&keypair_file).unwrap().into()), + nonce_account: 1, seed: None, - nonce_authority: Some( - read_keypair_file(&authority_keypair_file).unwrap().pubkey() - ), + nonce_authority: Some(nonce_authority_keypair.pubkey()), lamports: 50_000_000_000, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&keypair_file).unwrap().into() + ], } ); @@ -710,10 +752,10 @@ mod tests { &nonce_account_string, ]); assert_eq!( - parse_command(&test_get_nonce).unwrap(), + parse_command(&test_get_nonce, &default_keypair_file, None).unwrap(), CliCommandInfo { - command: CliCommand::GetNonce(nonce_account_keypair.pubkey(),), - require_keypair: false + command: CliCommand::GetNonce(nonce_account_keypair.pubkey()), + signers: vec![], } ); @@ -724,13 +766,13 @@ mod tests { .get_matches_from(vec!["test", "new-nonce", &keypair_file]); let nonce_account = read_keypair_file(&keypair_file).unwrap(); assert_eq!( - parse_command(&test_new_nonce).unwrap(), + parse_command(&test_new_nonce, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::NewNonce { nonce_account: nonce_account.pubkey(), - nonce_authority: None, + nonce_authority: 0, }, - require_keypair: true + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); @@ -744,15 +786,16 @@ mod tests { ]); let nonce_account = read_keypair_file(&keypair_file).unwrap(); assert_eq!( - parse_command(&test_new_nonce).unwrap(), + parse_command(&test_new_nonce, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::NewNonce { nonce_account: nonce_account.pubkey(), - nonce_authority: Some( - read_keypair_file(&authority_keypair_file).unwrap().into() - ), + nonce_authority: 1, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&authority_keypair_file).unwrap().into() + ], } ); @@ -763,13 +806,13 @@ mod tests { &nonce_account_string, ]); assert_eq!( - parse_command(&test_show_nonce_account).unwrap(), + parse_command(&test_show_nonce_account, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::ShowNonceAccount { nonce_account_pubkey: nonce_account_keypair.pubkey(), use_lamports_unit: false, }, - require_keypair: false + signers: vec![], } ); @@ -782,15 +825,20 @@ mod tests { "42", ]); assert_eq!( - parse_command(&test_withdraw_from_nonce_account).unwrap(), + parse_command( + &test_withdraw_from_nonce_account, + &default_keypair_file, + None + ) + .unwrap(), CliCommandInfo { command: CliCommand::WithdrawFromNonceAccount { nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(), - nonce_authority: None, + nonce_authority: 0, destination_account_pubkey: nonce_account_pubkey, lamports: 42_000_000_000 }, - require_keypair: true + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); @@ -802,15 +850,20 @@ mod tests { "42", ]); assert_eq!( - parse_command(&test_withdraw_from_nonce_account).unwrap(), + parse_command( + &test_withdraw_from_nonce_account, + &default_keypair_file, + None + ) + .unwrap(), CliCommandInfo { command: CliCommand::WithdrawFromNonceAccount { nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(), - nonce_authority: None, + nonce_authority: 0, destination_account_pubkey: nonce_account_pubkey, lamports: 42000000000 }, - require_keypair: true + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); @@ -825,17 +878,23 @@ mod tests { &authority_keypair_file, ]); assert_eq!( - parse_command(&test_withdraw_from_nonce_account).unwrap(), + parse_command( + &test_withdraw_from_nonce_account, + &default_keypair_file, + None + ) + .unwrap(), CliCommandInfo { command: CliCommand::WithdrawFromNonceAccount { nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(), - nonce_authority: Some( - read_keypair_file(&authority_keypair_file).unwrap().into() - ), + nonce_authority: 1, destination_account_pubkey: nonce_account_pubkey, lamports: 42_000_000_000 }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&authority_keypair_file).unwrap().into() + ], } ); } diff --git a/cli/src/stake.rs b/cli/src/stake.rs index 9b40e6491f..e6cf47b445 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -1,8 +1,8 @@ use crate::{ cli::{ build_balance_message, check_account_for_fee, check_unique_pubkeys, fee_payer_arg, - log_instruction_custom_error, nonce_authority_arg, return_signers, CliCommand, - CliCommandInfo, CliConfig, CliError, ProcessResult, FEE_PAYER_ARG, + generate_unique_signers, log_instruction_custom_error, nonce_authority_arg, return_signers, + CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult, SignerIndex, FEE_PAYER_ARG, }, nonce::{check_nonce_account, nonce_arg, NONCE_ARG, NONCE_AUTHORITY_ARG}, offline::*, @@ -11,11 +11,11 @@ use clap::{App, Arg, ArgMatches, SubCommand}; use console::style; use solana_clap_utils::{input_parsers::*, input_validators::*, offline::*, ArgConstant}; use solana_client::rpc_client::RpcClient; +use solana_remote_wallet::remote_wallet::RemoteWalletManager; use solana_sdk::{ account_utils::StateMut, message::Message, pubkey::Pubkey, - signature::Signer, system_instruction::{create_address_with_seed, SystemError}, sysvar::{ stake_history::{self, StakeHistory}, @@ -28,7 +28,7 @@ use solana_stake_program::{ stake_state::{Authorized, Lockup, Meta, StakeAuthorize, StakeState}, }; use solana_vote_program::vote_state::VoteState; -use std::ops::Deref; +use std::{ops::Deref, sync::Arc}; pub const STAKE_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant { name: "stake_authority", @@ -411,27 +411,38 @@ impl StakeSubCommands for App<'_, '_> { } } -pub fn parse_stake_create_account(matches: &ArgMatches<'_>) -> Result { +pub fn parse_stake_create_account( + matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, +) -> Result { let seed = matches.value_of("seed").map(|s| s.to_string()); - 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 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 = lamports_of_sol(matches, "amount").unwrap(); 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 nonce_authority = signer_of(NONCE_AUTHORITY_ARG.name, matches)?; - let fee_payer = signer_of(FEE_PAYER_ARG.name, matches)?; - let from = signer_of("from", matches)?; - let stake_account = signer_of("stake_account", matches)?.unwrap(); + let nonce_account = pubkey_of(matches, NONCE_ARG.name); + 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 (from, from_pubkey) = signer_of(matches, "from", wallet_manager)?; + let (stake_account, stake_account_pubkey) = + signer_of(matches, "stake_account", wallet_manager)?; + + let mut bulk_signers = vec![fee_payer, from, stake_account]; + 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::CreateStakeAccount { - stake_account: stake_account.into(), + stake_account: signer_info.index_of(stake_account_pubkey).unwrap(), seed, staker, withdrawer, @@ -444,45 +455,58 @@ pub fn parse_stake_create_account(matches: &ArgMatches<'_>) -> Result) -> Result { +pub fn parse_stake_delegate_stake( + matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, +) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap(); let force = matches.is_present("force"); 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 = signer_of(STAKE_AUTHORITY_ARG.name, matches)?; - let nonce_authority = signer_of(NONCE_AUTHORITY_ARG.name, matches)?; - let fee_payer = signer_of(FEE_PAYER_ARG.name, 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::DelegateStake { stake_account_pubkey, vote_account_pubkey, - stake_authority, + stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(), force, sign_only, blockhash_query, nonce_account, - nonce_authority, - fee_payer, + nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), + fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), }, - require_keypair, + signers: signer_info.signers, }) } pub fn parse_stake_authorize( matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, stake_authorize: StakeAuthorize, ) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); @@ -492,129 +516,181 @@ pub fn parse_stake_authorize( StakeAuthorize::Withdrawer => WITHDRAW_AUTHORITY_ARG.name, }; let sign_only = matches.is_present(SIGN_ONLY_ARG.name); - let authority = signer_of(authority_flag, matches)?; + let (authority, authority_pubkey) = signer_of(matches, authority_flag, wallet_manager)?; let blockhash_query = BlockhashQuery::new_from_matches(matches); - let nonce_account = pubkey_of(&matches, NONCE_ARG.name); - let nonce_authority = signer_of(NONCE_AUTHORITY_ARG.name, matches)?; - let fee_payer = signer_of(FEE_PAYER_ARG.name, matches)?; + let nonce_account = pubkey_of(matches, NONCE_ARG.name); + 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![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::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey, stake_authorize, - authority, + authority: signer_info.index_of(authority_pubkey).unwrap(), sign_only, blockhash_query, nonce_account, - nonce_authority, - fee_payer, + nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), + fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), }, - require_keypair: true, + signers: signer_info.signers, }) } -pub fn parse_split_stake(matches: &ArgMatches<'_>) -> Result { +pub fn parse_split_stake( + matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, +) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); - let split_stake_account = signer_of("split_stake_account", matches)?.unwrap(); + let (split_stake_account, split_stake_account_pubkey) = + signer_of(matches, "split_stake_account", wallet_manager)?; let lamports = lamports_of_sol(matches, "amount").unwrap(); 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 = signer_of(STAKE_AUTHORITY_ARG.name, matches)?; - let nonce_authority = signer_of(NONCE_AUTHORITY_ARG.name, matches)?; - let fee_payer = signer_of(FEE_PAYER_ARG.name, 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, split_stake_account]; + 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::SplitStake { stake_account_pubkey, - stake_authority, + stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(), sign_only, blockhash_query, nonce_account, - nonce_authority, - split_stake_account: split_stake_account.into(), + nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), + split_stake_account: signer_info.index_of(split_stake_account_pubkey).unwrap(), seed, lamports, - fee_payer, + fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), }, - require_keypair, + signers: signer_info.signers, }) } -pub fn parse_stake_deactivate_stake(matches: &ArgMatches<'_>) -> Result { +pub fn parse_stake_deactivate_stake( + matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, +) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); 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 = signer_of(STAKE_AUTHORITY_ARG.name, matches)?; - let nonce_authority = signer_of(NONCE_AUTHORITY_ARG.name, matches)?; - let fee_payer = signer_of(FEE_PAYER_ARG.name, 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::DeactivateStake { stake_account_pubkey, - stake_authority, + stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(), sign_only, blockhash_query, nonce_account, - nonce_authority, - fee_payer, + nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), + fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), }, - require_keypair, + signers: signer_info.signers, }) } -pub fn parse_stake_withdraw_stake(matches: &ArgMatches<'_>) -> Result { +pub fn parse_stake_withdraw_stake( + matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, +) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); let destination_account_pubkey = pubkey_of(matches, "destination_account_pubkey").unwrap(); let lamports = lamports_of_sol(matches, "amount").unwrap(); 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 nonce_authority = signer_of(NONCE_AUTHORITY_ARG.name, matches)?; - let withdraw_authority = signer_of(WITHDRAW_AUTHORITY_ARG.name, matches)?; - let fee_payer = signer_of(FEE_PAYER_ARG.name, matches)?; + let nonce_account = pubkey_of(matches, NONCE_ARG.name); + let (withdraw_authority, withdraw_authority_pubkey) = + signer_of(matches, WITHDRAW_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![withdraw_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::WithdrawStake { stake_account_pubkey, destination_account_pubkey, lamports, - withdraw_authority, + withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(), sign_only, blockhash_query, nonce_account, - nonce_authority, - fee_payer, + nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(), + fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), }, - require_keypair, + signers: signer_info.signers, }) } -pub fn parse_stake_set_lockup(matches: &ArgMatches<'_>) -> Result { +pub fn parse_stake_set_lockup( + matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, +) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").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 epoch = value_of(matches, "lockup_epoch").unwrap_or(0); + let unix_timestamp = unix_timestamp_from_rfc3339_datetime(matches, "lockup_date").unwrap_or(0); let new_custodian = pubkey_of(matches, "new_custodian").unwrap_or_default(); 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 nonce_account = pubkey_of(matches, NONCE_ARG.name); - let custodian = signer_of("custodian", matches)?; - let nonce_authority = signer_of(NONCE_AUTHORITY_ARG.name, matches)?; - let fee_payer = signer_of(FEE_PAYER_ARG.name, matches)?; + let (custodian, custodian_pubkey) = signer_of(matches, "custodian", 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![custodian, 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::StakeSetLockup { @@ -624,14 +700,14 @@ pub fn parse_stake_set_lockup(matches: &ArgMatches<'_>) -> Result) -> Result) -> Result) -> Result, staker: &Option, withdrawer: &Option, @@ -668,16 +744,17 @@ pub fn process_create_stake_account( sign_only: bool, blockhash_query: &BlockhashQuery, nonce_account: Option<&Pubkey>, - nonce_authority: Option<&dyn Signer>, - fee_payer: Option<&dyn Signer>, - from: Option<&dyn Signer>, + nonce_authority: SignerIndex, + fee_payer: SignerIndex, + from: SignerIndex, ) -> ProcessResult { + let stake_account = config.signers[stake_account]; let stake_account_address = if let Some(seed) = seed { create_address_with_seed(&stake_account.pubkey(), &seed, &solana_stake_program::id())? } else { stake_account.pubkey() }; - let from = from.unwrap_or_else(|| config.keypair.as_ref()); + let from = config.signers[from]; check_unique_pubkeys( (&from.pubkey(), "from keypair".to_string()), (&stake_account_address, "stake_account".to_string()), @@ -735,31 +812,22 @@ pub fn process_create_stake_account( let (recent_blockhash, fee_calculator) = blockhash_query.get_blockhash_fee_calculator(rpc_client)?; - let fee_payer = fee_payer.unwrap_or_else(|| config.keypair.as_ref()); - let mut tx_signers = if stake_account.pubkey() != from.pubkey() { - vec![fee_payer, from, stake_account] // both must sign if `from` and `to` differ - } else { - vec![fee_payer, from] // when stake_account == config.keypair and there's a seed, we only need one signature - }; - let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); + let fee_payer = config.signers[fee_payer]; + let nonce_authority = config.signers[nonce_authority]; - let mut tx = if let Some(nonce_account) = &nonce_account { - tx_signers.push(nonce_authority); - let message = Message::new_with_nonce( + let message = if let Some(nonce_account) = &nonce_account { + Message::new_with_nonce( ixs, Some(&fee_payer.pubkey()), nonce_account, &nonce_authority.pubkey(), - ); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&tx_signers, recent_blockhash)?; - tx + ) } else { - let message = Message::new_with_payer(ixs, Some(&fee_payer.pubkey())); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&tx_signers, recent_blockhash)?; - tx + Message::new_with_payer(ixs, Some(&fee_payer.pubkey())) }; + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&config.signers, recent_blockhash)?; + if sign_only { return_signers(&tx) } else { @@ -773,7 +841,7 @@ pub fn process_create_stake_account( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &tx_signers); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); log_instruction_custom_error::(result) } } @@ -785,18 +853,18 @@ pub fn process_stake_authorize( stake_account_pubkey: &Pubkey, authorized_pubkey: &Pubkey, stake_authorize: StakeAuthorize, - authority: Option<&dyn Signer>, + authority: SignerIndex, sign_only: bool, blockhash_query: &BlockhashQuery, nonce_account: Option, - nonce_authority: Option<&dyn Signer>, - fee_payer: Option<&dyn Signer>, + nonce_authority: SignerIndex, + fee_payer: SignerIndex, ) -> ProcessResult { check_unique_pubkeys( (stake_account_pubkey, "stake_account_pubkey".to_string()), (authorized_pubkey, "new_authorized_pubkey".to_string()), )?; - let authority = authority.unwrap_or_else(|| config.keypair.as_ref()); + let authority = config.signers[authority]; let (recent_blockhash, fee_calculator) = blockhash_query.get_blockhash_fee_calculator(rpc_client)?; let ixs = vec![stake_instruction::authorize( @@ -806,24 +874,22 @@ pub fn process_stake_authorize( stake_authorize, // stake or withdraw )]; - let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); - let fee_payer = fee_payer.unwrap_or_else(|| config.keypair.as_ref()); - let mut tx = if let Some(nonce_account) = &nonce_account { - let message = Message::new_with_nonce( + let nonce_authority = config.signers[nonce_authority]; + let fee_payer = config.signers[fee_payer]; + + let message = if let Some(nonce_account) = &nonce_account { + Message::new_with_nonce( ixs, Some(&fee_payer.pubkey()), nonce_account, &nonce_authority.pubkey(), - ); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&[fee_payer, nonce_authority, authority], recent_blockhash)?; - tx + ) } else { - let message = Message::new_with_payer(ixs, Some(&fee_payer.pubkey())); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&[fee_payer, authority], recent_blockhash)?; - tx + Message::new_with_payer(ixs, Some(&fee_payer.pubkey())) }; + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&config.signers, recent_blockhash)?; + if sign_only { return_signers(&tx) } else { @@ -837,7 +903,7 @@ pub fn process_stake_authorize( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); log_instruction_custom_error::(result) } } @@ -847,41 +913,36 @@ pub fn process_deactivate_stake_account( rpc_client: &RpcClient, config: &CliConfig, stake_account_pubkey: &Pubkey, - stake_authority: Option<&dyn Signer>, + stake_authority: SignerIndex, sign_only: bool, blockhash_query: &BlockhashQuery, nonce_account: Option, - nonce_authority: Option<&dyn Signer>, - fee_payer: Option<&dyn Signer>, + nonce_authority: SignerIndex, + fee_payer: SignerIndex, ) -> ProcessResult { let (recent_blockhash, fee_calculator) = blockhash_query.get_blockhash_fee_calculator(rpc_client)?; - let stake_authority = stake_authority.unwrap_or_else(|| config.keypair.as_ref()); + let stake_authority = config.signers[stake_authority]; let ixs = vec![stake_instruction::deactivate_stake( stake_account_pubkey, &stake_authority.pubkey(), )]; - let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); - let fee_payer = fee_payer.unwrap_or_else(|| config.keypair.as_ref()); - let mut tx = if let Some(nonce_account) = &nonce_account { - let message = Message::new_with_nonce( + let nonce_authority = config.signers[nonce_authority]; + let fee_payer = config.signers[fee_payer]; + + let message = if let Some(nonce_account) = &nonce_account { + Message::new_with_nonce( ixs, Some(&fee_payer.pubkey()), nonce_account, &nonce_authority.pubkey(), - ); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign( - &[fee_payer, nonce_authority, stake_authority], - recent_blockhash, - )?; - tx + ) } else { - let message = Message::new_with_payer(ixs, Some(&fee_payer.pubkey())); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&[fee_payer, stake_authority], recent_blockhash)?; - tx + Message::new_with_payer(ixs, Some(&fee_payer.pubkey())) }; + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&config.signers, recent_blockhash)?; + if sign_only { return_signers(&tx) } else { @@ -895,7 +956,7 @@ pub fn process_deactivate_stake_account( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); log_instruction_custom_error::(result) } } @@ -907,16 +968,16 @@ pub fn process_withdraw_stake( stake_account_pubkey: &Pubkey, destination_account_pubkey: &Pubkey, lamports: u64, - withdraw_authority: Option<&dyn Signer>, + withdraw_authority: SignerIndex, sign_only: bool, blockhash_query: &BlockhashQuery, nonce_account: Option<&Pubkey>, - nonce_authority: Option<&dyn Signer>, - fee_payer: Option<&dyn Signer>, + nonce_authority: SignerIndex, + fee_payer: SignerIndex, ) -> ProcessResult { let (recent_blockhash, fee_calculator) = blockhash_query.get_blockhash_fee_calculator(rpc_client)?; - let withdraw_authority = withdraw_authority.unwrap_or_else(|| config.keypair.as_ref()); + let withdraw_authority = config.signers[withdraw_authority]; let ixs = vec![stake_instruction::withdraw( stake_account_pubkey, @@ -925,27 +986,22 @@ pub fn process_withdraw_stake( lamports, )]; - let fee_payer = fee_payer.unwrap_or_else(|| config.keypair.as_ref()); - let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); - let mut tx = if let Some(nonce_account) = &nonce_account { - let message = Message::new_with_nonce( + let fee_payer = config.signers[fee_payer]; + 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(), - ); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign( - &[fee_payer, withdraw_authority, nonce_authority], - recent_blockhash, - )?; - tx + ) } else { - let message = Message::new_with_payer(ixs, Some(&fee_payer.pubkey())); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&[fee_payer, withdraw_authority], recent_blockhash)?; - tx + Message::new_with_payer(ixs, Some(&fee_payer.pubkey())) }; + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&config.signers, recent_blockhash)?; + if sign_only { return_signers(&tx) } else { @@ -959,7 +1015,7 @@ pub fn process_withdraw_stake( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); log_instruction_custom_error::(result) } } @@ -969,17 +1025,18 @@ pub fn process_split_stake( rpc_client: &RpcClient, config: &CliConfig, stake_account_pubkey: &Pubkey, - stake_authority: Option<&dyn Signer>, + stake_authority: SignerIndex, sign_only: bool, blockhash_query: &BlockhashQuery, nonce_account: Option, - nonce_authority: Option<&dyn Signer>, - split_stake_account: &dyn Signer, + nonce_authority: SignerIndex, + split_stake_account: SignerIndex, split_stake_account_seed: &Option, lamports: u64, - fee_payer: Option<&dyn Signer>, + fee_payer: SignerIndex, ) -> ProcessResult { - let fee_payer = fee_payer.unwrap_or_else(|| config.keypair.as_ref()); + let split_stake_account = config.signers[split_stake_account]; + let fee_payer = config.signers[fee_payer]; check_unique_pubkeys( (&fee_payer.pubkey(), "fee-payer keypair".to_string()), ( @@ -999,7 +1056,7 @@ pub fn process_split_stake( ), )?; - let stake_authority = stake_authority.unwrap_or_else(|| config.keypair.as_ref()); + let stake_authority = config.signers[stake_authority]; let split_stake_account_address = if let Some(seed) = split_stake_account_seed { create_address_with_seed( @@ -1060,35 +1117,21 @@ pub fn process_split_stake( ) }; - let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); + let nonce_authority = config.signers[nonce_authority]; - let mut tx = if let Some(nonce_account) = &nonce_account { - let message = Message::new_with_nonce( + let message = if let Some(nonce_account) = &nonce_account { + Message::new_with_nonce( ixs, Some(&fee_payer.pubkey()), nonce_account, &nonce_authority.pubkey(), - ); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign( - &[ - fee_payer, - nonce_authority, - stake_authority, - split_stake_account, - ], - recent_blockhash, - )?; - tx + ) } else { - let message = Message::new_with_payer(ixs, Some(&fee_payer.pubkey())); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign( - &[fee_payer, stake_authority, split_stake_account], - recent_blockhash, - )?; - tx + Message::new_with_payer(ixs, Some(&fee_payer.pubkey())) }; + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&config.signers, recent_blockhash)?; + if sign_only { return_signers(&tx) } else { @@ -1102,7 +1145,7 @@ pub fn process_split_stake( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); log_instruction_custom_error::(result) } } @@ -1113,16 +1156,16 @@ pub fn process_stake_set_lockup( config: &CliConfig, stake_account_pubkey: &Pubkey, lockup: &mut Lockup, - custodian: Option<&dyn Signer>, + custodian: SignerIndex, sign_only: bool, blockhash_query: &BlockhashQuery, nonce_account: Option, - nonce_authority: Option<&dyn Signer>, - fee_payer: Option<&dyn Signer>, + nonce_authority: SignerIndex, + fee_payer: SignerIndex, ) -> ProcessResult { let (recent_blockhash, fee_calculator) = blockhash_query.get_blockhash_fee_calculator(rpc_client)?; - let custodian = custodian.unwrap_or_else(|| config.keypair.as_ref()); + let custodian = config.signers[custodian]; // If new custodian is not explicitly set, default to current custodian if lockup.custodian == Pubkey::default() { lockup.custodian = custodian.pubkey(); @@ -1132,24 +1175,22 @@ pub fn process_stake_set_lockup( lockup, &custodian.pubkey(), )]; - let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); - let fee_payer = fee_payer.unwrap_or_else(|| config.keypair.as_ref()); - let mut tx = if let Some(nonce_account) = &nonce_account { - let message = Message::new_with_nonce( + let nonce_authority = config.signers[nonce_authority]; + let fee_payer = config.signers[fee_payer]; + + let message = if let Some(nonce_account) = &nonce_account { + Message::new_with_nonce( ixs, Some(&fee_payer.pubkey()), nonce_account, &nonce_authority.pubkey(), - ); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&[fee_payer, nonce_authority, custodian], recent_blockhash)?; - tx + ) } else { - let message = Message::new_with_payer(ixs, Some(&fee_payer.pubkey())); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&[fee_payer, custodian], recent_blockhash)?; - tx + Message::new_with_payer(ixs, Some(&fee_payer.pubkey())) }; + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&config.signers, recent_blockhash)?; + if sign_only { return_signers(&tx) } else { @@ -1163,7 +1204,7 @@ pub fn process_stake_set_lockup( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); log_instruction_custom_error::(result) } } @@ -1295,19 +1336,19 @@ pub fn process_delegate_stake( config: &CliConfig, stake_account_pubkey: &Pubkey, vote_account_pubkey: &Pubkey, - stake_authority: Option<&dyn Signer>, + stake_authority: SignerIndex, force: bool, sign_only: bool, blockhash_query: &BlockhashQuery, nonce_account: Option, - nonce_authority: Option<&dyn Signer>, - fee_payer: Option<&dyn Signer>, + nonce_authority: SignerIndex, + fee_payer: SignerIndex, ) -> ProcessResult { check_unique_pubkeys( - (&config.keypair.pubkey(), "cli keypair".to_string()), + (&config.signers[0].pubkey(), "cli keypair".to_string()), (stake_account_pubkey, "stake_account_pubkey".to_string()), )?; - let stake_authority = stake_authority.unwrap_or_else(|| config.keypair.as_ref()); + let stake_authority = config.signers[stake_authority]; if !sign_only { // Sanity check the vote account to ensure it is attached to a validator that has recently @@ -1362,27 +1403,22 @@ pub fn process_delegate_stake( &stake_authority.pubkey(), vote_account_pubkey, )]; - let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.as_ref()); - let fee_payer = fee_payer.unwrap_or_else(|| config.keypair.as_ref()); - let mut tx = if let Some(nonce_account) = &nonce_account { - let message = Message::new_with_nonce( + let nonce_authority = config.signers[nonce_authority]; + let fee_payer = config.signers[fee_payer]; + + let message = if let Some(nonce_account) = &nonce_account { + Message::new_with_nonce( ixs, Some(&fee_payer.pubkey()), nonce_account, &nonce_authority.pubkey(), - ); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign( - &[fee_payer, nonce_authority, stake_authority], - recent_blockhash, - )?; - tx + ) } else { - let message = Message::new_with_payer(ixs, Some(&fee_payer.pubkey())); - let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&[fee_payer, stake_authority], recent_blockhash)?; - tx + Message::new_with_payer(ixs, Some(&fee_payer.pubkey())) }; + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&config.signers, recent_blockhash)?; + if sign_only { return_signers(&tx) } else { @@ -1396,7 +1432,7 @@ pub fn process_delegate_stake( &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); log_instruction_custom_error::(result) } } @@ -1412,7 +1448,6 @@ mod tests { keypair_from_seed, read_keypair_file, write_keypair, Keypair, Presigner, Signer, }, }; - use std::rc::Rc; use tempfile::NamedTempFile; fn make_tmp_file() -> (String, NamedTempFile) { @@ -1426,6 +1461,10 @@ mod tests { authority_keypair_file: &str, stake_authorize: StakeAuthorize, ) { + let default_keypair = Keypair::new(); + let (default_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); + let stake_account_string = stake_account_pubkey.to_string(); let (subcommand, authority_flag) = match stake_authorize { @@ -1441,20 +1480,20 @@ mod tests { &stake_account_string, ]); assert_eq!( - parse_command(&test_authorize).unwrap(), + parse_command(&test_authorize, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: stake_account_pubkey, stake_authorize, - authority: None, + authority: 0, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); // Test Staker Subcommand w/ authority @@ -1467,20 +1506,23 @@ mod tests { &authority_keypair_file, ]); assert_eq!( - parse_command(&test_authorize).unwrap(), + parse_command(&test_authorize, &default_keypair_file, None).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()), + authority: 1, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&authority_keypair_file).unwrap().into() + ], } ); // Test Authorize Subcommand w/ sign-only @@ -1496,20 +1538,20 @@ mod tests { "--sign-only", ]); assert_eq!( - parse_command(&test_authorize).unwrap(), + parse_command(&test_authorize, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: stake_account_pubkey, stake_authorize, - authority: None, + authority: 0, sign_only: true, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); // Test Authorize Subcommand w/ offline feepayer @@ -1530,20 +1572,23 @@ mod tests { &pubkey.to_string(), ]); assert_eq!( - parse_command(&test_authorize).unwrap(), + parse_command(&test_authorize, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: stake_account_pubkey, stake_authorize, - authority: None, + authority: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, - nonce_authority: None, - fee_payer: Some(Presigner::new(&pubkey, &sig).into()), + nonce_authority: 0, + fee_payer: 1, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + Presigner::new(&pubkey, &sig).into() + ], } ); // Test Authorize Subcommand w/ offline fee payer and nonce authority @@ -1571,20 +1616,24 @@ mod tests { &pubkey2.to_string(), ]); assert_eq!( - parse_command(&test_authorize).unwrap(), + parse_command(&test_authorize, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: stake_account_pubkey, stake_authorize, - authority: None, + authority: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_account), - nonce_authority: Some(Presigner::new(&pubkey2, &sig2).into()), - fee_payer: Some(Presigner::new(&pubkey, &sig).into()), + nonce_authority: 2, + fee_payer: 1, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + Presigner::new(&pubkey, &sig).into(), + Presigner::new(&pubkey2, &sig2).into(), + ], } ); // Test Authorize Subcommand w/ blockhash @@ -1597,20 +1646,20 @@ mod tests { &blockhash_string, ]); assert_eq!( - parse_command(&test_authorize).unwrap(), + parse_command(&test_authorize, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: stake_account_pubkey, stake_authorize, - authority: None, + authority: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); // Test Authorize Subcommand w/ nonce @@ -1632,20 +1681,23 @@ mod tests { &nonce_keypair_file, ]); assert_eq!( - parse_command(&test_authorize).unwrap(), + parse_command(&test_authorize, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: stake_account_pubkey, stake_authorize, - authority: None, + authority: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_account_pubkey), - nonce_authority: Some(nonce_authority_keypair.into()), - fee_payer: None, + nonce_authority: 1, + fee_payer: 0, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + nonce_authority_keypair.into() + ], } ); // Test Authorize Subcommand w/ fee-payer @@ -1663,20 +1715,23 @@ mod tests { &fee_payer_keypair_file, ]); assert_eq!( - parse_command(&test_authorize).unwrap(), + parse_command(&test_authorize, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: stake_account_pubkey, stake_authorize, - authority: None, + authority: 0, sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: Some(read_keypair_file(&fee_payer_keypair_file).unwrap().into()), + nonce_authority: 0, + fee_payer: 1, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&fee_payer_keypair_file).unwrap().into(), + ], } ); // Test Authorize Subcommand w/ absentee fee-payer @@ -1695,20 +1750,23 @@ mod tests { &signer, ]); assert_eq!( - parse_command(&test_authorize).unwrap(), + parse_command(&test_authorize, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: stake_account_pubkey, stake_authorize, - authority: None, + authority: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, - nonce_authority: None, - fee_payer: Some(Presigner::new(&fee_payer_pubkey, &sig).into()), + nonce_authority: 0, + fee_payer: 1, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + Presigner::new(&fee_payer_pubkey, &sig).into() + ], } ); } @@ -1716,6 +1774,9 @@ mod tests { #[test] fn test_parse_command() { let test_commands = app("test", "desc", "version"); + let default_keypair = Keypair::new(); + let (default_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); 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(); @@ -1757,10 +1818,10 @@ mod tests { "43", ]); assert_eq!( - parse_command(&test_create_stake_account).unwrap(), + parse_command(&test_create_stake_account, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::CreateStakeAccount { - stake_account: Rc::new(stake_account_keypair.into()), + stake_account: 1, seed: None, staker: Some(authorized), withdrawer: Some(authorized), @@ -1773,11 +1834,14 @@ mod tests { sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: None, - from: None, + nonce_authority: 0, + fee_payer: 0, + from: 0, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + stake_account_keypair.into() + ], } ); @@ -1795,10 +1859,10 @@ mod tests { ]); assert_eq!( - parse_command(&test_create_stake_account2).unwrap(), + parse_command(&test_create_stake_account2, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::CreateStakeAccount { - stake_account: Rc::new(read_keypair_file(&keypair_file).unwrap().into()), + stake_account: 1, seed: None, staker: None, withdrawer: None, @@ -1807,11 +1871,14 @@ mod tests { sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: None, - from: None, + nonce_authority: 0, + fee_payer: 0, + from: 0, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&keypair_file).unwrap().into() + ], } ); @@ -1845,10 +1912,10 @@ mod tests { ]); assert_eq!( - parse_command(&test_create_stake_account2).unwrap(), + parse_command(&test_create_stake_account2, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::CreateStakeAccount { - stake_account: Rc::new(read_keypair_file(&keypair_file).unwrap().into()), + stake_account: 1, seed: None, staker: None, withdrawer: None, @@ -1857,11 +1924,14 @@ mod tests { sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(nonce_hash), nonce_account: Some(nonce_account), - nonce_authority: Some(Presigner::new(&offline_pubkey, &offline_sig).into()), - fee_payer: Some(Presigner::new(&offline_pubkey, &offline_sig).into()), - from: Some(Presigner::new(&offline_pubkey, &offline_sig).into()), + nonce_authority: 0, + fee_payer: 0, + from: 0, }, - require_keypair: false, + signers: vec![ + Presigner::new(&offline_pubkey, &offline_sig).into(), + read_keypair_file(&keypair_file).unwrap().into() + ], } ); @@ -1875,20 +1945,20 @@ mod tests { &vote_account_string, ]); assert_eq!( - parse_command(&test_delegate_stake).unwrap(), + parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, - stake_authority: None, + stake_authority: 0, force: false, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); @@ -1904,24 +1974,25 @@ mod tests { &stake_authority_keypair_file, ]); assert_eq!( - parse_command(&test_delegate_stake).unwrap(), + parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, - stake_authority: Some( - read_keypair_file(&stake_authority_keypair_file) - .unwrap() - .into() - ), + stake_authority: 1, force: false, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&stake_authority_keypair_file) + .unwrap() + .into() + ], } ); @@ -1934,20 +2005,20 @@ mod tests { &vote_account_string, ]); assert_eq!( - parse_command(&test_delegate_stake).unwrap(), + parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, - stake_authority: None, + stake_authority: 0, force: true, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); @@ -1963,20 +2034,20 @@ mod tests { &blockhash_string, ]); assert_eq!( - parse_command(&test_delegate_stake).unwrap(), + parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, - stake_authority: None, + stake_authority: 0, force: false, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); @@ -1990,20 +2061,20 @@ mod tests { "--sign-only", ]); assert_eq!( - parse_command(&test_delegate_stake).unwrap(), + parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, - stake_authority: None, + stake_authority: 0, force: false, sign_only: true, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); @@ -2024,20 +2095,23 @@ mod tests { &key1.to_string(), ]); assert_eq!( - parse_command(&test_delegate_stake).unwrap(), + parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, - stake_authority: None, + stake_authority: 0, force: false, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, - nonce_authority: None, - fee_payer: Some(Presigner::new(&key1, &sig1).into()), + nonce_authority: 0, + fee_payer: 1, }, - require_keypair: false + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + Presigner::new(&key1, &sig1).into() + ], } ); @@ -2064,20 +2138,24 @@ mod tests { &key2.to_string(), ]); assert_eq!( - parse_command(&test_delegate_stake).unwrap(), + parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, - stake_authority: None, + stake_authority: 0, force: false, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_account), - nonce_authority: Some(Presigner::new(&key2, &sig2).into()), - fee_payer: Some(Presigner::new(&key1, &sig1).into()), + nonce_authority: 2, + fee_payer: 1, }, - require_keypair: false + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + Presigner::new(&key1, &sig1).into(), + Presigner::new(&key2, &sig2).into(), + ], } ); @@ -2094,20 +2172,23 @@ mod tests { &fee_payer_keypair_file, ]); assert_eq!( - parse_command(&test_delegate_stake).unwrap(), + parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, - stake_authority: None, + stake_authority: 0, force: false, sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: Some(read_keypair_file(&fee_payer_keypair_file).unwrap().into()), + nonce_authority: 0, + fee_payer: 1, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&fee_payer_keypair_file).unwrap().into() + ], } ); @@ -2121,20 +2202,20 @@ mod tests { ]); assert_eq!( - parse_command(&test_withdraw_stake).unwrap(), + parse_command(&test_withdraw_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::WithdrawStake { stake_account_pubkey, destination_account_pubkey: stake_account_pubkey, lamports: 42_000_000_000, - withdraw_authority: None, + withdraw_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); @@ -2150,24 +2231,25 @@ mod tests { ]); assert_eq!( - parse_command(&test_withdraw_stake).unwrap(), + parse_command(&test_withdraw_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::WithdrawStake { stake_account_pubkey, destination_account_pubkey: stake_account_pubkey, lamports: 42_000_000_000, - withdraw_authority: Some( - read_keypair_file(&stake_authority_keypair_file) - .unwrap() - .into() - ), + withdraw_authority: 1, sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&stake_authority_keypair_file) + .unwrap() + .into() + ], } ); @@ -2193,24 +2275,25 @@ mod tests { ]); assert_eq!( - parse_command(&test_withdraw_stake).unwrap(), + parse_command(&test_withdraw_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::WithdrawStake { stake_account_pubkey, destination_account_pubkey: stake_account_pubkey, lamports: 42_000_000_000, - withdraw_authority: Some( - read_keypair_file(&stake_authority_keypair_file) - .unwrap() - .into() - ), + withdraw_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(nonce_hash), nonce_account: Some(nonce_account), - nonce_authority: Some(Presigner::new(&offline_pubkey, &offline_sig).into()), - fee_payer: Some(Presigner::new(&offline_pubkey, &offline_sig).into()), + nonce_authority: 1, + fee_payer: 1, }, - require_keypair: false, + signers: vec![ + read_keypair_file(&stake_authority_keypair_file) + .unwrap() + .into(), + Presigner::new(&offline_pubkey, &offline_sig).into() + ], } ); @@ -2221,18 +2304,18 @@ mod tests { &stake_account_string, ]); assert_eq!( - parse_command(&test_deactivate_stake).unwrap(), + parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::DeactivateStake { stake_account_pubkey, - stake_authority: None, + stake_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); @@ -2245,22 +2328,23 @@ mod tests { &stake_authority_keypair_file, ]); assert_eq!( - parse_command(&test_deactivate_stake).unwrap(), + parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::DeactivateStake { stake_account_pubkey, - stake_authority: Some( - read_keypair_file(&stake_authority_keypair_file) - .unwrap() - .into() - ), + stake_authority: 1, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&stake_authority_keypair_file) + .unwrap() + .into() + ], } ); @@ -2275,18 +2359,18 @@ mod tests { &blockhash_string, ]); assert_eq!( - parse_command(&test_deactivate_stake).unwrap(), + parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::DeactivateStake { stake_account_pubkey, - stake_authority: None, + stake_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); @@ -2299,18 +2383,18 @@ mod tests { "--sign-only", ]); assert_eq!( - parse_command(&test_deactivate_stake).unwrap(), + parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::DeactivateStake { stake_account_pubkey, - stake_authority: None, + stake_authority: 0, sign_only: true, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }, - require_keypair: true + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); @@ -2330,18 +2414,21 @@ mod tests { &key1.to_string(), ]); assert_eq!( - parse_command(&test_deactivate_stake).unwrap(), + parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::DeactivateStake { stake_account_pubkey, - stake_authority: None, + stake_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, - nonce_authority: None, - fee_payer: Some(Presigner::new(&key1, &sig1).into()), + nonce_authority: 0, + fee_payer: 1, }, - require_keypair: false + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + Presigner::new(&key1, &sig1).into() + ], } ); @@ -2367,18 +2454,22 @@ mod tests { &key2.to_string(), ]); assert_eq!( - parse_command(&test_deactivate_stake).unwrap(), + parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::DeactivateStake { stake_account_pubkey, - stake_authority: None, + stake_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_account), - nonce_authority: Some(Presigner::new(&key2, &sig2).into()), - fee_payer: Some(Presigner::new(&key1, &sig1).into()), + nonce_authority: 2, + fee_payer: 1, }, - require_keypair: false + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + Presigner::new(&key1, &sig1).into(), + Presigner::new(&key2, &sig2).into(), + ], } ); @@ -2391,18 +2482,21 @@ mod tests { &fee_payer_keypair_file, ]); assert_eq!( - parse_command(&test_deactivate_stake).unwrap(), + parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::DeactivateStake { stake_account_pubkey, - stake_authority: None, + stake_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: Some(read_keypair_file(&fee_payer_keypair_file).unwrap().into()), + nonce_authority: 0, + fee_payer: 1, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&fee_payer_keypair_file).unwrap().into() + ], } ); @@ -2422,25 +2516,26 @@ mod tests { "50", ]); assert_eq!( - parse_command(&test_split_stake_account).unwrap(), + parse_command(&test_split_stake_account, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::SplitStake { stake_account_pubkey: stake_account_keypair.pubkey(), - stake_authority: None, + stake_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - split_stake_account: Rc::new( - read_keypair_file(&split_stake_account_keypair_file) - .unwrap() - .into(), - ), + nonce_authority: 0, + split_stake_account: 1, seed: None, lamports: 50_000_000_000, - fee_payer: None, + fee_payer: 0, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + read_keypair_file(&split_stake_account_keypair_file) + .unwrap() + .into() + ], } ); @@ -2482,25 +2577,27 @@ mod tests { &stake_signer, ]); assert_eq!( - parse_command(&test_split_stake_account).unwrap(), + parse_command(&test_split_stake_account, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::SplitStake { stake_account_pubkey: stake_account_keypair.pubkey(), - stake_authority: Some(Presigner::new(&stake_auth_pubkey, &stake_sig).into()), + stake_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(nonce_hash), nonce_account: Some(nonce_account.into()), - nonce_authority: Some(Presigner::new(&nonce_auth_pubkey, &nonce_sig).into()), - split_stake_account: Rc::new( - read_keypair_file(&split_stake_account_keypair_file) - .unwrap() - .into(), - ), + nonce_authority: 1, + split_stake_account: 2, seed: None, lamports: 50_000_000_000, - fee_payer: Some(Presigner::new(&nonce_auth_pubkey, &nonce_sig).into()), + fee_payer: 1, }, - require_keypair: false, + signers: vec![ + Presigner::new(&stake_auth_pubkey, &stake_sig).into(), + Presigner::new(&nonce_auth_pubkey, &nonce_sig).into(), + read_keypair_file(&split_stake_account_keypair_file) + .unwrap() + .into(), + ], } ); } diff --git a/cli/src/storage.rs b/cli/src/storage.rs index 3ee2edf8d8..faa01081dd 100644 --- a/cli/src/storage.rs +++ b/cli/src/storage.rs @@ -1,16 +1,17 @@ use crate::cli::{ check_account_for_fee, check_unique_pubkeys, log_instruction_custom_error, CliCommand, - CliCommandInfo, CliConfig, CliError, ProcessResult, + CliCommandInfo, CliConfig, CliError, ProcessResult, SignerIndex, }; use clap::{App, Arg, ArgMatches, SubCommand}; -use solana_clap_utils::{input_parsers::*, input_validators::*}; +use solana_clap_utils::{input_parsers::*, input_validators::*, keypair::signer_from_path}; use solana_client::rpc_client::RpcClient; -use solana_sdk::signature::Keypair; +use solana_remote_wallet::remote_wallet::RemoteWalletManager; use solana_sdk::{ - account_utils::StateMut, message::Message, pubkey::Pubkey, signature::Signer, - system_instruction::SystemError, transaction::Transaction, + account_utils::StateMut, message::Message, pubkey::Pubkey, system_instruction::SystemError, + transaction::Transaction, }; use solana_storage_program::storage_instruction::{self, StorageAccountType}; +use std::sync::Arc; pub trait StorageSubCommands { fn storage_subcommands(self) -> Self; @@ -99,35 +100,49 @@ impl StorageSubCommands for App<'_, '_> { pub fn parse_storage_create_archiver_account( matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, ) -> Result { let account_owner = pubkey_of(matches, "storage_account_owner").unwrap(); let storage_account = keypair_of(matches, "storage_account").unwrap(); Ok(CliCommandInfo { command: CliCommand::CreateStorageAccount { account_owner, - storage_account: storage_account.into(), + storage_account: 1, account_type: StorageAccountType::Archiver, }, - require_keypair: true, + signers: vec![ + signer_from_path(matches, default_signer_path, "keypair", wallet_manager)?, + storage_account.into(), + ], }) } pub fn parse_storage_create_validator_account( matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, ) -> Result { let account_owner = pubkey_of(matches, "storage_account_owner").unwrap(); let storage_account = keypair_of(matches, "storage_account").unwrap(); Ok(CliCommandInfo { command: CliCommand::CreateStorageAccount { account_owner, - storage_account: storage_account.into(), + storage_account: 1, account_type: StorageAccountType::Validator, }, - require_keypair: true, + signers: vec![ + signer_from_path(matches, default_signer_path, "keypair", wallet_manager)?, + storage_account.into(), + ], }) } -pub fn parse_storage_claim_reward(matches: &ArgMatches<'_>) -> Result { +pub fn parse_storage_claim_reward( + matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, +) -> Result { let node_account_pubkey = pubkey_of(matches, "node_account_pubkey").unwrap(); let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap(); Ok(CliCommandInfo { @@ -135,7 +150,12 @@ pub fn parse_storage_claim_reward(matches: &ArgMatches<'_>) -> Result ProcessResult { + let storage_account = config.signers[storage_account]; let storage_account_pubkey = storage_account.pubkey(); check_unique_pubkeys( - (&config.keypair.pubkey(), "cli keypair".to_string()), + (&config.signers[0].pubkey(), "cli keypair".to_string()), ( &storage_account_pubkey, "storage_account_pubkey".to_string(), @@ -183,7 +204,7 @@ pub fn process_create_storage_account( .max(1); let ixs = storage_instruction::create_storage_account( - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &account_owner, &storage_account_pubkey, required_balance, @@ -193,18 +214,14 @@ pub fn process_create_storage_account( let message = Message::new(ixs); let mut tx = Transaction::new_unsigned(message); - tx.try_sign( - &[config.keypair.as_ref(), storage_account], - recent_blockhash, - )?; + tx.try_sign(&config.signers, recent_blockhash)?; check_account_for_fee( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &tx.message, )?; - let result = rpc_client - .send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref(), storage_account]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); log_instruction_custom_error::(result) } @@ -218,13 +235,13 @@ pub fn process_claim_storage_reward( let instruction = storage_instruction::claim_reward(node_account_pubkey, storage_account_pubkey); - let signers = [config.keypair.as_ref()]; + let signers = [config.signers[0]]; let message = Message::new_with_payer(vec![instruction], Some(&signers[0].pubkey())); let mut tx = Transaction::new_unsigned(message); tx.try_sign(&signers, recent_blockhash)?; check_account_for_fee( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &tx.message, )?; @@ -260,7 +277,7 @@ pub fn process_show_storage_account( mod tests { use super::*; use crate::cli::{app, parse_command}; - use solana_sdk::signature::write_keypair; + use solana_sdk::signature::{read_keypair_file, write_keypair, Keypair, Signer}; use tempfile::NamedTempFile; fn make_tmp_file() -> (String, NamedTempFile) { @@ -274,6 +291,10 @@ mod tests { let pubkey = Pubkey::new_rand(); let pubkey_string = pubkey.to_string(); + let default_keypair = Keypair::new(); + let (default_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); + let (keypair_file, mut tmp_file) = make_tmp_file(); let storage_account_keypair = Keypair::new(); write_keypair(&storage_account_keypair, tmp_file.as_file_mut()).unwrap(); @@ -285,14 +306,22 @@ mod tests { &keypair_file, ]); assert_eq!( - parse_command(&test_create_archiver_storage_account).unwrap(), + parse_command( + &test_create_archiver_storage_account, + &default_keypair_file, + None + ) + .unwrap(), CliCommandInfo { command: CliCommand::CreateStorageAccount { account_owner: pubkey, - storage_account: storage_account_keypair.into(), + storage_account: 1, account_type: StorageAccountType::Archiver, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + storage_account_keypair.into() + ], } ); @@ -309,14 +338,22 @@ mod tests { &keypair_file, ]); assert_eq!( - parse_command(&test_create_validator_storage_account).unwrap(), + parse_command( + &test_create_validator_storage_account, + &default_keypair_file, + None + ) + .unwrap(), CliCommandInfo { command: CliCommand::CreateStorageAccount { account_owner: pubkey, - storage_account: storage_account_keypair.into(), + storage_account: 1, account_type: StorageAccountType::Validator, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + storage_account_keypair.into() + ], } ); @@ -327,13 +364,13 @@ mod tests { &storage_account_string, ]); assert_eq!( - parse_command(&test_claim_storage_reward).unwrap(), + parse_command(&test_claim_storage_reward, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::ClaimStorageReward { node_account_pubkey: pubkey, storage_account_pubkey, }, - require_keypair: true + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); } diff --git a/cli/src/validator_info.rs b/cli/src/validator_info.rs index 3dde9cb1d2..c902758eaa 100644 --- a/cli/src/validator_info.rs +++ b/cli/src/validator_info.rs @@ -11,9 +11,11 @@ use serde_json::{Map, Value}; use solana_clap_utils::{ input_parsers::pubkey_of, input_validators::{is_pubkey, is_url}, + keypair::signer_from_path, }; use solana_client::rpc_client::RpcClient; use solana_config_program::{config_instruction, get_config_data, ConfigKeys, ConfigState}; +use solana_remote_wallet::remote_wallet::RemoteWalletManager; use solana_sdk::{ account::Account, commitment_config::CommitmentConfig, @@ -22,7 +24,7 @@ use solana_sdk::{ signature::{Keypair, Signer}, transaction::Transaction, }; -use std::error; +use std::{error, sync::Arc}; use titlecase::titlecase; pub const MAX_SHORT_FIELD_LENGTH: usize = 70; @@ -224,17 +226,26 @@ impl ValidatorInfoSubCommands for App<'_, '_> { } } -pub fn parse_validator_info_command(matches: &ArgMatches<'_>) -> Result { +pub fn parse_validator_info_command( + matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, +) -> Result { let info_pubkey = pubkey_of(matches, "info_pubkey"); // Prepare validator info - let validator_info = parse_args(&matches); + let validator_info = parse_args(matches); Ok(CliCommandInfo { command: CliCommand::SetValidatorInfo { validator_info, force_keybase: matches.is_present("force"), info_pubkey, }, - require_keypair: true, + signers: vec![signer_from_path( + matches, + default_signer_path, + "keypair", + wallet_manager, + )?], }) } @@ -244,7 +255,7 @@ pub fn parse_get_validator_info_command( let info_pubkey = pubkey_of(matches, "info_pubkey"); Ok(CliCommandInfo { command: CliCommand::GetValidatorInfo(info_pubkey), - require_keypair: false, + signers: vec![], }) } @@ -257,7 +268,7 @@ pub fn process_set_validator_info( ) -> ProcessResult { // Validate keybase username if let Some(string) = validator_info.get("keybaseUsername") { - let result = verify_keybase(&config.keypair.pubkey(), &string); + let result = verify_keybase(&config.signers[0].pubkey(), &string); if result.is_err() { if force_keybase { println!("--force supplied, ignoring: {:?}", result); @@ -282,7 +293,7 @@ pub fn process_set_validator_info( }) .find(|(pubkey, account)| { let (validator_pubkey, _) = parse_validator_info(&pubkey, &account).unwrap(); - validator_pubkey == config.keypair.pubkey() + validator_pubkey == config.signers[0].pubkey() }); // Create validator-info keypair to use if info_pubkey not provided or does not exist @@ -300,7 +311,7 @@ pub fn process_set_validator_info( .poll_get_balance_with_commitment(&info_pubkey, CommitmentConfig::default()) .unwrap_or(0); - let keys = vec![(id(), false), (config.keypair.pubkey(), true)]; + let keys = vec![(id(), false), (config.signers[0].pubkey(), true)]; let (message, signers): (Message, Vec<&dyn Signer>) = if balance == 0 { if info_pubkey != info_keypair.pubkey() { println!( @@ -311,12 +322,12 @@ pub fn process_set_validator_info( } println!( "Publishing info for Validator {:?}", - config.keypair.pubkey() + config.signers[0].pubkey() ); let lamports = rpc_client .get_minimum_balance_for_rent_exemption(ValidatorInfo::max_space() as usize)?; let mut instructions = config_instruction::create_account::( - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &info_keypair.pubkey(), lamports, keys.clone(), @@ -327,13 +338,13 @@ pub fn process_set_validator_info( keys, &validator_info, )]); - let signers = vec![config.keypair.as_ref(), &info_keypair]; + let signers = vec![config.signers[0], &info_keypair]; let message = Message::new(instructions); (message, signers) } else { println!( "Updating Validator {:?} info at: {:?}", - config.keypair.pubkey(), + config.signers[0].pubkey(), info_pubkey ); let instructions = vec![config_instruction::store( @@ -342,8 +353,8 @@ pub fn process_set_validator_info( keys, &validator_info, )]; - let message = Message::new_with_payer(instructions, Some(&config.keypair.pubkey())); - let signers = vec![config.keypair.as_ref()]; + let message = Message::new_with_payer(instructions, Some(&config.signers[0].pubkey())); + let signers = vec![config.signers[0]]; (message, signers) }; @@ -353,7 +364,7 @@ pub fn process_set_validator_info( tx.try_sign(&signers, recent_blockhash)?; check_account_for_fee( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &tx.message, )?; diff --git a/cli/src/vote.rs b/cli/src/vote.rs index 76b905e229..33c1c6db40 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -1,16 +1,16 @@ use crate::cli::{ - build_balance_message, check_account_for_fee, check_unique_pubkeys, - log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult, + build_balance_message, check_account_for_fee, check_unique_pubkeys, generate_unique_signers, + log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, CliSignerInfo, + ProcessResult, }; use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand}; use solana_clap_utils::{input_parsers::*, input_validators::*}; use solana_client::rpc_client::RpcClient; +use solana_remote_wallet::remote_wallet::RemoteWalletManager; use solana_sdk::{ account::Account, message::Message, pubkey::Pubkey, - signature::Keypair, - signature::Signer, system_instruction::{create_address_with_seed, SystemError}, transaction::Transaction, }; @@ -18,6 +18,7 @@ use solana_vote_program::{ vote_instruction::{self, VoteError}, vote_state::{VoteAuthorize, VoteInit, VoteState}, }; +use std::sync::Arc; pub trait VoteSubCommands { fn vote_subcommands(self) -> Self; @@ -176,56 +177,88 @@ impl VoteSubCommands for App<'_, '_> { } } -pub fn parse_vote_create_account(matches: &ArgMatches<'_>) -> Result { - let vote_account = keypair_of(matches, "vote_account").unwrap(); +pub fn parse_vote_create_account( + matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, +) -> Result { + let (vote_account, _) = signer_of(matches, "vote_account", wallet_manager)?; let seed = matches.value_of("seed").map(|s| s.to_string()); let identity_pubkey = pubkey_of(matches, "identity_pubkey").unwrap(); let commission = value_t_or_exit!(matches, "commission", u8); let authorized_voter = pubkey_of(matches, "authorized_voter"); let authorized_withdrawer = pubkey_of(matches, "authorized_withdrawer"); + let payer_provided = None; + let CliSignerInfo { signers } = generate_unique_signers( + vec![payer_provided, vote_account], + matches, + default_signer_path, + wallet_manager, + )?; + Ok(CliCommandInfo { command: CliCommand::CreateVoteAccount { - vote_account: vote_account.into(), seed, node_pubkey: identity_pubkey, authorized_voter, authorized_withdrawer, commission, }, - require_keypair: true, + signers, }) } pub fn parse_vote_authorize( matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, vote_authorize: VoteAuthorize, ) -> Result { let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap(); let new_authorized_pubkey = pubkey_of(matches, "new_authorized_pubkey").unwrap(); + let authorized_voter_provided = None; + let CliSignerInfo { signers } = generate_unique_signers( + vec![authorized_voter_provided], + matches, + default_signer_path, + wallet_manager, + )?; + Ok(CliCommandInfo { command: CliCommand::VoteAuthorize { vote_account_pubkey, new_authorized_pubkey, vote_authorize, }, - require_keypair: true, + signers, }) } -pub fn parse_vote_update_validator(matches: &ArgMatches<'_>) -> Result { +pub fn parse_vote_update_validator( + matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: Option<&Arc>, +) -> Result { let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap(); let new_identity_pubkey = pubkey_of(matches, "new_identity_pubkey").unwrap(); - let authorized_voter = keypair_of(matches, "authorized_voter").unwrap(); + let (authorized_voter, _) = signer_of(matches, "authorized_voter", wallet_manager)?; + + let payer_provided = None; + let CliSignerInfo { signers } = generate_unique_signers( + vec![payer_provided, authorized_voter], + matches, + default_signer_path, + wallet_manager, + )?; Ok(CliCommandInfo { command: CliCommand::VoteUpdateValidator { vote_account_pubkey, new_identity_pubkey, - authorized_voter: authorized_voter.into(), }, - require_keypair: true, + signers, }) } @@ -239,20 +272,20 @@ pub fn parse_vote_get_account_command( pubkey: vote_account_pubkey, use_lamports_unit, }, - require_keypair: false, + signers: vec![], }) } pub fn process_create_vote_account( rpc_client: &RpcClient, config: &CliConfig, - vote_account: &Keypair, seed: &Option, identity_pubkey: &Pubkey, authorized_voter: &Option, authorized_withdrawer: &Option, commission: u8, ) -> ProcessResult { + let vote_account = config.signers[1]; let vote_account_pubkey = vote_account.pubkey(); let vote_account_address = if let Some(seed) = seed { create_address_with_seed(&vote_account_pubkey, &seed, &solana_vote_program::id())? @@ -260,7 +293,7 @@ pub fn process_create_vote_account( vote_account_pubkey }; check_unique_pubkeys( - (&config.keypair.pubkey(), "cli keypair".to_string()), + (&config.signers[0].pubkey(), "cli keypair".to_string()), (&vote_account_address, "vote_account".to_string()), )?; @@ -294,16 +327,16 @@ pub fn process_create_vote_account( let ixs = if let Some(seed) = seed { vote_instruction::create_account_with_seed( - &config.keypair.pubkey(), // from - &vote_account_address, // to - &vote_account_pubkey, // base - seed, // seed + &config.signers[0].pubkey(), // from + &vote_account_address, // to + &vote_account_pubkey, // base + seed, // seed &vote_init, required_balance, ) } else { vote_instruction::create_account( - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &vote_account_pubkey, &vote_init, required_balance, @@ -311,22 +344,16 @@ pub fn process_create_vote_account( }; let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; - let signers = if vote_account_pubkey != config.keypair.pubkey() { - vec![config.keypair.as_ref(), vote_account] // both must sign if `from` and `to` differ - } else { - vec![config.keypair.as_ref()] // when stake_account == config.keypair and there's a seed, we only need one signature - }; - let message = Message::new(ixs); let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&signers, recent_blockhash)?; + tx.try_sign(&config.signers, recent_blockhash)?; check_account_for_fee( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &signers); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); log_instruction_custom_error::(result) } @@ -343,22 +370,22 @@ pub fn process_vote_authorize( )?; let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let ixs = vec![vote_instruction::authorize( - vote_account_pubkey, // vote account to update - &config.keypair.pubkey(), // current authorized voter - new_authorized_pubkey, // new vote signer/withdrawer - vote_authorize, // vote or withdraw + vote_account_pubkey, // vote account to update + &config.signers[0].pubkey(), // current authorized voter + new_authorized_pubkey, // new vote signer/withdrawer + vote_authorize, // vote or withdraw )]; - let message = Message::new_with_payer(ixs, Some(&config.keypair.pubkey())); + let message = Message::new_with_payer(ixs, Some(&config.signers[0].pubkey())); let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&[config.keypair.as_ref()], recent_blockhash)?; + tx.try_sign(&config.signers, recent_blockhash)?; check_account_for_fee( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.signers[0]]); log_instruction_custom_error::(result) } @@ -367,8 +394,8 @@ pub fn process_vote_update_validator( config: &CliConfig, vote_account_pubkey: &Pubkey, new_identity_pubkey: &Pubkey, - authorized_voter: &Keypair, ) -> ProcessResult { + let authorized_voter = config.signers[1]; check_unique_pubkeys( (vote_account_pubkey, "vote_account_pubkey".to_string()), (new_identity_pubkey, "new_identity_pubkey".to_string()), @@ -380,19 +407,16 @@ pub fn process_vote_update_validator( new_identity_pubkey, )]; - let message = Message::new_with_payer(ixs, Some(&config.keypair.pubkey())); + let message = Message::new_with_payer(ixs, Some(&config.signers[0].pubkey())); let mut tx = Transaction::new_unsigned(message); - tx.try_sign( - &[config.keypair.as_ref(), authorized_voter], - recent_blockhash, - )?; + tx.try_sign(&config.signers, recent_blockhash)?; check_account_for_fee( rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), &fee_calculator, &tx.message, )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.keypair.as_ref()]); + let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers); log_instruction_custom_error::(result) } @@ -474,7 +498,7 @@ pub fn process_show_vote_account( mod tests { use super::*; use crate::cli::{app, parse_command}; - use solana_sdk::signature::write_keypair; + use solana_sdk::signature::{read_keypair_file, write_keypair, Keypair, Signer}; use tempfile::NamedTempFile; fn make_tmp_file() -> (String, NamedTempFile) { @@ -492,6 +516,10 @@ mod tests { let pubkey2 = keypair2.pubkey(); let pubkey2_string = pubkey2.to_string(); + let default_keypair = Keypair::new(); + let (default_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); + let test_authorize_voter = test_commands.clone().get_matches_from(vec![ "test", "vote-authorize-voter", @@ -499,14 +527,14 @@ mod tests { &pubkey2_string, ]); assert_eq!( - parse_command(&test_authorize_voter).unwrap(), + parse_command(&test_authorize_voter, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::VoteAuthorize { vote_account_pubkey: pubkey, new_authorized_pubkey: pubkey2, vote_authorize: VoteAuthorize::Voter }, - require_keypair: true + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } ); @@ -525,17 +553,19 @@ mod tests { "10", ]); assert_eq!( - parse_command(&test_create_vote_account).unwrap(), + parse_command(&test_create_vote_account, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::CreateVoteAccount { - vote_account: keypair.into(), seed: None, node_pubkey, authorized_voter: None, authorized_withdrawer: None, commission: 10, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + Box::new(keypair) + ], } ); @@ -550,17 +580,19 @@ mod tests { &node_pubkey_string, ]); assert_eq!( - parse_command(&test_create_vote_account2).unwrap(), + parse_command(&test_create_vote_account2, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::CreateVoteAccount { - vote_account: keypair.into(), seed: None, node_pubkey, authorized_voter: None, authorized_withdrawer: None, commission: 100, }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + Box::new(keypair) + ], } ); @@ -579,17 +611,19 @@ mod tests { &authed.to_string(), ]); assert_eq!( - parse_command(&test_create_vote_account3).unwrap(), + parse_command(&test_create_vote_account3, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::CreateVoteAccount { - vote_account: keypair.into(), seed: None, node_pubkey, authorized_voter: Some(authed), authorized_withdrawer: None, commission: 100 }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + Box::new(keypair) + ], } ); @@ -606,17 +640,19 @@ mod tests { &authed.to_string(), ]); assert_eq!( - parse_command(&test_create_vote_account4).unwrap(), + parse_command(&test_create_vote_account4, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::CreateVoteAccount { - vote_account: keypair.into(), seed: None, node_pubkey, authorized_voter: None, authorized_withdrawer: Some(authed), commission: 100 }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + Box::new(keypair) + ], } ); @@ -628,16 +664,16 @@ mod tests { &keypair_file, ]); assert_eq!( - parse_command(&test_update_validator).unwrap(), + parse_command(&test_update_validator, &default_keypair_file, None).unwrap(), CliCommandInfo { command: CliCommand::VoteUpdateValidator { vote_account_pubkey: pubkey, new_identity_pubkey: pubkey2, - authorized_voter: solana_sdk::signature::read_keypair_file(&keypair_file) - .unwrap() - .into(), }, - require_keypair: true + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + Box::new(read_keypair_file(&keypair_file).unwrap()) + ], } ); } diff --git a/cli/tests/deploy.rs b/cli/tests/deploy.rs index 58c3c8f731..66f9bc9df0 100644 --- a/cli/tests/deploy.rs +++ b/cli/tests/deploy.rs @@ -3,7 +3,7 @@ use solana_cli::cli::{process_command, CliCommand, CliConfig}; use solana_client::rpc_client::RpcClient; use solana_core::validator::new_validator_for_tests; use solana_faucet::faucet::run_local_faucet; -use solana_sdk::{bpf_loader, pubkey::Pubkey}; +use solana_sdk::{bpf_loader, pubkey::Pubkey, signature::Keypair}; use std::{ fs::{remove_dir_all, File}, io::Read, @@ -38,6 +38,7 @@ fn test_cli_deploy_program() { .unwrap(); let mut config = CliConfig::default(); + let keypair = Keypair::new(); config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); config.command = CliCommand::Airdrop { faucet_host: None, @@ -45,6 +46,7 @@ fn test_cli_deploy_program() { pubkey: None, lamports: minimum_balance_for_rent_exemption + 1, // min balance for rent exemption + leftover for tx processing }; + config.signers = vec![&keypair]; process_command(&config).unwrap(); config.command = CliCommand::Deploy(pathbuf.to_str().unwrap().to_string()); diff --git a/cli/tests/nonce.rs b/cli/tests/nonce.rs index 1df026dcdf..b5efc7a36e 100644 --- a/cli/tests/nonce.rs +++ b/cli/tests/nonce.rs @@ -4,25 +4,14 @@ use solana_faucet::faucet::run_local_faucet; use solana_sdk::{ hash::Hash, pubkey::Pubkey, - signature::{keypair_from_seed, read_keypair_file, write_keypair, Keypair, Signer}, + signature::{keypair_from_seed, Keypair, Signer}, system_instruction::create_address_with_seed, system_program, }; -use std::fs::remove_dir_all; -use std::sync::mpsc::channel; +use std::{fs::remove_dir_all, sync::mpsc::channel, thread::sleep, time::Duration}; #[cfg(test)] use solana_core::validator::new_validator_for_tests; -use std::rc::Rc; -use std::thread::sleep; -use std::time::Duration; - -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 check_balance(expected_balance: u64, client: &RpcClient, pubkey: &Pubkey) { (0..5).for_each(|tries| { @@ -45,28 +34,9 @@ fn test_nonce() { let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new_socket(leader_data.rpc); + let json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); - let mut config_payer = CliConfig::default(); - config_payer.json_rpc_url = - format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); - - let keypair = keypair_from_seed(&[0u8; 32]).unwrap(); - let (keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&keypair, tmp_file.as_file_mut()).unwrap(); - let mut config_nonce = CliConfig::default(); - config_nonce.keypair = keypair.into(); - config_nonce.json_rpc_url = - format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); - - full_battery_tests( - &rpc_client, - &faucet_addr, - &mut config_payer, - &mut config_nonce, - &keypair_file, - None, - None, - ); + full_battery_tests(&rpc_client, &faucet_addr, json_rpc_url, None, false); server.close().unwrap(); remove_dir_all(ledger_path).unwrap(); @@ -80,27 +50,14 @@ fn test_nonce_with_seed() { let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new_socket(leader_data.rpc); - - let mut config_payer = CliConfig::default(); - config_payer.json_rpc_url = - format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); - - let keypair = keypair_from_seed(&[0u8; 32]).unwrap(); - let (keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&keypair, tmp_file.as_file_mut()).unwrap(); - let mut config_nonce = CliConfig::default(); - config_nonce.keypair = keypair.into(); - config_nonce.json_rpc_url = - format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + let json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); full_battery_tests( &rpc_client, &faucet_addr, - &mut config_payer, - &mut config_nonce, - &keypair_file, + json_rpc_url, Some(String::from("seed")), - None, + false, ); server.close().unwrap(); @@ -115,78 +72,73 @@ fn test_nonce_with_authority() { let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new_socket(leader_data.rpc); + let json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); - let mut config_payer = CliConfig::default(); - config_payer.json_rpc_url = - format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); - - let nonce_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); - let (nonce_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&nonce_keypair, tmp_file.as_file_mut()).unwrap(); - let mut config_nonce = CliConfig::default(); - config_nonce.json_rpc_url = - format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); - - let nonce_authority = Keypair::new(); - let (authority_keypair_file, mut tmp_file2) = make_tmp_file(); - write_keypair(&nonce_authority, tmp_file2.as_file_mut()).unwrap(); - - full_battery_tests( - &rpc_client, - &faucet_addr, - &mut config_payer, - &mut config_nonce, - &nonce_keypair_file, - None, - Some(&authority_keypair_file), - ); + full_battery_tests(&rpc_client, &faucet_addr, json_rpc_url, None, true); server.close().unwrap(); remove_dir_all(ledger_path).unwrap(); } -fn read_keypair_from_option(keypair_file: &Option<&str>) -> Option> { - keypair_file.map(|akf| read_keypair_file(&akf).unwrap().into()) -} - fn full_battery_tests( rpc_client: &RpcClient, faucet_addr: &std::net::SocketAddr, - config_payer: &mut CliConfig, - config_nonce: &mut CliConfig, - nonce_keypair_file: &str, + json_rpc_url: String, seed: Option, - authority_keypair_file: Option<&str>, + use_nonce_authority: bool, ) { + let mut config_payer = CliConfig::default(); + config_payer.json_rpc_url = json_rpc_url.clone(); + let payer = Keypair::new(); + config_payer.signers = vec![&payer]; + request_and_confirm_airdrop( &rpc_client, &faucet_addr, - &config_payer.keypair.pubkey(), + &config_payer.signers[0].pubkey(), 2000, ) .unwrap(); - check_balance(2000, &rpc_client, &config_payer.keypair.pubkey()); + check_balance(2000, &rpc_client, &config_payer.signers[0].pubkey()); + + let mut config_nonce = CliConfig::default(); + config_nonce.json_rpc_url = json_rpc_url; + let nonce_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); + config_nonce.signers = vec![&nonce_keypair]; let nonce_account = if let Some(seed) = seed.as_ref() { - create_address_with_seed(&config_nonce.keypair.pubkey(), seed, &system_program::id()) - .unwrap() + create_address_with_seed( + &config_nonce.signers[0].pubkey(), + seed, + &system_program::id(), + ) + .unwrap() } else { - read_keypair_file(&nonce_keypair_file).unwrap().pubkey() + nonce_keypair.pubkey() + }; + + let nonce_authority = Keypair::new(); + let optional_authority = if use_nonce_authority { + Some(nonce_authority.pubkey()) + } else { + None }; // Create nonce account + config_payer.signers.push(&nonce_keypair); config_payer.command = CliCommand::CreateNonceAccount { - nonce_account: Rc::new(read_keypair_file(&nonce_keypair_file).unwrap().into()), + nonce_account: 1, seed, - nonce_authority: read_keypair_from_option(&authority_keypair_file).map(|k| k.pubkey()), + nonce_authority: optional_authority, lamports: 1000, }; process_command(&config_payer).unwrap(); - check_balance(1000, &rpc_client, &config_payer.keypair.pubkey()); + check_balance(1000, &rpc_client, &config_payer.signers[0].pubkey()); check_balance(1000, &rpc_client, &nonce_account); // Get nonce + config_payer.signers.pop(); config_payer.command = CliCommand::GetNonce(nonce_account); let first_nonce_string = process_command(&config_payer).unwrap(); let first_nonce = first_nonce_string.parse::().unwrap(); @@ -198,14 +150,24 @@ fn full_battery_tests( assert_eq!(first_nonce, second_nonce); + let mut authorized_signers: Vec<&dyn Signer> = vec![&payer]; + let index = if use_nonce_authority { + authorized_signers.push(&nonce_authority); + 1 + } else { + 0 + }; + // New nonce + config_payer.signers = authorized_signers.clone(); config_payer.command = CliCommand::NewNonce { nonce_account, - nonce_authority: read_keypair_from_option(&authority_keypair_file), + nonce_authority: index, }; process_command(&config_payer).unwrap(); // Get nonce + config_payer.signers = vec![&payer]; config_payer.command = CliCommand::GetNonce(nonce_account); let third_nonce_string = process_command(&config_payer).unwrap(); let third_nonce = third_nonce_string.parse::().unwrap(); @@ -214,14 +176,15 @@ fn full_battery_tests( // Withdraw from nonce account let payee_pubkey = Pubkey::new_rand(); + config_payer.signers = authorized_signers; config_payer.command = CliCommand::WithdrawFromNonceAccount { nonce_account, - nonce_authority: read_keypair_from_option(&authority_keypair_file), + nonce_authority: index, destination_account_pubkey: payee_pubkey, lamports: 100, }; process_command(&config_payer).unwrap(); - check_balance(1000, &rpc_client, &config_payer.keypair.pubkey()); + check_balance(1000, &rpc_client, &config_payer.signers[0].pubkey()); check_balance(900, &rpc_client, &nonce_account); check_balance(100, &rpc_client, &payee_pubkey); @@ -234,48 +197,37 @@ fn full_battery_tests( // Set new authority let new_authority = Keypair::new(); - let (new_authority_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&new_authority, tmp_file.as_file_mut()).unwrap(); config_payer.command = CliCommand::AuthorizeNonceAccount { nonce_account, - nonce_authority: read_keypair_from_option(&authority_keypair_file), - new_authority: read_keypair_file(&new_authority_keypair_file) - .unwrap() - .pubkey(), + nonce_authority: index, + new_authority: new_authority.pubkey(), }; process_command(&config_payer).unwrap(); // Old authority fails now config_payer.command = CliCommand::NewNonce { nonce_account, - nonce_authority: read_keypair_from_option(&authority_keypair_file), + nonce_authority: index, }; process_command(&config_payer).unwrap_err(); // New authority can advance nonce + config_payer.signers = vec![&payer, &new_authority]; config_payer.command = CliCommand::NewNonce { nonce_account, - nonce_authority: Some( - read_keypair_file(&new_authority_keypair_file) - .unwrap() - .into(), - ), + nonce_authority: 1, }; process_command(&config_payer).unwrap(); // New authority can withdraw from nonce account config_payer.command = CliCommand::WithdrawFromNonceAccount { nonce_account, - nonce_authority: Some( - read_keypair_file(&new_authority_keypair_file) - .unwrap() - .into(), - ), + nonce_authority: 1, destination_account_pubkey: payee_pubkey, lamports: 100, }; process_command(&config_payer).unwrap(); - check_balance(1000, &rpc_client, &config_payer.keypair.pubkey()); + check_balance(1000, &rpc_client, &config_payer.signers[0].pubkey()); check_balance(800, &rpc_client, &nonce_account); check_balance(200, &rpc_client, &payee_pubkey); } diff --git a/cli/tests/pay.rs b/cli/tests/pay.rs index dae91759a5..2d30c6f119 100644 --- a/cli/tests/pay.rs +++ b/cli/tests/pay.rs @@ -12,22 +12,12 @@ use solana_sdk::{ fee_calculator::FeeCalculator, nonce_state::NonceState, pubkey::Pubkey, - signature::{read_keypair_file, write_keypair, Keypair, Signer}, + signature::{Keypair, Signer}, }; -use std::fs::remove_dir_all; -use std::sync::mpsc::channel; +use std::{fs::remove_dir_all, sync::mpsc::channel, thread::sleep, time::Duration}; #[cfg(test)] use solana_core::validator::new_validator_for_tests; -use std::rc::Rc; -use std::thread::sleep; -use std::time::Duration; -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 check_balance(expected_balance: u64, client: &RpcClient, pubkey: &Pubkey) { (0..5).for_each(|tries| { @@ -52,32 +42,36 @@ fn test_cli_timestamp_tx() { let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new_socket(leader_data.rpc); + let default_signer0 = Keypair::new(); + let default_signer1 = Keypair::new(); let mut config_payer = CliConfig::default(); config_payer.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + config_payer.signers = vec![&default_signer0]; let mut config_witness = CliConfig::default(); config_witness.json_rpc_url = config_payer.json_rpc_url.clone(); + config_witness.signers = vec![&default_signer1]; assert_ne!( - config_payer.keypair.pubkey(), - config_witness.keypair.pubkey() + config_payer.signers[0].pubkey(), + config_witness.signers[0].pubkey() ); request_and_confirm_airdrop( &rpc_client, &faucet_addr, - &config_payer.keypair.pubkey(), + &config_payer.signers[0].pubkey(), 50, ) .unwrap(); - check_balance(50, &rpc_client, &config_payer.keypair.pubkey()); + check_balance(50, &rpc_client, &config_payer.signers[0].pubkey()); request_and_confirm_airdrop( &rpc_client, &faucet_addr, - &config_witness.keypair.pubkey(), + &config_witness.signers[0].pubkey(), 1, ) .unwrap(); @@ -89,7 +83,7 @@ fn test_cli_timestamp_tx() { lamports: 10, to: bob_pubkey, timestamp: Some(dt), - timestamp_pubkey: Some(config_witness.keypair.pubkey()), + timestamp_pubkey: Some(config_witness.signers[0].pubkey()), ..PayCommand::default() }); let sig_response = process_command(&config_payer); @@ -101,7 +95,7 @@ fn test_cli_timestamp_tx() { .expect("base58-encoded public key"); let process_id = Pubkey::new(&process_id_vec); - check_balance(40, &rpc_client, &config_payer.keypair.pubkey()); // config_payer balance + check_balance(40, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance check_balance(10, &rpc_client, &process_id); // contract balance check_balance(0, &rpc_client, &bob_pubkey); // recipient balance @@ -109,7 +103,7 @@ fn test_cli_timestamp_tx() { config_witness.command = CliCommand::TimeElapsed(bob_pubkey, process_id, dt); process_command(&config_witness).unwrap(); - check_balance(40, &rpc_client, &config_payer.keypair.pubkey()); // config_payer balance + check_balance(40, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance check_balance(0, &rpc_client, &process_id); // contract balance check_balance(10, &rpc_client, &bob_pubkey); // recipient balance @@ -127,30 +121,34 @@ fn test_cli_witness_tx() { let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new_socket(leader_data.rpc); + let default_signer0 = Keypair::new(); + let default_signer1 = Keypair::new(); let mut config_payer = CliConfig::default(); config_payer.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + config_payer.signers = vec![&default_signer0]; let mut config_witness = CliConfig::default(); config_witness.json_rpc_url = config_payer.json_rpc_url.clone(); + config_witness.signers = vec![&default_signer1]; assert_ne!( - config_payer.keypair.pubkey(), - config_witness.keypair.pubkey() + config_payer.signers[0].pubkey(), + config_witness.signers[0].pubkey() ); request_and_confirm_airdrop( &rpc_client, &faucet_addr, - &config_payer.keypair.pubkey(), + &config_payer.signers[0].pubkey(), 50, ) .unwrap(); request_and_confirm_airdrop( &rpc_client, &faucet_addr, - &config_witness.keypair.pubkey(), + &config_witness.signers[0].pubkey(), 1, ) .unwrap(); @@ -159,7 +157,7 @@ fn test_cli_witness_tx() { config_payer.command = CliCommand::Pay(PayCommand { lamports: 10, to: bob_pubkey, - witnesses: Some(vec![config_witness.keypair.pubkey()]), + witnesses: Some(vec![config_witness.signers[0].pubkey()]), ..PayCommand::default() }); let sig_response = process_command(&config_payer); @@ -171,7 +169,7 @@ fn test_cli_witness_tx() { .expect("base58-encoded public key"); let process_id = Pubkey::new(&process_id_vec); - check_balance(40, &rpc_client, &config_payer.keypair.pubkey()); // config_payer balance + check_balance(40, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance check_balance(10, &rpc_client, &process_id); // contract balance check_balance(0, &rpc_client, &bob_pubkey); // recipient balance @@ -179,7 +177,7 @@ fn test_cli_witness_tx() { config_witness.command = CliCommand::Witness(bob_pubkey, process_id); process_command(&config_witness).unwrap(); - check_balance(40, &rpc_client, &config_payer.keypair.pubkey()); // config_payer balance + check_balance(40, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance check_balance(0, &rpc_client, &process_id); // contract balance check_balance(10, &rpc_client, &bob_pubkey); // recipient balance @@ -197,23 +195,27 @@ fn test_cli_cancel_tx() { let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new_socket(leader_data.rpc); + let default_signer0 = Keypair::new(); + let default_signer1 = Keypair::new(); let mut config_payer = CliConfig::default(); config_payer.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + config_payer.signers = vec![&default_signer0]; let mut config_witness = CliConfig::default(); config_witness.json_rpc_url = config_payer.json_rpc_url.clone(); + config_witness.signers = vec![&default_signer1]; assert_ne!( - config_payer.keypair.pubkey(), - config_witness.keypair.pubkey() + config_payer.signers[0].pubkey(), + config_witness.signers[0].pubkey() ); request_and_confirm_airdrop( &rpc_client, &faucet_addr, - &config_payer.keypair.pubkey(), + &config_payer.signers[0].pubkey(), 50, ) .unwrap(); @@ -222,7 +224,7 @@ fn test_cli_cancel_tx() { config_payer.command = CliCommand::Pay(PayCommand { lamports: 10, to: bob_pubkey, - witnesses: Some(vec![config_witness.keypair.pubkey()]), + witnesses: Some(vec![config_witness.signers[0].pubkey()]), cancelable: true, ..PayCommand::default() }); @@ -235,7 +237,7 @@ fn test_cli_cancel_tx() { .expect("base58-encoded public key"); let process_id = Pubkey::new(&process_id_vec); - check_balance(40, &rpc_client, &config_payer.keypair.pubkey()); // config_payer balance + check_balance(40, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance check_balance(10, &rpc_client, &process_id); // contract balance check_balance(0, &rpc_client, &bob_pubkey); // recipient balance @@ -243,7 +245,7 @@ fn test_cli_cancel_tx() { config_payer.command = CliCommand::Cancel(process_id); process_command(&config_payer).unwrap(); - check_balance(50, &rpc_client, &config_payer.keypair.pubkey()); // config_payer balance + check_balance(50, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance check_balance(0, &rpc_client, &process_id); // contract balance check_balance(0, &rpc_client, &bob_pubkey); // recipient balance @@ -261,22 +263,26 @@ fn test_offline_pay_tx() { let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new_socket(leader_data.rpc); + let default_signer = Keypair::new(); + let default_offline_signer = Keypair::new(); let mut config_offline = CliConfig::default(); config_offline.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + config_offline.signers = vec![&default_offline_signer]; let mut config_online = CliConfig::default(); config_online.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + config_online.signers = vec![&default_signer]; assert_ne!( - config_offline.keypair.pubkey(), - config_online.keypair.pubkey() + config_offline.signers[0].pubkey(), + config_online.signers[0].pubkey() ); request_and_confirm_airdrop( &rpc_client, &faucet_addr, - &config_offline.keypair.pubkey(), + &config_offline.signers[0].pubkey(), 50, ) .unwrap(); @@ -284,12 +290,12 @@ fn test_offline_pay_tx() { request_and_confirm_airdrop( &rpc_client, &faucet_addr, - &config_online.keypair.pubkey(), + &config_online.signers[0].pubkey(), 50, ) .unwrap(); - check_balance(50, &rpc_client, &config_offline.keypair.pubkey()); - check_balance(50, &rpc_client, &config_online.keypair.pubkey()); + check_balance(50, &rpc_client, &config_offline.signers[0].pubkey()); + check_balance(50, &rpc_client, &config_online.signers[0].pubkey()); let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap(); config_offline.command = CliCommand::Pay(PayCommand { @@ -301,15 +307,15 @@ fn test_offline_pay_tx() { }); let sig_response = process_command(&config_offline).unwrap(); - check_balance(50, &rpc_client, &config_offline.keypair.pubkey()); - check_balance(50, &rpc_client, &config_online.keypair.pubkey()); + check_balance(50, &rpc_client, &config_offline.signers[0].pubkey()); + check_balance(50, &rpc_client, &config_online.signers[0].pubkey()); check_balance(0, &rpc_client, &bob_pubkey); let (blockhash, signers) = parse_sign_only_reply_string(&sig_response); let offline_presigner = - presigner_from_pubkey_sigs(&config_offline.keypair.pubkey(), &signers).unwrap(); - let online_pubkey = config_online.keypair.pubkey(); - config_online.keypair = offline_presigner.into(); + presigner_from_pubkey_sigs(&config_offline.signers[0].pubkey(), &signers).unwrap(); + let online_pubkey = config_online.signers[0].pubkey(); + config_online.signers = vec![&offline_presigner]; config_online.command = CliCommand::Pay(PayCommand { lamports: 10, to: bob_pubkey, @@ -318,7 +324,7 @@ fn test_offline_pay_tx() { }); process_command(&config_online).unwrap(); - check_balance(40, &rpc_client, &config_offline.keypair.pubkey()); + check_balance(40, &rpc_client, &config_offline.signers[0].pubkey()); check_balance(50, &rpc_client, &online_pubkey); check_balance(10, &rpc_client, &bob_pubkey); @@ -336,9 +342,11 @@ fn test_nonced_pay_tx() { let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new_socket(leader_data.rpc); + let default_signer = Keypair::new(); let mut config = CliConfig::default(); config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + config.signers = vec![&default_signer]; let minimum_nonce_balance = rpc_client .get_minimum_balance_for_rent_exemption(NonceState::size()) @@ -347,29 +355,28 @@ fn test_nonced_pay_tx() { request_and_confirm_airdrop( &rpc_client, &faucet_addr, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), 50 + minimum_nonce_balance, ) .unwrap(); check_balance( 50 + minimum_nonce_balance, &rpc_client, - &config.keypair.pubkey(), + &config.signers[0].pubkey(), ); // Create nonce account let nonce_account = Keypair::new(); - let (nonce_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap(); config.command = CliCommand::CreateNonceAccount { - nonce_account: Rc::new(read_keypair_file(&nonce_keypair_file).unwrap().into()), + nonce_account: 1, seed: None, - nonce_authority: Some(config.keypair.pubkey()), + nonce_authority: Some(config.signers[0].pubkey()), lamports: minimum_nonce_balance, }; + config.signers.push(&nonce_account); process_command(&config).unwrap(); - check_balance(50, &rpc_client, &config.keypair.pubkey()); + check_balance(50, &rpc_client, &config.signers[0].pubkey()); check_balance(minimum_nonce_balance, &rpc_client, &nonce_account.pubkey()); // Fetch nonce hash @@ -381,6 +388,7 @@ fn test_nonced_pay_tx() { }; let bob_pubkey = Pubkey::new_rand(); + config.signers = vec![&default_signer]; config.command = CliCommand::Pay(PayCommand { lamports: 10, to: bob_pubkey, @@ -390,7 +398,7 @@ fn test_nonced_pay_tx() { }); process_command(&config).expect("failed to process pay command"); - check_balance(40, &rpc_client, &config.keypair.pubkey()); + check_balance(40, &rpc_client, &config.signers[0].pubkey()); check_balance(10, &rpc_client, &bob_pubkey); // Verify that nonce has been used diff --git a/cli/tests/request_airdrop.rs b/cli/tests/request_airdrop.rs index 475bc47e60..ca92a60662 100644 --- a/cli/tests/request_airdrop.rs +++ b/cli/tests/request_airdrop.rs @@ -2,8 +2,8 @@ use solana_cli::cli::{process_command, CliCommand, CliConfig}; use solana_client::rpc_client::RpcClient; use solana_core::validator::new_validator_for_tests; use solana_faucet::faucet::run_local_faucet; -use std::fs::remove_dir_all; -use std::sync::mpsc::channel; +use solana_sdk::signature::Keypair; +use std::{fs::remove_dir_all, sync::mpsc::channel}; #[test] fn test_cli_request_airdrop() { @@ -20,6 +20,8 @@ fn test_cli_request_airdrop() { pubkey: None, lamports: 50, }; + let keypair = Keypair::new(); + bob_config.signers = vec![&keypair]; let sig_response = process_command(&bob_config); sig_response.unwrap(); @@ -27,7 +29,7 @@ fn test_cli_request_airdrop() { let rpc_client = RpcClient::new_socket(leader_data.rpc); let balance = rpc_client - .retry_get_balance(&bob_config.keypair.pubkey(), 1) + .retry_get_balance(&bob_config.signers[0].pubkey(), 1) .unwrap() .unwrap(); assert_eq!(balance, 50); diff --git a/cli/tests/stake.rs b/cli/tests/stake.rs index 526cb4b0ef..d2de68ed80 100644 --- a/cli/tests/stake.rs +++ b/cli/tests/stake.rs @@ -10,27 +10,16 @@ use solana_sdk::{ fee_calculator::FeeCalculator, nonce_state::NonceState, pubkey::Pubkey, - signature::{keypair_from_seed, read_keypair_file, write_keypair, Keypair, Signer}, + signature::{keypair_from_seed, Keypair, Signer}, system_instruction::create_address_with_seed, }; use solana_stake_program::stake_state::{Lockup, StakeAuthorize, StakeState}; -use std::fs::remove_dir_all; -use std::sync::mpsc::channel; +use std::{fs::remove_dir_all, sync::mpsc::channel, thread::sleep, time::Duration}; #[cfg(test)] use solana_core::validator::{ new_validator_for_tests, new_validator_for_tests_ex, new_validator_for_tests_with_vote_pubkey, }; -use std::rc::Rc; -use std::thread::sleep; -use std::time::Duration; - -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 check_balance(expected_balance: u64, client: &RpcClient, pubkey: &Pubkey) { (0..5).for_each(|tries| { @@ -53,21 +42,26 @@ fn test_stake_delegation_force() { let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new_socket(leader_data.rpc); + let default_signer = Keypair::new(); let mut config = CliConfig::default(); config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + config.signers = vec![&default_signer]; - request_and_confirm_airdrop(&rpc_client, &faucet_addr, &config.keypair.pubkey(), 100_000) - .unwrap(); + request_and_confirm_airdrop( + &rpc_client, + &faucet_addr, + &config.signers[0].pubkey(), + 100_000, + ) + .unwrap(); // Create vote account let vote_keypair = Keypair::new(); - let (vote_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&vote_keypair, tmp_file.as_file_mut()).unwrap(); + config.signers = vec![&default_signer, &vote_keypair]; config.command = CliCommand::CreateVoteAccount { - vote_account: read_keypair_file(&vote_keypair_file).unwrap().into(), seed: None, - node_pubkey: config.keypair.pubkey(), + node_pubkey: config.signers[0].pubkey(), authorized_voter: None, authorized_withdrawer: None, commission: 0, @@ -76,10 +70,9 @@ fn test_stake_delegation_force() { // Create stake account let stake_keypair = Keypair::new(); - let (stake_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); + config.signers = vec![&default_signer, &stake_keypair]; config.command = CliCommand::CreateStakeAccount { - stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), + stake_account: 1, seed: None, staker: None, withdrawer: None, @@ -88,23 +81,24 @@ fn test_stake_delegation_force() { sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: None, - from: None, + nonce_authority: 0, + fee_payer: 0, + from: 0, }; process_command(&config).unwrap(); // Delegate stake fails (vote account had never voted) + config.signers = vec![&default_signer]; config.command = CliCommand::DelegateStake { stake_account_pubkey: stake_keypair.pubkey(), vote_account_pubkey: vote_keypair.pubkey(), - stake_authority: None, + stake_authority: 0, force: false, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap_err(); @@ -112,13 +106,13 @@ fn test_stake_delegation_force() { config.command = CliCommand::DelegateStake { stake_account_pubkey: stake_keypair.pubkey(), vote_account_pubkey: vote_keypair.pubkey(), - stake_authority: None, + stake_authority: 0, force: true, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); @@ -139,28 +133,22 @@ fn test_seed_stake_delegation_and_deactivation() { let rpc_client = RpcClient::new_socket(leader_data.rpc); let validator_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); - let (validator_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&validator_keypair, tmp_file.as_file_mut()).unwrap(); let mut config_validator = CliConfig::default(); - config_validator.keypair = validator_keypair.into(); config_validator.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); - - let mut config_stake = CliConfig::default(); - config_stake.json_rpc_url = - format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + config_validator.signers = vec![&validator_keypair]; request_and_confirm_airdrop( &rpc_client, &faucet_addr, - &config_validator.keypair.pubkey(), + &config_validator.signers[0].pubkey(), 100_000, ) .unwrap(); - check_balance(100_000, &rpc_client, &config_validator.keypair.pubkey()); + check_balance(100_000, &rpc_client, &config_validator.signers[0].pubkey()); let stake_address = create_address_with_seed( - &config_validator.keypair.pubkey(), + &config_validator.signers[0].pubkey(), "hi there", &solana_stake_program::id(), ) @@ -169,7 +157,7 @@ fn test_seed_stake_delegation_and_deactivation() { // Create stake account with a seed, uses the validator config as the base, // which is nice ;) config_validator.command = CliCommand::CreateStakeAccount { - stake_account: Rc::new(read_keypair_file(&validator_keypair_file).unwrap().into()), + stake_account: 0, seed: Some("hi there".to_string()), staker: None, withdrawer: None, @@ -178,9 +166,9 @@ fn test_seed_stake_delegation_and_deactivation() { sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: None, - from: None, + nonce_authority: 0, + fee_payer: 0, + from: 0, }; process_command(&config_validator).unwrap(); @@ -188,25 +176,25 @@ fn test_seed_stake_delegation_and_deactivation() { config_validator.command = CliCommand::DelegateStake { stake_account_pubkey: stake_address, vote_account_pubkey: vote_pubkey, - stake_authority: None, + stake_authority: 0, force: false, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; process_command(&config_validator).unwrap(); // Deactivate stake config_validator.command = CliCommand::DeactivateStake { stake_account_pubkey: stake_address, - stake_authority: None, + stake_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; process_command(&config_validator).unwrap(); @@ -225,31 +213,28 @@ fn test_stake_delegation_and_deactivation() { let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new_socket(leader_data.rpc); + let validator_keypair = Keypair::new(); let mut config_validator = CliConfig::default(); config_validator.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + config_validator.signers = vec![&validator_keypair]; let stake_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); - let (stake_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); - let mut config_stake = CliConfig::default(); - config_stake.keypair = stake_keypair.into(); - config_stake.json_rpc_url = - format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); request_and_confirm_airdrop( &rpc_client, &faucet_addr, - &config_validator.keypair.pubkey(), + &config_validator.signers[0].pubkey(), 100_000, ) .unwrap(); - check_balance(100_000, &rpc_client, &config_validator.keypair.pubkey()); + check_balance(100_000, &rpc_client, &config_validator.signers[0].pubkey()); // Create stake account + config_validator.signers.push(&stake_keypair); config_validator.command = CliCommand::CreateStakeAccount { - stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), + stake_account: 1, seed: None, staker: None, withdrawer: None, @@ -258,35 +243,36 @@ fn test_stake_delegation_and_deactivation() { sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: None, - from: None, + nonce_authority: 0, + fee_payer: 0, + from: 0, }; process_command(&config_validator).unwrap(); // Delegate stake + config_validator.signers.pop(); config_validator.command = CliCommand::DelegateStake { - stake_account_pubkey: config_stake.keypair.pubkey(), + stake_account_pubkey: stake_keypair.pubkey(), vote_account_pubkey: vote_pubkey, - stake_authority: None, + stake_authority: 0, force: false, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; process_command(&config_validator).unwrap(); // Deactivate stake config_validator.command = CliCommand::DeactivateStake { - stake_account_pubkey: config_stake.keypair.pubkey(), - stake_authority: None, + stake_account_pubkey: stake_keypair.pubkey(), + stake_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; process_command(&config_validator).unwrap(); @@ -309,113 +295,114 @@ fn test_offline_stake_delegation_and_deactivation() { let mut config_validator = CliConfig::default(); config_validator.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + let validator_keypair = Keypair::new(); + config_validator.signers = vec![&validator_keypair]; let mut config_payer = CliConfig::default(); config_payer.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); let stake_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); - let (stake_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); - let mut config_stake = CliConfig::default(); - config_stake.keypair = stake_keypair.into(); - config_stake.json_rpc_url = - format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); let mut config_offline = CliConfig::default(); config_offline.json_rpc_url = String::default(); config_offline.command = CliCommand::ClusterVersion; + let offline_keypair = Keypair::new(); + config_offline.signers = vec![&offline_keypair]; // Verfiy that we cannot reach the cluster process_command(&config_offline).unwrap_err(); request_and_confirm_airdrop( &rpc_client, &faucet_addr, - &config_validator.keypair.pubkey(), + &config_validator.signers[0].pubkey(), 100_000, ) .unwrap(); - check_balance(100_000, &rpc_client, &config_validator.keypair.pubkey()); + check_balance(100_000, &rpc_client, &config_validator.signers[0].pubkey()); request_and_confirm_airdrop( &rpc_client, &faucet_addr, - &config_offline.keypair.pubkey(), + &config_offline.signers[0].pubkey(), 100_000, ) .unwrap(); - check_balance(100_000, &rpc_client, &config_offline.keypair.pubkey()); + check_balance(100_000, &rpc_client, &config_offline.signers[0].pubkey()); // Create stake account + config_validator.signers.push(&stake_keypair); config_validator.command = CliCommand::CreateStakeAccount { - stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), + stake_account: 1, seed: None, - staker: Some(config_offline.keypair.pubkey().into()), + staker: Some(config_offline.signers[0].pubkey().into()), withdrawer: None, lockup: Lockup::default(), lamports: 50_000, sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: None, - from: None, + nonce_authority: 0, + fee_payer: 0, + from: 0, }; process_command(&config_validator).unwrap(); // Delegate stake offline let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap(); config_offline.command = CliCommand::DelegateStake { - stake_account_pubkey: config_stake.keypair.pubkey(), + stake_account_pubkey: stake_keypair.pubkey(), vote_account_pubkey: vote_pubkey, - stake_authority: None, + stake_authority: 0, force: false, sign_only: true, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; let sig_response = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sig_response); let offline_presigner = - presigner_from_pubkey_sigs(&config_offline.keypair.pubkey(), &signers).unwrap(); + presigner_from_pubkey_sigs(&config_offline.signers[0].pubkey(), &signers).unwrap(); + config_payer.signers = vec![&offline_presigner]; config_payer.command = CliCommand::DelegateStake { - stake_account_pubkey: config_stake.keypair.pubkey(), + stake_account_pubkey: stake_keypair.pubkey(), vote_account_pubkey: vote_pubkey, - stake_authority: Some(offline_presigner.clone().into()), + stake_authority: 0, force: false, sign_only: false, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, - nonce_authority: None, - fee_payer: Some(offline_presigner.clone().into()), + nonce_authority: 0, + fee_payer: 0, }; process_command(&config_payer).unwrap(); // Deactivate stake offline let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap(); config_offline.command = CliCommand::DeactivateStake { - stake_account_pubkey: config_stake.keypair.pubkey(), - stake_authority: None, + stake_account_pubkey: stake_keypair.pubkey(), + stake_authority: 0, sign_only: true, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; let sig_response = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sig_response); let offline_presigner = - presigner_from_pubkey_sigs(&config_offline.keypair.pubkey(), &signers).unwrap(); + presigner_from_pubkey_sigs(&config_offline.signers[0].pubkey(), &signers).unwrap(); + config_payer.signers = vec![&offline_presigner]; config_payer.command = CliCommand::DeactivateStake { - stake_account_pubkey: config_stake.keypair.pubkey(), - stake_authority: Some(offline_presigner.clone().into()), + stake_account_pubkey: stake_keypair.pubkey(), + stake_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, - nonce_authority: None, - fee_payer: Some(offline_presigner.clone().into()), + nonce_authority: 0, + fee_payer: 0, }; process_command(&config_payer).unwrap(); @@ -436,25 +423,27 @@ fn test_nonced_stake_delegation_and_deactivation() { let rpc_client = RpcClient::new_socket(leader_data.rpc); let config_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); - let (config_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&config_keypair, tmp_file.as_file_mut()).unwrap(); let mut config = CliConfig::default(); - config.keypair = config_keypair.into(); + config.signers = vec![&config_keypair]; config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); let minimum_nonce_balance = rpc_client .get_minimum_balance_for_rent_exemption(NonceState::size()) .unwrap(); - request_and_confirm_airdrop(&rpc_client, &faucet_addr, &config.keypair.pubkey(), 100_000) - .unwrap(); + request_and_confirm_airdrop( + &rpc_client, + &faucet_addr, + &config.signers[0].pubkey(), + 100_000, + ) + .unwrap(); // Create stake account let stake_keypair = Keypair::new(); - let (stake_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); + config.signers.push(&stake_keypair); config.command = CliCommand::CreateStakeAccount { - stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), + stake_account: 1, seed: None, staker: None, withdrawer: None, @@ -463,20 +452,19 @@ fn test_nonced_stake_delegation_and_deactivation() { sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: None, - from: None, + nonce_authority: 0, + fee_payer: 0, + from: 0, }; process_command(&config).unwrap(); // Create nonce account let nonce_account = Keypair::new(); - let (nonce_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap(); + config.signers[1] = &nonce_account; config.command = CliCommand::CreateNonceAccount { - nonce_account: Rc::new(read_keypair_file(&nonce_keypair_file).unwrap().into()), + nonce_account: 1, seed: None, - nonce_authority: Some(config.keypair.pubkey()), + nonce_authority: Some(config.signers[0].pubkey()), lamports: minimum_nonce_balance, }; process_command(&config).unwrap(); @@ -490,16 +478,17 @@ fn test_nonced_stake_delegation_and_deactivation() { }; // Delegate stake + config.signers = vec![&config_keypair]; config.command = CliCommand::DelegateStake { stake_account_pubkey: stake_keypair.pubkey(), vote_account_pubkey: vote_pubkey, - stake_authority: None, + stake_authority: 0, force: false, sign_only: false, blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), nonce_account: Some(nonce_account.pubkey()), - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); @@ -514,12 +503,12 @@ fn test_nonced_stake_delegation_and_deactivation() { // Deactivate stake config.command = CliCommand::DeactivateStake { stake_account_pubkey: stake_keypair.pubkey(), - stake_authority: None, + stake_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(nonce_hash), nonce_account: Some(nonce_account.pubkey()), - nonce_authority: Some(read_keypair_file(&config_keypair_file).unwrap().into()), - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); @@ -537,20 +526,25 @@ fn test_stake_authorize() { let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new_socket(leader_data.rpc); + let default_signer = Keypair::new(); let mut config = CliConfig::default(); config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + config.signers = vec![&default_signer]; - request_and_confirm_airdrop(&rpc_client, &faucet_addr, &config.keypair.pubkey(), 100_000) - .unwrap(); + request_and_confirm_airdrop( + &rpc_client, + &faucet_addr, + &config.signers[0].pubkey(), + 100_000, + ) + .unwrap(); let offline_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); - let (offline_authority_file, mut tmp_file) = make_tmp_file(); - write_keypair(&offline_keypair, tmp_file.as_file_mut()).unwrap(); let mut config_offline = CliConfig::default(); - config_offline.keypair = offline_keypair.into(); + config_offline.signers = vec![&offline_keypair]; config_offline.json_rpc_url = String::default(); - let offline_authority_pubkey = config_offline.keypair.pubkey(); + let offline_authority_pubkey = config_offline.signers[0].pubkey(); config_offline.command = CliCommand::ClusterVersion; // Verfiy that we cannot reach the cluster process_command(&config_offline).unwrap_err(); @@ -558,7 +552,7 @@ fn test_stake_authorize() { request_and_confirm_airdrop( &rpc_client, &faucet_addr, - &config_offline.keypair.pubkey(), + &config_offline.signers[0].pubkey(), 100_000, ) .unwrap(); @@ -566,10 +560,9 @@ fn test_stake_authorize() { // Create stake account, identity is authority let stake_keypair = Keypair::new(); let stake_account_pubkey = stake_keypair.pubkey(); - let (stake_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); + config.signers.push(&stake_keypair); config.command = CliCommand::CreateStakeAccount { - stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), + stake_account: 1, seed: None, staker: None, withdrawer: None, @@ -578,27 +571,26 @@ fn test_stake_authorize() { sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: None, - from: None, + nonce_authority: 0, + fee_payer: 0, + from: 0, }; process_command(&config).unwrap(); // Assign new online stake authority let online_authority = Keypair::new(); let online_authority_pubkey = online_authority.pubkey(); - let (online_authority_file, mut tmp_file) = make_tmp_file(); - write_keypair(&online_authority, tmp_file.as_file_mut()).unwrap(); + config.signers.pop(); config.command = CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: online_authority_pubkey, stake_authorize: StakeAuthorize::Staker, - authority: None, + authority: 0, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap(); @@ -610,16 +602,17 @@ fn test_stake_authorize() { assert_eq!(current_authority, online_authority_pubkey); // Assign new offline stake authority + config.signers.push(&online_authority); config.command = CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: offline_authority_pubkey, stake_authorize: StakeAuthorize::Staker, - authority: Some(read_keypair_file(&online_authority_file).unwrap().into()), + authority: 1, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap(); @@ -633,34 +626,33 @@ fn test_stake_authorize() { // Offline assignment of new nonced stake authority let nonced_authority = Keypair::new(); let nonced_authority_pubkey = nonced_authority.pubkey(); - let (nonced_authority_file, mut tmp_file) = make_tmp_file(); - write_keypair(&nonced_authority, tmp_file.as_file_mut()).unwrap(); let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap(); config_offline.command = CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: nonced_authority_pubkey, stake_authorize: StakeAuthorize::Staker, - authority: Some(read_keypair_file(&offline_authority_file).unwrap().into()), + authority: 0, sign_only: true, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; let sign_reply = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sign_reply); let offline_presigner = presigner_from_pubkey_sigs(&offline_authority_pubkey, &signers).unwrap(); + config.signers = vec![&offline_presigner]; config.command = CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: nonced_authority_pubkey, stake_authorize: StakeAuthorize::Staker, - authority: Some(offline_presigner.clone().into()), + authority: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, - nonce_authority: None, - fee_payer: Some(offline_presigner.clone().into()), + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap(); @@ -676,12 +668,11 @@ fn test_stake_authorize() { .get_minimum_balance_for_rent_exemption(NonceState::size()) .unwrap(); let nonce_account = Keypair::new(); - let (nonce_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap(); + config.signers = vec![&default_signer, &nonce_account]; config.command = CliCommand::CreateNonceAccount { - nonce_account: Rc::new(read_keypair_file(&nonce_keypair_file).unwrap().into()), + nonce_account: 1, seed: None, - nonce_authority: Some(config_offline.keypair.pubkey()), + nonce_authority: Some(offline_authority_pubkey), lamports: minimum_nonce_balance, }; process_command(&config).unwrap(); @@ -697,18 +688,17 @@ fn test_stake_authorize() { // Nonced assignment of new online stake authority let online_authority = Keypair::new(); let online_authority_pubkey = online_authority.pubkey(); - let (_online_authority_file, mut tmp_file) = make_tmp_file(); - write_keypair(&online_authority, tmp_file.as_file_mut()).unwrap(); + config_offline.signers.push(&nonced_authority); config_offline.command = CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: online_authority_pubkey, stake_authorize: StakeAuthorize::Staker, - authority: Some(read_keypair_file(&nonced_authority_file).unwrap().into()), + authority: 1, sign_only: true, blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), nonce_account: Some(nonce_account.pubkey()), - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; let sign_reply = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sign_reply); @@ -717,16 +707,17 @@ fn test_stake_authorize() { presigner_from_pubkey_sigs(&offline_authority_pubkey, &signers).unwrap(); let nonced_authority_presigner = presigner_from_pubkey_sigs(&nonced_authority_pubkey, &signers).unwrap(); + config.signers = vec![&offline_presigner, &nonced_authority_presigner]; config.command = CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: online_authority_pubkey, stake_authorize: StakeAuthorize::Staker, - authority: Some(nonced_authority_presigner.clone().into()), + authority: 1, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_account.pubkey()), - nonce_authority: Some(offline_presigner.clone().into()), - fee_payer: Some(offline_presigner.clone().into()), + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap(); @@ -760,28 +751,30 @@ fn test_stake_authorize_with_fee_payer() { let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new_socket(leader_data.rpc); + let default_signer = Keypair::new(); + let default_pubkey = default_signer.pubkey(); let mut config = CliConfig::default(); config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + config.signers = vec![&default_signer]; let payer_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); - let (payer_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&payer_keypair, tmp_file.as_file_mut()).unwrap(); let mut config_payer = CliConfig::default(); - config_payer.keypair = payer_keypair.into(); + config_payer.signers = vec![&payer_keypair]; config_payer.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); - let payer_pubkey = config_payer.keypair.pubkey(); + let payer_pubkey = config_payer.signers[0].pubkey(); let mut config_offline = CliConfig::default(); - let offline_pubkey = config_offline.keypair.pubkey(); + let offline_signer = Keypair::new(); + config_offline.signers = vec![&offline_signer]; + let offline_pubkey = config_offline.signers[0].pubkey(); // Verify we're offline config_offline.command = CliCommand::ClusterVersion; process_command(&config_offline).unwrap_err(); - request_and_confirm_airdrop(&rpc_client, &faucet_addr, &config.keypair.pubkey(), 100_000) - .unwrap(); - check_balance(100_000, &rpc_client, &config.keypair.pubkey()); + request_and_confirm_airdrop(&rpc_client, &faucet_addr, &default_pubkey, 100_000).unwrap(); + check_balance(100_000, &rpc_client, &config.signers[0].pubkey()); request_and_confirm_airdrop(&rpc_client, &faucet_addr, &payer_pubkey, 100_000).unwrap(); check_balance(100_000, &rpc_client, &payer_pubkey); @@ -792,10 +785,9 @@ fn test_stake_authorize_with_fee_payer() { // Create stake account, identity is authority let stake_keypair = Keypair::new(); let stake_account_pubkey = stake_keypair.pubkey(); - let (stake_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); + config.signers.push(&stake_keypair); config.command = CliCommand::CreateStakeAccount { - stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), + stake_account: 1, seed: None, staker: None, withdrawer: None, @@ -804,44 +796,33 @@ fn test_stake_authorize_with_fee_payer() { sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: None, - from: None, + nonce_authority: 0, + fee_payer: 0, + from: 0, }; process_command(&config).unwrap(); // `config` balance should be 50,000 - 1 stake account sig - 1 fee sig - check_balance( - 50_000 - SIG_FEE - SIG_FEE, - &rpc_client, - &config.keypair.pubkey(), - ); + check_balance(50_000 - SIG_FEE - SIG_FEE, &rpc_client, &default_pubkey); // Assign authority with separate fee payer + config.signers = vec![&default_signer, &payer_keypair]; config.command = CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: offline_pubkey, stake_authorize: StakeAuthorize::Staker, - authority: None, + authority: 0, sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: Some(read_keypair_file(&payer_keypair_file).unwrap().into()), + nonce_authority: 0, + fee_payer: 1, }; process_command(&config).unwrap(); // `config` balance has not changed, despite submitting the TX - check_balance( - 50_000 - SIG_FEE - SIG_FEE, - &rpc_client, - &config.keypair.pubkey(), - ); + check_balance(50_000 - SIG_FEE - SIG_FEE, &rpc_client, &default_pubkey); // `config_payer` however has paid `config`'s authority sig // and `config_payer`'s fee sig - check_balance( - 100_000 - SIG_FEE - SIG_FEE, - &rpc_client, - &config_payer.keypair.pubkey(), - ); + check_balance(100_000 - SIG_FEE - SIG_FEE, &rpc_client, &payer_pubkey); // Assign authority with offline fee payer let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap(); @@ -849,41 +830,34 @@ fn test_stake_authorize_with_fee_payer() { stake_account_pubkey, new_authorized_pubkey: payer_pubkey, stake_authorize: StakeAuthorize::Staker, - authority: None, + authority: 0, sign_only: true, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; let sign_reply = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sign_reply); let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap(); + config.signers = vec![&offline_presigner]; config.command = CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey: payer_pubkey, stake_authorize: StakeAuthorize::Staker, - authority: Some(offline_presigner.clone().into()), + authority: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, - nonce_authority: None, - fee_payer: Some(offline_presigner.clone().into()), + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); // `config`'s balance again has not changed - check_balance( - 50_000 - SIG_FEE - SIG_FEE, - &rpc_client, - &config.keypair.pubkey(), - ); + check_balance(50_000 - SIG_FEE - SIG_FEE, &rpc_client, &default_pubkey); // `config_offline` however has paid 1 sig due to being both authority // and fee payer - check_balance( - 100_000 - SIG_FEE, - &rpc_client, - &config_offline.keypair.pubkey(), - ); + check_balance(100_000 - SIG_FEE, &rpc_client, &offline_pubkey); server.close().unwrap(); remove_dir_all(ledger_path).unwrap(); @@ -899,20 +873,29 @@ fn test_stake_split() { let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new_socket(leader_data.rpc); + let default_signer = Keypair::new(); + let offline_signer = Keypair::new(); let mut config = CliConfig::default(); config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + config.signers = vec![&default_signer]; let mut config_offline = CliConfig::default(); config_offline.json_rpc_url = String::default(); - let offline_pubkey = config_offline.keypair.pubkey(); + config_offline.signers = vec![&offline_signer]; + let offline_pubkey = config_offline.signers[0].pubkey(); // Verify we're offline config_offline.command = CliCommand::ClusterVersion; process_command(&config_offline).unwrap_err(); - request_and_confirm_airdrop(&rpc_client, &faucet_addr, &config.keypair.pubkey(), 500_000) - .unwrap(); - check_balance(500_000, &rpc_client, &config.keypair.pubkey()); + request_and_confirm_airdrop( + &rpc_client, + &faucet_addr, + &config.signers[0].pubkey(), + 500_000, + ) + .unwrap(); + check_balance(500_000, &rpc_client, &config.signers[0].pubkey()); request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000).unwrap(); check_balance(100_000, &rpc_client, &offline_pubkey); @@ -923,10 +906,9 @@ fn test_stake_split() { .unwrap(); let stake_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); let stake_account_pubkey = stake_keypair.pubkey(); - let (stake_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); + config.signers.push(&stake_keypair); config.command = CliCommand::CreateStakeAccount { - stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), + stake_account: 1, seed: None, staker: Some(offline_pubkey), withdrawer: Some(offline_pubkey), @@ -935,9 +917,9 @@ fn test_stake_split() { sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: None, - from: None, + nonce_authority: 0, + fee_payer: 0, + from: 0, }; process_command(&config).unwrap(); check_balance( @@ -951,20 +933,18 @@ fn test_stake_split() { .get_minimum_balance_for_rent_exemption(NonceState::size()) .unwrap(); let nonce_account = keypair_from_seed(&[1u8; 32]).unwrap(); - let nonce_account_pubkey = nonce_account.pubkey(); - let (nonce_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap(); + config.signers = vec![&default_signer, &nonce_account]; config.command = CliCommand::CreateNonceAccount { - nonce_account: Rc::new(read_keypair_file(&nonce_keypair_file).unwrap().into()), + nonce_account: 1, seed: None, nonce_authority: Some(offline_pubkey), lamports: minimum_nonce_balance, }; process_command(&config).unwrap(); - check_balance(minimum_nonce_balance, &rpc_client, &nonce_account_pubkey); + check_balance(minimum_nonce_balance, &rpc_client, &nonce_account.pubkey()); // Fetch nonce hash - let account = rpc_client.get_account(&nonce_account_pubkey).unwrap(); + let account = rpc_client.get_account(&nonce_account.pubkey()).unwrap(); let nonce_state: NonceState = account.state().unwrap(); let nonce_hash = match nonce_state { NonceState::Initialized(_meta, hash) => hash, @@ -973,35 +953,35 @@ fn test_stake_split() { // Nonced offline split let split_account = keypair_from_seed(&[2u8; 32]).unwrap(); - let (split_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&split_account, tmp_file.as_file_mut()).unwrap(); check_balance(0, &rpc_client, &split_account.pubkey()); + config_offline.signers.push(&split_account); config_offline.command = CliCommand::SplitStake { stake_account_pubkey: stake_account_pubkey, - stake_authority: None, + stake_authority: 0, sign_only: true, blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), - nonce_account: Some(nonce_account_pubkey), - nonce_authority: None, - split_stake_account: Rc::new(read_keypair_file(&split_keypair_file).unwrap().into()), + nonce_account: Some(nonce_account.pubkey()), + nonce_authority: 0, + split_stake_account: 1, seed: None, lamports: 2 * minimum_stake_balance, - fee_payer: None, + fee_payer: 0, }; let sig_response = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sig_response); let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap(); + config.signers = vec![&offline_presigner, &split_account]; config.command = CliCommand::SplitStake { stake_account_pubkey: stake_account_pubkey, - stake_authority: Some(offline_presigner.clone().into()), + stake_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), - nonce_account: Some(nonce_account_pubkey), - nonce_authority: Some(offline_presigner.clone().into()), - split_stake_account: Rc::new(read_keypair_file(&split_keypair_file).unwrap().into()), + nonce_account: Some(nonce_account.pubkey()), + nonce_authority: 0, + split_stake_account: 1, seed: None, lamports: 2 * minimum_stake_balance, - fee_payer: Some(offline_presigner.clone().into()), + fee_payer: 0, }; process_command(&config).unwrap(); check_balance( @@ -1029,20 +1009,29 @@ fn test_stake_set_lockup() { let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new_socket(leader_data.rpc); + let default_signer = Keypair::new(); + let offline_signer = Keypair::new(); let mut config = CliConfig::default(); config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + config.signers = vec![&default_signer]; let mut config_offline = CliConfig::default(); config_offline.json_rpc_url = String::default(); - let offline_pubkey = config_offline.keypair.pubkey(); + config_offline.signers = vec![&offline_signer]; + let offline_pubkey = config_offline.signers[0].pubkey(); // Verify we're offline config_offline.command = CliCommand::ClusterVersion; process_command(&config_offline).unwrap_err(); - request_and_confirm_airdrop(&rpc_client, &faucet_addr, &config.keypair.pubkey(), 500_000) - .unwrap(); - check_balance(500_000, &rpc_client, &config.keypair.pubkey()); + request_and_confirm_airdrop( + &rpc_client, + &faucet_addr, + &config.signers[0].pubkey(), + 500_000, + ) + .unwrap(); + check_balance(500_000, &rpc_client, &config.signers[0].pubkey()); request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000).unwrap(); check_balance(100_000, &rpc_client, &offline_pubkey); @@ -1054,14 +1043,13 @@ fn test_stake_set_lockup() { let stake_keypair = keypair_from_seed(&[0u8; 32]).unwrap(); let stake_account_pubkey = stake_keypair.pubkey(); - let (stake_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); let mut lockup = Lockup::default(); - lockup.custodian = config.keypair.pubkey(); + lockup.custodian = config.signers[0].pubkey(); + config.signers.push(&stake_keypair); config.command = CliCommand::CreateStakeAccount { - stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), + stake_account: 1, seed: None, staker: Some(offline_pubkey), withdrawer: Some(offline_pubkey), @@ -1070,9 +1058,9 @@ fn test_stake_set_lockup() { sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: None, - from: None, + nonce_authority: 0, + fee_payer: 0, + from: 0, }; process_command(&config).unwrap(); check_balance( @@ -1087,15 +1075,16 @@ fn test_stake_set_lockup() { epoch: 200, custodian: Pubkey::default(), }; + config.signers.pop(); config.command = CliCommand::StakeSetLockup { stake_account_pubkey, lockup, - custodian: None, + custodian: 0, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap(); @@ -1104,14 +1093,12 @@ fn test_stake_set_lockup() { StakeState::Initialized(meta) => meta.lockup, _ => panic!("Unexpected stake state!"), }; - lockup.custodian = config.keypair.pubkey(); // Default new_custodian is config.keypair + lockup.custodian = config.signers[0].pubkey(); // Default new_custodian is config.signers[0] assert_eq!(current_lockup, lockup); // Set custodian to another pubkey let online_custodian = Keypair::new(); let online_custodian_pubkey = online_custodian.pubkey(); - let (online_custodian_file, mut tmp_file) = make_tmp_file(); - write_keypair(&online_custodian, tmp_file.as_file_mut()).unwrap(); let lockup = Lockup { unix_timestamp: 1581534571, @@ -1121,12 +1108,12 @@ fn test_stake_set_lockup() { config.command = CliCommand::StakeSetLockup { stake_account_pubkey, lockup, - custodian: None, + custodian: 0, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); @@ -1135,15 +1122,16 @@ fn test_stake_set_lockup() { epoch: 202, custodian: Pubkey::default(), }; + config.signers = vec![&default_signer, &online_custodian]; config.command = CliCommand::StakeSetLockup { stake_account_pubkey, lockup, - custodian: Some(read_keypair_file(&online_custodian_file).unwrap().into()), + custodian: 1, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap(); @@ -1164,12 +1152,12 @@ fn test_stake_set_lockup() { config.command = CliCommand::StakeSetLockup { stake_account_pubkey, lockup, - custodian: Some(online_custodian.into()), + custodian: 1, sign_only: false, blockhash_query: BlockhashQuery::default(), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); @@ -1179,10 +1167,9 @@ fn test_stake_set_lockup() { .unwrap(); let nonce_account = keypair_from_seed(&[1u8; 32]).unwrap(); let nonce_account_pubkey = nonce_account.pubkey(); - let (nonce_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap(); + config.signers = vec![&default_signer, &nonce_account]; config.command = CliCommand::CreateNonceAccount { - nonce_account: Rc::new(read_keypair_file(&nonce_keypair_file).unwrap().into()), + nonce_account: 1, seed: None, nonce_authority: Some(offline_pubkey), lamports: minimum_nonce_balance, @@ -1207,25 +1194,26 @@ fn test_stake_set_lockup() { config_offline.command = CliCommand::StakeSetLockup { stake_account_pubkey, lockup, - custodian: None, + custodian: 0, sign_only: true, blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), nonce_account: Some(nonce_account_pubkey), - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; let sig_response = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sig_response); let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap(); + config.signers = vec![&offline_presigner]; config.command = CliCommand::StakeSetLockup { stake_account_pubkey, lockup, - custodian: Some(offline_presigner.clone().into()), + custodian: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_account_pubkey), - nonce_authority: Some(offline_presigner.clone().into()), - fee_payer: Some(offline_presigner.clone().into()), + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap(); @@ -1252,29 +1240,30 @@ fn test_offline_nonced_create_stake_account_and_withdraw() { let rpc_client = RpcClient::new_socket(leader_data.rpc); let mut config = CliConfig::default(); - config.keypair = keypair_from_seed(&[1u8; 32]).unwrap().into(); + let default_signer = keypair_from_seed(&[1u8; 32]).unwrap(); + config.signers = vec![&default_signer]; config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); let mut config_offline = CliConfig::default(); - config_offline.keypair = keypair_from_seed(&[2u8; 32]).unwrap().into(); - let offline_pubkey = config_offline.keypair.pubkey(); + let offline_signer = keypair_from_seed(&[2u8; 32]).unwrap(); + config_offline.signers = vec![&offline_signer]; + let offline_pubkey = config_offline.signers[0].pubkey(); config_offline.json_rpc_url = String::default(); config_offline.command = CliCommand::ClusterVersion; // Verfiy that we cannot reach the cluster process_command(&config_offline).unwrap_err(); - request_and_confirm_airdrop(&rpc_client, &faucet_addr, &config.keypair.pubkey(), 200_000) - .unwrap(); - check_balance(200_000, &rpc_client, &config.keypair.pubkey()); - request_and_confirm_airdrop( &rpc_client, &faucet_addr, - &config_offline.keypair.pubkey(), - 100_000, + &config.signers[0].pubkey(), + 200_000, ) .unwrap(); - check_balance(100_000, &rpc_client, &config_offline.keypair.pubkey()); + check_balance(200_000, &rpc_client, &config.signers[0].pubkey()); + + request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000).unwrap(); + check_balance(100_000, &rpc_client, &offline_pubkey); // Create nonce account let minimum_nonce_balance = rpc_client @@ -1282,10 +1271,9 @@ fn test_offline_nonced_create_stake_account_and_withdraw() { .unwrap(); let nonce_account = keypair_from_seed(&[3u8; 32]).unwrap(); let nonce_pubkey = nonce_account.pubkey(); - let (nonce_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap(); + config.signers.push(&nonce_account); config.command = CliCommand::CreateNonceAccount { - nonce_account: Rc::new(read_keypair_file(&nonce_keypair_file).unwrap().into()), + nonce_account: 1, seed: None, nonce_authority: Some(offline_pubkey), lamports: minimum_nonce_balance, @@ -1303,10 +1291,9 @@ fn test_offline_nonced_create_stake_account_and_withdraw() { // Create stake account offline let stake_keypair = keypair_from_seed(&[4u8; 32]).unwrap(); let stake_pubkey = stake_keypair.pubkey(); - let (stake_keypair_file, mut tmp_file) = make_tmp_file(); - write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); + config_offline.signers.push(&stake_keypair); config_offline.command = CliCommand::CreateStakeAccount { - stake_account: Rc::new(read_keypair_file(&stake_keypair_file).unwrap().into()), + stake_account: 1, seed: None, staker: None, withdrawer: None, @@ -1315,17 +1302,17 @@ fn test_offline_nonced_create_stake_account_and_withdraw() { sign_only: true, blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), nonce_account: Some(nonce_pubkey), - nonce_authority: None, - fee_payer: None, - from: None, + nonce_authority: 0, + fee_payer: 0, + from: 0, }; let sig_response = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sig_response); let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap(); + let stake_presigner = presigner_from_pubkey_sigs(&stake_pubkey, &signers).unwrap(); + config.signers = vec![&offline_presigner, &stake_presigner]; config.command = CliCommand::CreateStakeAccount { - stake_account: presigner_from_pubkey_sigs(&stake_pubkey, &signers) - .map(|p| Rc::new(p.into())) - .unwrap(), + stake_account: 1, seed: None, staker: Some(offline_pubkey), withdrawer: None, @@ -1334,9 +1321,9 @@ fn test_offline_nonced_create_stake_account_and_withdraw() { sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_pubkey), - nonce_authority: Some(offline_presigner.clone().into()), - fee_payer: Some(offline_presigner.clone().into()), - from: Some(offline_presigner.clone().into()), + nonce_authority: 0, + fee_payer: 0, + from: 0, }; process_command(&config).unwrap(); check_balance(50_000, &rpc_client, &stake_pubkey); @@ -1352,30 +1339,32 @@ fn test_offline_nonced_create_stake_account_and_withdraw() { // Offline, nonced stake-withdraw let recipient = keypair_from_seed(&[5u8; 32]).unwrap(); let recipient_pubkey = recipient.pubkey(); + config_offline.signers.pop(); config_offline.command = CliCommand::WithdrawStake { stake_account_pubkey: stake_pubkey, destination_account_pubkey: recipient_pubkey, lamports: 42, - withdraw_authority: None, + withdraw_authority: 0, sign_only: true, blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), nonce_account: Some(nonce_pubkey), - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; let sig_response = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sig_response); let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap(); + config.signers = vec![&offline_presigner]; config.command = CliCommand::WithdrawStake { stake_account_pubkey: stake_pubkey, destination_account_pubkey: recipient_pubkey, lamports: 42, - withdraw_authority: Some(offline_presigner.clone().into()), + withdraw_authority: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_pubkey), - nonce_authority: Some(offline_presigner.clone().into()), - fee_payer: Some(offline_presigner.clone().into()), + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); check_balance(42, &rpc_client, &recipient_pubkey); @@ -1390,8 +1379,9 @@ fn test_offline_nonced_create_stake_account_and_withdraw() { // Create another stake account. This time with seed let seed = "seedy"; + config_offline.signers = vec![&offline_signer, &stake_keypair]; config_offline.command = CliCommand::CreateStakeAccount { - stake_account: Rc::new(Box::new(read_keypair_file(&stake_keypair_file).unwrap())), + stake_account: 1, seed: Some(seed.to_string()), staker: None, withdrawer: None, @@ -1400,16 +1390,17 @@ fn test_offline_nonced_create_stake_account_and_withdraw() { sign_only: true, blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), nonce_account: Some(nonce_pubkey), - nonce_authority: None, - fee_payer: None, - from: None, + nonce_authority: 0, + fee_payer: 0, + from: 0, }; let sig_response = process_command(&config_offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sig_response); let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap(); let stake_presigner = presigner_from_pubkey_sigs(&stake_pubkey, &signers).unwrap(); + config.signers = vec![&offline_presigner, &stake_presigner]; config.command = CliCommand::CreateStakeAccount { - stake_account: Rc::new(stake_presigner.into()), + stake_account: 1, seed: Some(seed.to_string()), staker: Some(offline_pubkey.into()), withdrawer: Some(offline_pubkey.into()), @@ -1418,9 +1409,9 @@ fn test_offline_nonced_create_stake_account_and_withdraw() { sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_pubkey), - nonce_authority: Some(offline_presigner.clone().into()), - fee_payer: Some(offline_presigner.clone().into()), - from: Some(offline_presigner.clone().into()), + nonce_authority: 0, + fee_payer: 0, + from: 0, }; process_command(&config).unwrap(); let seed_address = diff --git a/cli/tests/transfer.rs b/cli/tests/transfer.rs index 9b5476aa77..a276128f1a 100644 --- a/cli/tests/transfer.rs +++ b/cli/tests/transfer.rs @@ -10,22 +10,12 @@ use solana_sdk::{ fee_calculator::FeeCalculator, nonce_state::NonceState, pubkey::Pubkey, - signature::{keypair_from_seed, read_keypair_file, write_keypair, Signer}, + signature::{keypair_from_seed, Keypair, Signer}, }; -use std::fs::remove_dir_all; -use std::sync::mpsc::channel; +use std::{fs::remove_dir_all, sync::mpsc::channel, thread::sleep, time::Duration}; #[cfg(test)] use solana_core::validator::new_validator_for_tests_ex; -use std::rc::Rc; -use std::thread::sleep; -use std::time::Duration; -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 check_balance(expected_balance: u64, client: &RpcClient, pubkey: &Pubkey) { (0..5).for_each(|tries| { @@ -50,13 +40,15 @@ fn test_transfer() { let rpc_client = RpcClient::new_socket(leader_data.rpc); + let default_signer = Keypair::new(); + let default_offline_signer = Keypair::new(); + let mut config = CliConfig::default(); config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + config.signers = vec![&default_signer]; - let sender_pubkey = config.keypair.pubkey(); + let sender_pubkey = config.signers[0].pubkey(); let recipient_pubkey = Pubkey::new(&[1u8; 32]); - println!("sender: {:?}", sender_pubkey); - println!("recipient: {:?}", recipient_pubkey); request_and_confirm_airdrop(&rpc_client, &faucet_addr, &sender_pubkey, 50_000).unwrap(); check_balance(50_000, &rpc_client, &sender_pubkey); @@ -66,12 +58,12 @@ fn test_transfer() { config.command = CliCommand::Transfer { lamports: 10, to: recipient_pubkey, - from: None, + from: 0, sign_only: false, blockhash_query: BlockhashQuery::All, nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); check_balance(49_989, &rpc_client, &sender_pubkey); @@ -79,12 +71,12 @@ fn test_transfer() { let mut offline = CliConfig::default(); offline.json_rpc_url = String::default(); + offline.signers = vec![&default_offline_signer]; // Verify we cannot contact the cluster offline.command = CliCommand::ClusterVersion; process_command(&offline).unwrap_err(); - let offline_pubkey = offline.keypair.pubkey(); - println!("offline: {:?}", offline_pubkey); + let offline_pubkey = offline.signers[0].pubkey(); request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 50).unwrap(); check_balance(50, &rpc_client, &offline_pubkey); @@ -93,25 +85,26 @@ fn test_transfer() { offline.command = CliCommand::Transfer { lamports: 10, to: recipient_pubkey, - from: None, + from: 0, sign_only: true, blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()), nonce_account: None, - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; let sign_only_reply = process_command(&offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sign_only_reply); let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap(); + config.signers = vec![&offline_presigner]; config.command = CliCommand::Transfer { lamports: 10, to: recipient_pubkey, - from: Some(offline_presigner.clone().into()), + from: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: None, - nonce_authority: None, - fee_payer: Some(offline_presigner.clone().into()), + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); check_balance(39, &rpc_client, &offline_pubkey); @@ -119,13 +112,12 @@ fn test_transfer() { // Create nonce account let nonce_account = keypair_from_seed(&[3u8; 32]).unwrap(); - let (nonce_account_file, mut tmp_file) = make_tmp_file(); - write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap(); let minimum_nonce_balance = rpc_client .get_minimum_balance_for_rent_exemption(NonceState::size()) .unwrap(); + config.signers = vec![&default_signer, &nonce_account]; config.command = CliCommand::CreateNonceAccount { - nonce_account: Rc::new(read_keypair_file(&nonce_account_file).unwrap().into()), + nonce_account: 1, seed: None, nonce_authority: None, lamports: minimum_nonce_balance, @@ -142,15 +134,16 @@ fn test_transfer() { }; // Nonced transfer + config.signers = vec![&default_signer]; config.command = CliCommand::Transfer { lamports: 10, to: recipient_pubkey, - from: None, + from: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(nonce_hash), nonce_account: Some(nonce_account.pubkey()), - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); check_balance(49_976 - minimum_nonce_balance, &rpc_client, &sender_pubkey); @@ -164,9 +157,10 @@ fn test_transfer() { assert_ne!(nonce_hash, new_nonce_hash); // Assign nonce authority to offline + config.signers = vec![&default_signer]; config.command = CliCommand::AuthorizeNonceAccount { nonce_account: nonce_account.pubkey(), - nonce_authority: None, + nonce_authority: 0, new_authority: offline_pubkey, }; process_command(&config).unwrap(); @@ -181,28 +175,30 @@ fn test_transfer() { }; // Offline, nonced transfer + offline.signers = vec![&default_offline_signer]; offline.command = CliCommand::Transfer { lamports: 10, to: recipient_pubkey, - from: None, + from: 0, sign_only: true, blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()), nonce_account: Some(nonce_account.pubkey()), - nonce_authority: None, - fee_payer: None, + nonce_authority: 0, + fee_payer: 0, }; let sign_only_reply = process_command(&offline).unwrap(); let (blockhash, signers) = parse_sign_only_reply_string(&sign_only_reply); let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap(); + config.signers = vec![&offline_presigner]; config.command = CliCommand::Transfer { lamports: 10, to: recipient_pubkey, - from: Some(offline_presigner.clone().into()), + from: 0, sign_only: false, blockhash_query: BlockhashQuery::FeeCalculator(blockhash), nonce_account: Some(nonce_account.pubkey()), - nonce_authority: Some(offline_presigner.clone().into()), - fee_payer: Some(offline_presigner.clone().into()), + nonce_authority: 0, + fee_payer: 0, }; process_command(&config).unwrap(); check_balance(28, &rpc_client, &offline_pubkey); diff --git a/keygen/src/keygen.rs b/keygen/src/keygen.rs index f9bacd39b1..cbf071138a 100644 --- a/keygen/src/keygen.rs +++ b/keygen/src/keygen.rs @@ -8,11 +8,12 @@ use num_cpus; use solana_clap_utils::{ input_validators::is_derivation, keypair::{ - keypair_from_seed_phrase, keypair_util_from_path, prompt_passphrase, + keypair_from_seed_phrase, prompt_passphrase, signer_from_path, SKIP_SEED_PHRASE_VALIDATION_ARG, }, }; use solana_cli_config::config::{Config, CONFIG_FILE}; +use solana_remote_wallet::remote_wallet::{maybe_wallet_manager, RemoteWalletManager}; use solana_sdk::{ pubkey::write_pubkey_file, signature::{keypair_from_seed, write_keypair, write_keypair_file, Keypair, Signer}, @@ -49,6 +50,7 @@ fn check_for_overwrite(outfile: &str, matches: &ArgMatches) { fn get_keypair_from_matches( matches: &ArgMatches, config: Config, + wallet_manager: Option>, ) -> Result, Box> { let mut path = dirs::home_dir().expect("home directory"); let path = if matches.is_present("keypair") { @@ -59,7 +61,7 @@ fn get_keypair_from_matches( path.extend(&[".config", "solana", "id.json"]); path.to_str().unwrap() }; - keypair_util_from_path(matches, path, "pubkey recovery") + signer_from_path(matches, path, "pubkey recovery", wallet_manager.as_ref()) } fn output_keypair( @@ -380,9 +382,11 @@ fn main() -> Result<(), Box> { Config::default() }; + let wallet_manager = maybe_wallet_manager()?; + match matches.subcommand() { ("pubkey", Some(matches)) => { - let pubkey = get_keypair_from_matches(matches, config)?.try_pubkey()?; + let pubkey = get_keypair_from_matches(matches, config, wallet_manager)?.try_pubkey()?; if matches.is_present("outfile") { let outfile = matches.value_of("outfile").unwrap(); @@ -559,7 +563,7 @@ fn main() -> Result<(), Box> { } } ("verify", Some(matches)) => { - let keypair = get_keypair_from_matches(matches, config)?; + let keypair = get_keypair_from_matches(matches, config, wallet_manager)?; let test_data = b"test"; let signature = keypair.try_sign_message(test_data)?; let pubkey_bs58 = matches.value_of("pubkey").unwrap(); diff --git a/remote-wallet/src/ledger.rs b/remote-wallet/src/ledger.rs index 315ced43db..4937e2c4b4 100644 --- a/remote-wallet/src/ledger.rs +++ b/remote-wallet/src/ledger.rs @@ -1,5 +1,5 @@ use crate::remote_wallet::{ - initialize_wallet_manager, DerivationPath, RemoteWallet, RemoteWalletError, RemoteWalletInfo, + DerivationPath, RemoteWallet, RemoteWalletError, RemoteWalletInfo, RemoteWalletManager, }; use dialoguer::{theme::ColorfulTheme, Select}; use log::*; @@ -378,9 +378,8 @@ fn extend_and_serialize(derivation_path: &DerivationPath) -> Vec { /// Choose a Ledger wallet based on matching info fields pub fn get_ledger_from_info( info: RemoteWalletInfo, + wallet_manager: &RemoteWalletManager, ) -> Result, RemoteWalletError> { - let wallet_manager = initialize_wallet_manager()?; - let _device_count = wallet_manager.update_devices()?; let devices = wallet_manager.list_devices(); let (pubkeys, device_paths): (Vec, Vec) = devices .iter() diff --git a/remote-wallet/src/remote_keypair.rs b/remote-wallet/src/remote_keypair.rs index 06ebaa1733..d6c740f20b 100644 --- a/remote-wallet/src/remote_keypair.rs +++ b/remote-wallet/src/remote_keypair.rs @@ -1,7 +1,8 @@ use crate::{ ledger::get_ledger_from_info, remote_wallet::{ - DerivationPath, RemoteWallet, RemoteWalletError, RemoteWalletInfo, RemoteWalletType, + DerivationPath, RemoteWallet, RemoteWalletError, RemoteWalletInfo, RemoteWalletManager, + RemoteWalletType, }, }; use solana_sdk::{ @@ -12,24 +13,29 @@ use solana_sdk::{ pub struct RemoteKeypair { pub wallet_type: RemoteWalletType, pub derivation_path: DerivationPath, + pub pubkey: Pubkey, } impl RemoteKeypair { - pub fn new(wallet_type: RemoteWalletType, derivation_path: DerivationPath) -> Self { - Self { + pub fn new( + wallet_type: RemoteWalletType, + derivation_path: DerivationPath, + ) -> Result { + let pubkey = match &wallet_type { + RemoteWalletType::Ledger(wallet) => wallet.get_pubkey(&derivation_path)?, + }; + + Ok(Self { wallet_type, derivation_path, - } + pubkey, + }) } } impl Signer for RemoteKeypair { fn try_pubkey(&self) -> Result { - match &self.wallet_type { - RemoteWalletType::Ledger(wallet) => wallet - .get_pubkey(&self.derivation_path) - .map_err(|e| e.into()), - } + Ok(self.pubkey) } fn try_sign_message(&self, message: &[u8]) -> Result { @@ -44,17 +50,18 @@ impl Signer for RemoteKeypair { pub fn generate_remote_keypair( path: String, explicit_derivation_path: Option, + wallet_manager: &RemoteWalletManager, ) -> Result { let (remote_wallet_info, mut derivation_path) = RemoteWalletInfo::parse_path(path)?; if let Some(derivation) = explicit_derivation_path { derivation_path = derivation; } if remote_wallet_info.manufacturer == "ledger" { - let ledger = get_ledger_from_info(remote_wallet_info)?; - Ok(RemoteKeypair { - wallet_type: RemoteWalletType::Ledger(ledger), + let ledger = get_ledger_from_info(remote_wallet_info, wallet_manager)?; + Ok(RemoteKeypair::new( + RemoteWalletType::Ledger(ledger), derivation_path, - }) + )?) } else { Err(RemoteWalletError::DeviceTypeMismatch) } diff --git a/remote-wallet/src/remote_wallet.rs b/remote-wallet/src/remote_wallet.rs index e5037faeb9..2895c14961 100644 --- a/remote-wallet/src/remote_wallet.rs +++ b/remote-wallet/src/remote_wallet.rs @@ -299,6 +299,17 @@ pub fn initialize_wallet_manager() -> Result, RemoteWal Ok(RemoteWalletManager::new(hidapi)) } +pub fn maybe_wallet_manager() -> Result>, RemoteWalletError> { + let wallet_manager = initialize_wallet_manager()?; + let device_count = wallet_manager.update_devices()?; + if device_count > 0 { + Ok(Some(wallet_manager)) + } else { + drop(wallet_manager); + Ok(None) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/sdk/src/signature.rs b/sdk/src/signature.rs index 838b5e1474..8f6dad2392 100644 --- a/sdk/src/signature.rs +++ b/sdk/src/signature.rs @@ -183,8 +183,8 @@ impl From for Box where T: Signer + 'static, { - fn from(keypair_util: T) -> Self { - Box::new(keypair_util) + fn from(signer: T) -> Self { + Box::new(signer) } }