From 700685c223e092eaf83d720ba90a85be09afc0b7 Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Thu, 18 Feb 2021 00:08:12 -0700 Subject: [PATCH] CLI: Add hidden support for `SystemInstruction::TransferWithSeed` --- cli/src/cli.rs | 103 +++++++++++++++++++++++++++++++++++++++++- cli/tests/nonce.rs | 4 ++ cli/tests/transfer.rs | 78 ++++++++++++++++++++++++++++++++ cli/tests/vote.rs | 2 + 4 files changed, 185 insertions(+), 2 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index a97ff779fe..73808643ea 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -355,6 +355,8 @@ pub enum CliCommand { nonce_account: Option, nonce_authority: SignerIndex, fee_payer: SignerIndex, + derived_address_seed: Option, + derived_address_program_id: Option, }, } @@ -858,6 +860,12 @@ pub fn parse_command( let signer_info = default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; + let derived_address_seed = matches + .value_of("derived_address_seed") + .map(|s| s.to_string()); + let derived_address_program_id = + resolve_derived_address_program_id(matches, "derived_address_program_id"); + Ok(CliCommandInfo { command: CliCommand::Transfer { amount, @@ -869,6 +877,8 @@ pub fn parse_command( 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(), + derived_address_seed, + derived_address_program_id, }, signers: signer_info.signers, }) @@ -1106,8 +1116,11 @@ fn process_transfer( nonce_account: Option<&Pubkey>, nonce_authority: SignerIndex, fee_payer: SignerIndex, + derived_address_seed: Option, + derived_address_program_id: Option<&Pubkey>, ) -> ProcessResult { let from = config.signers[from]; + let mut from_pubkey = from.pubkey(); let (recent_blockhash, fee_calculator) = blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?; @@ -1115,8 +1128,28 @@ fn process_transfer( let nonce_authority = config.signers[nonce_authority]; let fee_payer = config.signers[fee_payer]; + let derived_parts = derived_address_seed.zip(derived_address_program_id); + let with_seed = if let Some((seed, program_id)) = derived_parts { + let base_pubkey = from_pubkey; + from_pubkey = Pubkey::create_with_seed(&base_pubkey, &seed, program_id)?; + Some((base_pubkey, seed, program_id, from_pubkey)) + } else { + None + }; + let build_message = |lamports| { - let ixs = vec![system_instruction::transfer(&from.pubkey(), to, lamports)]; + let ixs = if let Some((base_pubkey, seed, program_id, from_pubkey)) = with_seed.as_ref() { + vec![system_instruction::transfer_with_seed( + from_pubkey, + base_pubkey, + seed.clone(), + program_id, + to, + lamports, + )] + } else { + vec![system_instruction::transfer(&from_pubkey, to, lamports)] + }; if let Some(nonce_account) = &nonce_account { Message::new_with_nonce( @@ -1135,7 +1168,7 @@ fn process_transfer( sign_only, amount, &fee_calculator, - &from.pubkey(), + &from_pubkey, &fee_payer.pubkey(), build_message, config.commitment, @@ -1741,6 +1774,8 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { ref nonce_account, nonce_authority, fee_payer, + derived_address_seed, + ref derived_address_program_id, } => process_transfer( &rpc_client, config, @@ -1753,6 +1788,8 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { nonce_account.as_ref(), *nonce_authority, *fee_payer, + derived_address_seed.clone(), + derived_address_program_id.as_ref(), ), } } @@ -2086,6 +2123,23 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .takes_value(false) .help("Return signature immediately after submitting the transaction, instead of waiting for confirmations"), ) + .arg( + Arg::with_name("derived_address_seed") + .long("derived-address-seed") + .takes_value(true) + .value_name("SEED_STRING") + .requires("derived_address_program_id") + .validator(is_derived_address_seed) + .hidden(true) + ) + .arg( + Arg::with_name("derived_address_program_id") + .long("derived-address-program-id") + .takes_value(true) + .value_name("PROGRAM_ID") + .requires("derived_address_seed") + .hidden(true) + ) .offline_args() .nonce_args(false) .arg(fee_payer_arg()), @@ -2787,6 +2841,8 @@ mod tests { nonce_account: None, nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }, signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } @@ -2809,6 +2865,8 @@ mod tests { nonce_account: None, nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }, signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } @@ -2835,6 +2893,8 @@ mod tests { nonce_account: None, nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }, signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } @@ -2865,6 +2925,8 @@ mod tests { nonce_account: None, nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }, signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()], } @@ -2903,6 +2965,8 @@ mod tests { nonce_account: None, nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }, signers: vec![Presigner::new(&from_pubkey, &from_sig).into()], } @@ -2942,6 +3006,8 @@ mod tests { nonce_account: Some(nonce_address), nonce_authority: 1, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }, signers: vec![ read_keypair_file(&default_keypair_file).unwrap().into(), @@ -2949,5 +3015,38 @@ mod tests { ], } ); + + //Test Transfer Subcommand, with seed + let derived_address_seed = "seed".to_string(); + let derived_address_program_id = "STAKE"; + let test_transfer = test_commands.clone().get_matches_from(vec![ + "test", + "transfer", + &to_string, + "42", + "--derived-address-seed", + &derived_address_seed, + "--derived-address-program-id", + derived_address_program_id, + ]); + assert_eq!( + parse_command(&test_transfer, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Transfer { + amount: SpendAmount::Some(42_000_000_000), + to: to_pubkey, + from: 0, + sign_only: false, + no_wait: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, + fee_payer: 0, + derived_address_seed: Some(derived_address_seed), + derived_address_program_id: Some(solana_stake_program::id()), + }, + signers: vec![read_keypair_file(&default_keypair_file).unwrap().into(),], + } + ); } } diff --git a/cli/tests/nonce.rs b/cli/tests/nonce.rs index b3cc3fbed3..6c2722e305 100644 --- a/cli/tests/nonce.rs +++ b/cli/tests/nonce.rs @@ -298,6 +298,8 @@ fn test_create_account_with_seed() { nonce_account: Some(nonce_address), nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }; authority_config.output_format = OutputFormat::JsonCompact; let sign_only_reply = process_command(&authority_config).unwrap(); @@ -322,6 +324,8 @@ fn test_create_account_with_seed() { nonce_account: Some(nonce_address), nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }; process_command(&submit_config).unwrap(); check_recent_balance(241, &rpc_client, &nonce_address); diff --git a/cli/tests/transfer.rs b/cli/tests/transfer.rs index 54bc2fd4ac..09bf63ccc0 100644 --- a/cli/tests/transfer.rs +++ b/cli/tests/transfer.rs @@ -56,6 +56,8 @@ fn test_transfer() { nonce_account: None, nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }; process_command(&config).unwrap(); check_recent_balance(49_989, &rpc_client, &sender_pubkey); @@ -72,6 +74,8 @@ fn test_transfer() { nonce_account: None, nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }; assert!(process_command(&config).is_err()); check_recent_balance(49_989, &rpc_client, &sender_pubkey); @@ -100,6 +104,8 @@ fn test_transfer() { nonce_account: None, nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }; offline.output_format = OutputFormat::JsonCompact; let sign_only_reply = process_command(&offline).unwrap(); @@ -117,6 +123,8 @@ fn test_transfer() { nonce_account: None, nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }; process_command(&config).unwrap(); check_recent_balance(39, &rpc_client, &offline_pubkey); @@ -162,6 +170,8 @@ fn test_transfer() { nonce_account: Some(nonce_account.pubkey()), nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }; process_command(&config).unwrap(); check_recent_balance(49_976 - minimum_nonce_balance, &rpc_client, &sender_pubkey); @@ -208,6 +218,8 @@ fn test_transfer() { nonce_account: Some(nonce_account.pubkey()), nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }; let sign_only_reply = process_command(&offline).unwrap(); let sign_only = parse_sign_only_reply_string(&sign_only_reply); @@ -227,6 +239,8 @@ fn test_transfer() { nonce_account: Some(nonce_account.pubkey()), nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }; process_command(&config).unwrap(); check_recent_balance(28, &rpc_client, &offline_pubkey); @@ -290,6 +304,8 @@ fn test_transfer_multisession_signing() { nonce_account: None, nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }; fee_payer_config.output_format = OutputFormat::JsonCompact; let sign_only_reply = process_command(&fee_payer_config).unwrap(); @@ -316,6 +332,8 @@ fn test_transfer_multisession_signing() { nonce_account: None, nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }; from_config.output_format = OutputFormat::JsonCompact; let sign_only_reply = process_command(&from_config).unwrap(); @@ -339,6 +357,8 @@ fn test_transfer_multisession_signing() { nonce_account: None, nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }; process_command(&config).unwrap(); @@ -384,8 +404,66 @@ fn test_transfer_all() { nonce_account: None, nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }; process_command(&config).unwrap(); check_recent_balance(0, &rpc_client, &sender_pubkey); check_recent_balance(49_999, &rpc_client, &recipient_pubkey); } + +#[test] +fn test_transfer_with_seed() { + solana_logger::setup(); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1); + let faucet_addr = run_local_faucet(mint_keypair, None); + + let rpc_client = + RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed()); + + let default_signer = Keypair::new(); + + let mut config = CliConfig::recent_for_tests(); + config.json_rpc_url = test_validator.rpc_url(); + config.signers = vec![&default_signer]; + + let sender_pubkey = config.signers[0].pubkey(); + let recipient_pubkey = Pubkey::new(&[1u8; 32]); + let derived_address_seed = "seed".to_string(); + let derived_address_program_id = solana_stake_program::id(); + let derived_address = Pubkey::create_with_seed( + &sender_pubkey, + &derived_address_seed, + &derived_address_program_id, + ) + .unwrap(); + + request_and_confirm_airdrop(&rpc_client, &faucet_addr, &sender_pubkey, 1, &config).unwrap(); + request_and_confirm_airdrop(&rpc_client, &faucet_addr, &derived_address, 50_000, &config) + .unwrap(); + check_recent_balance(1, &rpc_client, &sender_pubkey); + check_recent_balance(50_000, &rpc_client, &derived_address); + check_recent_balance(0, &rpc_client, &recipient_pubkey); + + check_ready(&rpc_client); + + // Transfer with seed + config.command = CliCommand::Transfer { + amount: SpendAmount::Some(50_000), + to: recipient_pubkey, + from: 0, + sign_only: false, + no_wait: false, + blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), + nonce_account: None, + nonce_authority: 0, + fee_payer: 0, + derived_address_seed: Some(derived_address_seed), + derived_address_program_id: Some(derived_address_program_id), + }; + process_command(&config).unwrap(); + check_recent_balance(0, &rpc_client, &sender_pubkey); + check_recent_balance(50_000, &rpc_client, &recipient_pubkey); + check_recent_balance(0, &rpc_client, &derived_address); +} diff --git a/cli/tests/vote.rs b/cli/tests/vote.rs index 6639c75605..66bf0c1c19 100644 --- a/cli/tests/vote.rs +++ b/cli/tests/vote.rs @@ -76,6 +76,8 @@ fn test_vote_authorize_and_withdraw() { nonce_account: None, nonce_authority: 0, fee_payer: 0, + derived_address_seed: None, + derived_address_program_id: None, }; process_command(&config).unwrap(); let expected_balance = expected_balance + 1_000;