| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 |
- #![allow(clippy::arithmetic_side_effects)]
- use {
- solana_account::ReadableAccount,
- solana_cli::{
- check_balance,
- cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
- spend_utils::SpendAmount,
- },
- solana_cli_output::{parse_sign_only_reply_string, OutputFormat},
- solana_commitment_config::CommitmentConfig,
- solana_faucet::faucet::run_local_faucet_with_unique_port_for_tests,
- solana_keypair::Keypair,
- solana_net_utils::SocketAddrSpace,
- solana_rpc_client::rpc_client::RpcClient,
- solana_rpc_client_nonce_utils::blockhash_query::{self, BlockhashQuery},
- solana_signer::{null_signer::NullSigner, Signer},
- solana_test_validator::TestValidator,
- solana_vote_program::vote_state::{VoteAuthorize, VoteStateV4},
- test_case::test_case,
- };
- #[test_case(None; "base")]
- #[test_case(Some(1_000_000); "with_compute_unit_price")]
- fn test_vote_authorize_and_withdraw(compute_unit_price: Option<u64>) {
- let mint_keypair = Keypair::new();
- let mint_pubkey = mint_keypair.pubkey();
- let faucet_addr = run_local_faucet_with_unique_port_for_tests(mint_keypair);
- let test_validator =
- TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
- 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];
- request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 100_000)
- .unwrap();
- // Create vote account
- let vote_account_keypair = Keypair::new();
- let vote_account_pubkey = vote_account_keypair.pubkey();
- config.signers = vec![&default_signer, &vote_account_keypair];
- config.command = CliCommand::CreateVoteAccount {
- vote_account: 1,
- seed: None,
- identity_account: 0,
- authorized_voter: None,
- authorized_withdrawer: config.signers[0].pubkey(),
- commission: 0,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- let vote_account = rpc_client
- .get_account(&vote_account_keypair.pubkey())
- .unwrap();
- let vote_state =
- VoteStateV4::deserialize(vote_account.data(), &vote_account_keypair.pubkey()).unwrap();
- let authorized_withdrawer = vote_state.authorized_withdrawer;
- assert_eq!(authorized_withdrawer, config.signers[0].pubkey());
- let expected_balance = rpc_client
- .get_minimum_balance_for_rent_exemption(VoteStateV4::size_of())
- .unwrap()
- .max(1);
- check_balance!(expected_balance, &rpc_client, &vote_account_pubkey);
- // Transfer in some more SOL
- config.signers = vec![&default_signer];
- config.command = CliCommand::Transfer {
- amount: SpendAmount::Some(10_000),
- to: vote_account_pubkey,
- from: 0,
- sign_only: false,
- dump_transaction_message: false,
- allow_unfunded_recipient: true,
- no_wait: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- derived_address_seed: None,
- derived_address_program_id: None,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- let expected_balance = expected_balance + 10_000;
- check_balance!(expected_balance, &rpc_client, &vote_account_pubkey);
- // Authorize vote account withdrawal to another signer
- let first_withdraw_authority = Keypair::new();
- config.signers = vec![&default_signer];
- config.command = CliCommand::VoteAuthorize {
- vote_account_pubkey,
- new_authorized_pubkey: first_withdraw_authority.pubkey(),
- vote_authorize: VoteAuthorize::Withdrawer,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- authorized: 0,
- new_authorized: None,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- let vote_account = rpc_client
- .get_account(&vote_account_keypair.pubkey())
- .unwrap();
- let vote_state =
- VoteStateV4::deserialize(vote_account.data(), &vote_account_keypair.pubkey()).unwrap();
- let authorized_withdrawer = vote_state.authorized_withdrawer;
- assert_eq!(authorized_withdrawer, first_withdraw_authority.pubkey());
- // Authorize vote account withdrawal to another signer with checked instruction
- let withdraw_authority = Keypair::new();
- config.signers = vec![&default_signer, &first_withdraw_authority];
- config.command = CliCommand::VoteAuthorize {
- vote_account_pubkey,
- new_authorized_pubkey: withdraw_authority.pubkey(),
- vote_authorize: VoteAuthorize::Withdrawer,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- authorized: 1,
- new_authorized: Some(1),
- compute_unit_price,
- };
- process_command(&config).unwrap_err(); // unsigned by new authority should fail
- config.signers = vec![
- &default_signer,
- &first_withdraw_authority,
- &withdraw_authority,
- ];
- config.command = CliCommand::VoteAuthorize {
- vote_account_pubkey,
- new_authorized_pubkey: withdraw_authority.pubkey(),
- vote_authorize: VoteAuthorize::Withdrawer,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- authorized: 1,
- new_authorized: Some(2),
- compute_unit_price,
- };
- process_command(&config).unwrap();
- let vote_account = rpc_client
- .get_account(&vote_account_keypair.pubkey())
- .unwrap();
- let vote_state =
- VoteStateV4::deserialize(vote_account.data(), &vote_account_keypair.pubkey()).unwrap();
- let authorized_withdrawer = vote_state.authorized_withdrawer;
- assert_eq!(authorized_withdrawer, withdraw_authority.pubkey());
- // Withdraw from vote account
- let destination_account = solana_pubkey::new_rand(); // Send withdrawal to new account to make balance check easy
- config.signers = vec![&default_signer, &withdraw_authority];
- config.command = CliCommand::WithdrawFromVoteAccount {
- vote_account_pubkey,
- withdraw_authority: 1,
- withdraw_amount: SpendAmount::Some(1_000),
- destination_account_pubkey: destination_account,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- let expected_balance = expected_balance - 1_000;
- check_balance!(expected_balance, &rpc_client, &vote_account_pubkey);
- check_balance!(1_000, &rpc_client, &destination_account);
- // Re-assign validator identity
- let new_identity_keypair = Keypair::new();
- config.signers.push(&new_identity_keypair);
- config.command = CliCommand::VoteUpdateValidator {
- vote_account_pubkey,
- new_identity_account: 2,
- withdraw_authority: 1,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- // Close vote account
- let destination_account = solana_pubkey::new_rand(); // Send withdrawal to new account to make balance check easy
- config.signers = vec![&default_signer, &withdraw_authority];
- config.command = CliCommand::CloseVoteAccount {
- vote_account_pubkey,
- withdraw_authority: 1,
- destination_account_pubkey: destination_account,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config).unwrap();
- check_balance!(0, &rpc_client, &vote_account_pubkey);
- check_balance!(expected_balance, &rpc_client, &destination_account);
- }
- #[test_case(None; "base")]
- #[test_case(Some(1_000_000); "with_compute_unit_price")]
- fn test_offline_vote_authorize_and_withdraw(compute_unit_price: Option<u64>) {
- let mint_keypair = Keypair::new();
- let mint_pubkey = mint_keypair.pubkey();
- let faucet_addr = run_local_faucet_with_unique_port_for_tests(mint_keypair);
- let test_validator =
- TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
- let rpc_client =
- RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
- let default_signer = Keypair::new();
- let mut config_payer = CliConfig::recent_for_tests();
- config_payer.json_rpc_url = test_validator.rpc_url();
- config_payer.signers = vec![&default_signer];
- let mut config_offline = CliConfig::recent_for_tests();
- config_offline.json_rpc_url = String::default();
- config_offline.command = CliCommand::ClusterVersion;
- let offline_keypair = Keypair::new();
- config_offline.signers = vec![&offline_keypair];
- // Verify that we cannot reach the cluster
- process_command(&config_offline).unwrap_err();
- request_and_confirm_airdrop(
- &rpc_client,
- &config_payer,
- &config_payer.signers[0].pubkey(),
- 100_000,
- )
- .unwrap();
- check_balance!(100_000, &rpc_client, &config_payer.signers[0].pubkey());
- request_and_confirm_airdrop(
- &rpc_client,
- &config_offline,
- &config_offline.signers[0].pubkey(),
- 100_000,
- )
- .unwrap();
- check_balance!(100_000, &rpc_client, &config_offline.signers[0].pubkey());
- // Create vote account with specific withdrawer
- let vote_account_keypair = Keypair::new();
- let vote_account_pubkey = vote_account_keypair.pubkey();
- config_payer.signers = vec![&default_signer, &vote_account_keypair];
- config_payer.command = CliCommand::CreateVoteAccount {
- vote_account: 1,
- seed: None,
- identity_account: 0,
- authorized_voter: None,
- authorized_withdrawer: offline_keypair.pubkey(),
- commission: 0,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config_payer).unwrap();
- let vote_account = rpc_client
- .get_account(&vote_account_keypair.pubkey())
- .unwrap();
- let vote_state =
- VoteStateV4::deserialize(vote_account.data(), &vote_account_keypair.pubkey()).unwrap();
- let authorized_withdrawer = vote_state.authorized_withdrawer;
- assert_eq!(authorized_withdrawer, offline_keypair.pubkey());
- let expected_balance = rpc_client
- .get_minimum_balance_for_rent_exemption(VoteStateV4::size_of())
- .unwrap()
- .max(1);
- check_balance!(expected_balance, &rpc_client, &vote_account_pubkey);
- // Transfer in some more SOL
- config_payer.signers = vec![&default_signer];
- config_payer.command = CliCommand::Transfer {
- amount: SpendAmount::Some(10_000),
- to: vote_account_pubkey,
- from: 0,
- sign_only: false,
- dump_transaction_message: false,
- allow_unfunded_recipient: true,
- no_wait: false,
- blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- derived_address_seed: None,
- derived_address_program_id: None,
- compute_unit_price,
- };
- process_command(&config_payer).unwrap();
- let expected_balance = expected_balance + 10_000;
- check_balance!(expected_balance, &rpc_client, &vote_account_pubkey);
- // Authorize vote account withdrawal to another signer, offline
- let withdraw_authority = Keypair::new();
- let blockhash = rpc_client.get_latest_blockhash().unwrap();
- config_offline.command = CliCommand::VoteAuthorize {
- vote_account_pubkey,
- new_authorized_pubkey: withdraw_authority.pubkey(),
- vote_authorize: VoteAuthorize::Withdrawer,
- sign_only: true,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::None(blockhash),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- authorized: 0,
- new_authorized: None,
- compute_unit_price,
- };
- config_offline.output_format = OutputFormat::JsonCompact;
- let sig_response = process_command(&config_offline).unwrap();
- let sign_only = parse_sign_only_reply_string(&sig_response);
- assert!(sign_only.has_all_signers());
- let offline_presigner = sign_only
- .presigner_of(&config_offline.signers[0].pubkey())
- .unwrap();
- config_payer.signers = vec![&offline_presigner];
- config_payer.command = CliCommand::VoteAuthorize {
- vote_account_pubkey,
- new_authorized_pubkey: withdraw_authority.pubkey(),
- vote_authorize: VoteAuthorize::Withdrawer,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- authorized: 0,
- new_authorized: None,
- compute_unit_price,
- };
- process_command(&config_payer).unwrap();
- let vote_account = rpc_client
- .get_account(&vote_account_keypair.pubkey())
- .unwrap();
- let vote_state =
- VoteStateV4::deserialize(vote_account.data(), &vote_account_keypair.pubkey()).unwrap();
- let authorized_withdrawer = vote_state.authorized_withdrawer;
- assert_eq!(authorized_withdrawer, withdraw_authority.pubkey());
- // Withdraw from vote account offline
- let destination_account = solana_pubkey::new_rand(); // Send withdrawal to new account to make balance check easy
- let blockhash = rpc_client.get_latest_blockhash().unwrap();
- let fee_payer_null_signer = NullSigner::new(&default_signer.pubkey());
- config_offline.signers = vec![&fee_payer_null_signer, &withdraw_authority];
- config_offline.command = CliCommand::WithdrawFromVoteAccount {
- vote_account_pubkey,
- withdraw_authority: 1,
- withdraw_amount: SpendAmount::Some(1_000),
- destination_account_pubkey: destination_account,
- sign_only: true,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::None(blockhash),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- config_offline.output_format = OutputFormat::JsonCompact;
- let sig_response = process_command(&config_offline).unwrap();
- let sign_only = parse_sign_only_reply_string(&sig_response);
- let offline_presigner = sign_only
- .presigner_of(&config_offline.signers[1].pubkey())
- .unwrap();
- config_payer.signers = vec![&default_signer, &offline_presigner];
- config_payer.command = CliCommand::WithdrawFromVoteAccount {
- vote_account_pubkey,
- withdraw_authority: 1,
- withdraw_amount: SpendAmount::Some(1_000),
- destination_account_pubkey: destination_account,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config_payer).unwrap();
- let expected_balance = expected_balance - 1_000;
- check_balance!(expected_balance, &rpc_client, &vote_account_pubkey);
- check_balance!(1_000, &rpc_client, &destination_account);
- // Re-assign validator identity offline
- let blockhash = rpc_client.get_latest_blockhash().unwrap();
- let new_identity_keypair = Keypair::new();
- let new_identity_null_signer = NullSigner::new(&new_identity_keypair.pubkey());
- config_offline.signers = vec![
- &fee_payer_null_signer,
- &withdraw_authority,
- &new_identity_null_signer,
- ];
- config_offline.command = CliCommand::VoteUpdateValidator {
- vote_account_pubkey,
- new_identity_account: 2,
- withdraw_authority: 1,
- sign_only: true,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::None(blockhash),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config_offline).unwrap();
- config_offline.output_format = OutputFormat::JsonCompact;
- let sig_response = process_command(&config_offline).unwrap();
- let sign_only = parse_sign_only_reply_string(&sig_response);
- let offline_presigner = sign_only
- .presigner_of(&config_offline.signers[1].pubkey())
- .unwrap();
- config_payer.signers = vec![&default_signer, &offline_presigner, &new_identity_keypair];
- config_payer.command = CliCommand::VoteUpdateValidator {
- vote_account_pubkey,
- new_identity_account: 2,
- withdraw_authority: 1,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config_payer).unwrap();
- // Close vote account offline. Must use WithdrawFromVoteAccount and specify amount, since
- // CloseVoteAccount requires RpcClient
- let destination_account = solana_pubkey::new_rand(); // Send withdrawal to new account to make balance check easy
- config_offline.signers = vec![&fee_payer_null_signer, &withdraw_authority];
- config_offline.command = CliCommand::WithdrawFromVoteAccount {
- vote_account_pubkey,
- withdraw_authority: 1,
- withdraw_amount: SpendAmount::Some(expected_balance),
- destination_account_pubkey: destination_account,
- sign_only: true,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::None(blockhash),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config_offline).unwrap();
- config_offline.output_format = OutputFormat::JsonCompact;
- let sig_response = process_command(&config_offline).unwrap();
- let sign_only = parse_sign_only_reply_string(&sig_response);
- let offline_presigner = sign_only
- .presigner_of(&config_offline.signers[1].pubkey())
- .unwrap();
- config_payer.signers = vec![&default_signer, &offline_presigner];
- config_payer.command = CliCommand::WithdrawFromVoteAccount {
- vote_account_pubkey,
- withdraw_authority: 1,
- withdraw_amount: SpendAmount::Some(expected_balance),
- destination_account_pubkey: destination_account,
- sign_only: false,
- dump_transaction_message: false,
- blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
- nonce_account: None,
- nonce_authority: 0,
- memo: None,
- fee_payer: 0,
- compute_unit_price,
- };
- process_command(&config_payer).unwrap();
- check_balance!(0, &rpc_client, &vote_account_pubkey);
- check_balance!(expected_balance, &rpc_client, &destination_account);
- }
|