From 804fac8ea98a92fa6a15ed222a469a3cca7bd342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Mei=C3=9Fner?= Date: Fri, 25 Feb 2022 08:21:28 +0100 Subject: [PATCH] Removes KeyedAccount from tests in vote processor. (#23333) --- programs/vote/src/vote_processor.rs | 848 +++++++++++++++++++++- programs/vote/src/vote_state/mod.rs | 1011 +-------------------------- 2 files changed, 847 insertions(+), 1012 deletions(-) diff --git a/programs/vote/src/vote_processor.rs b/programs/vote/src/vote_processor.rs index 5f0fbdea7c..24cc8f0542 100644 --- a/programs/vote/src/vote_processor.rs +++ b/programs/vote/src/vote_processor.rs @@ -182,17 +182,23 @@ mod tests { use { super::*, crate::{ + vote_error::VoteError, vote_instruction::{ authorize, authorize_checked, create_account, update_commission, update_validator_identity, update_vote_state, update_vote_state_switch, vote, vote_switch, withdraw, VoteInstruction, }, - vote_state::{Vote, VoteAuthorize, VoteInit, VoteState, VoteStateUpdate}, + vote_state::{ + Lockout, Vote, VoteAuthorize, VoteInit, VoteState, VoteStateUpdate, + VoteStateVersions, + }, }, bincode::serialize, solana_program_runtime::invoke_context::mock_process_instruction, solana_sdk::{ - account::{self, Account, AccountSharedData}, + account::{self, Account, AccountSharedData, ReadableAccount}, + account_utils::StateMut, + feature_set::FeatureSet, hash::Hash, instruction::{AccountMeta, Instruction}, sysvar::{self, clock::Clock, slot_hashes::SlotHashes}, @@ -221,6 +227,32 @@ mod tests { ) } + fn process_instruction_disabled_features( + instruction_data: &[u8], + transaction_accounts: Vec<(Pubkey, AccountSharedData)>, + instruction_accounts: Vec, + expected_result: Result<(), InstructionError>, + ) -> Vec { + mock_process_instruction( + &id(), + Vec::new(), + instruction_data, + transaction_accounts, + instruction_accounts, + expected_result, + |first_instruction_account: usize, + instruction_data: &[u8], + invoke_context: &mut InvokeContext| { + invoke_context.feature_set = std::sync::Arc::new(FeatureSet::default()); + super::process_instruction( + first_instruction_account, + instruction_data, + invoke_context, + ) + }, + ) + } + fn process_instruction_as_one_arg( instruction: &Instruction, expected_result: Result<(), InstructionError>, @@ -270,7 +302,74 @@ mod tests { Pubkey::from_str("BadVote111111111111111111111111111111111111").unwrap() } - // these are for 100% coverage in this file + fn create_default_rent_account() -> AccountSharedData { + account::create_account_shared_data_for_test(&Rent::free()) + } + + fn create_default_clock_account() -> AccountSharedData { + account::create_account_shared_data_for_test(&Clock::default()) + } + + fn create_test_account() -> (Pubkey, AccountSharedData) { + let rent = Rent::default(); + let balance = VoteState::get_rent_exempt_reserve(&rent); + let vote_pubkey = solana_sdk::pubkey::new_rand(); + ( + vote_pubkey, + vote_state::create_account(&vote_pubkey, &solana_sdk::pubkey::new_rand(), 0, balance), + ) + } + + fn create_test_account_with_authorized() -> (Pubkey, Pubkey, Pubkey, AccountSharedData) { + let vote_pubkey = solana_sdk::pubkey::new_rand(); + let authorized_voter = solana_sdk::pubkey::new_rand(); + let authorized_withdrawer = solana_sdk::pubkey::new_rand(); + + ( + vote_pubkey, + authorized_voter, + authorized_withdrawer, + vote_state::create_account_with_authorized( + &solana_sdk::pubkey::new_rand(), + &authorized_voter, + &authorized_withdrawer, + 0, + 100, + ), + ) + } + + fn create_test_account_with_epoch_credits( + credits_to_append: &[u64], + ) -> (Pubkey, AccountSharedData) { + let (vote_pubkey, vote_account) = create_test_account(); + let vote_account_space = vote_account.data().len(); + + let mut vote_state = VoteState::from(&vote_account).unwrap(); + vote_state.authorized_withdrawer = vote_pubkey; + vote_state.epoch_credits = Vec::new(); + + let mut current_epoch_credits = 0; + let mut previous_epoch_credits = 0; + for (epoch, credits) in credits_to_append.iter().enumerate() { + current_epoch_credits += credits; + vote_state.epoch_credits.push(( + u64::try_from(epoch).unwrap(), + current_epoch_credits, + previous_epoch_credits, + )); + previous_epoch_credits = current_epoch_credits; + } + + let lamports = vote_account.lamports(); + let mut vote_account_with_epoch_credits = + AccountSharedData::new(lamports, vote_account_space, &id()); + let versioned = VoteStateVersions::new_current(vote_state); + VoteState::to(&versioned, &mut vote_account_with_epoch_credits); + + (vote_pubkey, vote_account_with_epoch_credits) + } + #[test] fn test_vote_process_instruction_decode_bail() { process_instruction( @@ -281,6 +380,749 @@ mod tests { ); } + #[test] + fn test_initialize_vote_account() { + let vote_pubkey = solana_sdk::pubkey::new_rand(); + let vote_account = AccountSharedData::new(100, VoteState::size_of(), &id()); + let node_pubkey = solana_sdk::pubkey::new_rand(); + let node_account = AccountSharedData::default(); + let instruction_data = serialize(&VoteInstruction::InitializeAccount(VoteInit { + node_pubkey, + authorized_voter: vote_pubkey, + authorized_withdrawer: vote_pubkey, + commission: 0, + })) + .unwrap(); + let mut instruction_accounts = vec![ + AccountMeta { + pubkey: vote_pubkey, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::rent::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: node_pubkey, + is_signer: true, + is_writable: false, + }, + ]; + + // init should pass + let accounts = process_instruction( + &instruction_data, + vec![ + (vote_pubkey, vote_account.clone()), + (sysvar::rent::id(), create_default_rent_account()), + (sysvar::clock::id(), create_default_clock_account()), + (node_pubkey, node_account.clone()), + ], + instruction_accounts.clone(), + Ok(()), + ); + + // reinit should fail + process_instruction( + &instruction_data, + vec![ + (vote_pubkey, accounts[0].clone()), + (sysvar::rent::id(), create_default_rent_account()), + (sysvar::clock::id(), create_default_clock_account()), + (node_pubkey, accounts[3].clone()), + ], + instruction_accounts.clone(), + Err(InstructionError::AccountAlreadyInitialized), + ); + + // init should fail, account is too big + process_instruction( + &instruction_data, + vec![ + ( + vote_pubkey, + AccountSharedData::new(100, 2 * VoteState::size_of(), &id()), + ), + (sysvar::rent::id(), create_default_rent_account()), + (sysvar::clock::id(), create_default_clock_account()), + (node_pubkey, node_account.clone()), + ], + instruction_accounts.clone(), + Err(InstructionError::InvalidAccountData), + ); + + // init should fail, node_pubkey didn't sign the transaction + instruction_accounts[3].is_signer = false; + process_instruction( + &instruction_data, + vec![ + (vote_pubkey, vote_account), + (sysvar::rent::id(), create_default_rent_account()), + (sysvar::clock::id(), create_default_clock_account()), + (node_pubkey, node_account), + ], + instruction_accounts, + Err(InstructionError::MissingRequiredSignature), + ); + } + + #[test] + fn test_vote_update_validator_identity() { + let (vote_pubkey, _authorized_voter, authorized_withdrawer, vote_account) = + create_test_account_with_authorized(); + let node_pubkey = solana_sdk::pubkey::new_rand(); + let instruction_data = serialize(&VoteInstruction::UpdateValidatorIdentity).unwrap(); + let transaction_accounts = vec![ + (vote_pubkey, vote_account), + (node_pubkey, AccountSharedData::default()), + (authorized_withdrawer, AccountSharedData::default()), + ]; + let mut instruction_accounts = vec![ + AccountMeta { + pubkey: vote_pubkey, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: node_pubkey, + is_signer: true, + is_writable: false, + }, + AccountMeta { + pubkey: authorized_withdrawer, + is_signer: true, + is_writable: false, + }, + ]; + + // should fail, node_pubkey didn't sign the transaction + instruction_accounts[1].is_signer = false; + let accounts = process_instruction( + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(InstructionError::MissingRequiredSignature), + ); + instruction_accounts[1].is_signer = true; + let vote_state: VoteState = StateMut::::state(&accounts[0]) + .unwrap() + .convert_to_current(); + assert_ne!(vote_state.node_pubkey, node_pubkey); + + // should fail, authorized_withdrawer didn't sign the transaction + instruction_accounts[2].is_signer = false; + let accounts = process_instruction( + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(InstructionError::MissingRequiredSignature), + ); + instruction_accounts[2].is_signer = true; + let vote_state: VoteState = StateMut::::state(&accounts[0]) + .unwrap() + .convert_to_current(); + assert_ne!(vote_state.node_pubkey, node_pubkey); + + // should pass + let accounts = process_instruction( + &instruction_data, + transaction_accounts, + instruction_accounts, + Ok(()), + ); + let vote_state: VoteState = StateMut::::state(&accounts[0]) + .unwrap() + .convert_to_current(); + assert_eq!(vote_state.node_pubkey, node_pubkey); + } + + #[test] + fn test_vote_update_commission() { + let (vote_pubkey, _authorized_voter, authorized_withdrawer, vote_account) = + create_test_account_with_authorized(); + let instruction_data = serialize(&VoteInstruction::UpdateCommission(42)).unwrap(); + let transaction_accounts = vec![ + (vote_pubkey, vote_account), + (authorized_withdrawer, AccountSharedData::default()), + ]; + let mut instruction_accounts = vec![ + AccountMeta { + pubkey: vote_pubkey, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: authorized_withdrawer, + is_signer: true, + is_writable: false, + }, + ]; + + // should pass + let accounts = process_instruction( + &serialize(&VoteInstruction::UpdateCommission(u8::MAX)).unwrap(), + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + let vote_state: VoteState = StateMut::::state(&accounts[0]) + .unwrap() + .convert_to_current(); + assert_eq!(vote_state.commission, u8::MAX); + + // should pass + let accounts = process_instruction( + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + let vote_state: VoteState = StateMut::::state(&accounts[0]) + .unwrap() + .convert_to_current(); + assert_eq!(vote_state.commission, 42); + + // should fail, authorized_withdrawer didn't sign the transaction + instruction_accounts[1].is_signer = false; + let accounts = process_instruction( + &instruction_data, + transaction_accounts, + instruction_accounts, + Err(InstructionError::MissingRequiredSignature), + ); + let vote_state: VoteState = StateMut::::state(&accounts[0]) + .unwrap() + .convert_to_current(); + assert_eq!(vote_state.commission, 0); + } + + #[test] + fn test_vote_signature() { + let (vote_pubkey, vote_account) = create_test_account(); + let vote = Vote::new(vec![1], Hash::default()); + let slot_hashes = SlotHashes::new(&[(*vote.slots.last().unwrap(), vote.hash)]); + let slot_hashes_account = account::create_account_shared_data_for_test(&slot_hashes); + let instruction_data = serialize(&VoteInstruction::Vote(vote.clone())).unwrap(); + let mut transaction_accounts = vec![ + (vote_pubkey, vote_account), + (sysvar::slot_hashes::id(), slot_hashes_account.clone()), + (sysvar::clock::id(), create_default_clock_account()), + ]; + let mut instruction_accounts = vec![ + AccountMeta { + pubkey: vote_pubkey, + is_signer: true, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::slot_hashes::id(), + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + ]; + + // should fail, unsigned + instruction_accounts[0].is_signer = false; + process_instruction( + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(InstructionError::MissingRequiredSignature), + ); + instruction_accounts[0].is_signer = true; + + // should pass + let accounts = process_instruction( + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + let vote_state: VoteState = StateMut::::state(&accounts[0]) + .unwrap() + .convert_to_current(); + assert_eq!( + vote_state.votes, + vec![Lockout::new(*vote.slots.last().unwrap())] + ); + assert_eq!(vote_state.credits(), 0); + + // should fail, wrong hash + transaction_accounts[1] = ( + sysvar::slot_hashes::id(), + account::create_account_shared_data_for_test(&SlotHashes::new(&[( + *vote.slots.last().unwrap(), + solana_sdk::hash::hash(&[0u8]), + )])), + ); + process_instruction( + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(VoteError::SlotHashMismatch.into()), + ); + + // should fail, wrong slot + transaction_accounts[1] = ( + sysvar::slot_hashes::id(), + account::create_account_shared_data_for_test(&SlotHashes::new(&[(0, vote.hash)])), + ); + process_instruction( + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(VoteError::SlotsMismatch.into()), + ); + + // should fail, empty slot_hashes + transaction_accounts[1] = ( + sysvar::slot_hashes::id(), + account::create_account_shared_data_for_test(&SlotHashes::new(&[])), + ); + process_instruction( + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(VoteError::VoteTooOld.into()), + ); + transaction_accounts[1] = (sysvar::slot_hashes::id(), slot_hashes_account); + + // should fail, uninitialized + let vote_account = AccountSharedData::new(100, VoteState::size_of(), &id()); + transaction_accounts[0] = (vote_pubkey, vote_account); + process_instruction( + &instruction_data, + transaction_accounts, + instruction_accounts, + Err(InstructionError::UninitializedAccount), + ); + } + + #[test] + fn test_authorize_voter() { + let (vote_pubkey, vote_account) = create_test_account(); + let authorized_voter_pubkey = solana_sdk::pubkey::new_rand(); + let clock = Clock { + epoch: 1, + leader_schedule_epoch: 2, + ..Clock::default() + }; + let clock_account = account::create_account_shared_data_for_test(&clock); + let instruction_data = serialize(&VoteInstruction::Authorize( + authorized_voter_pubkey, + VoteAuthorize::Voter, + )) + .unwrap(); + let mut transaction_accounts = vec![ + (vote_pubkey, vote_account), + (sysvar::clock::id(), clock_account), + (authorized_voter_pubkey, AccountSharedData::default()), + ]; + let mut instruction_accounts = vec![ + AccountMeta { + pubkey: vote_pubkey, + is_signer: true, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + ]; + + // should fail, unsigned + instruction_accounts[0].is_signer = false; + process_instruction( + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(InstructionError::MissingRequiredSignature), + ); + instruction_accounts[0].is_signer = true; + + // should pass + let accounts = process_instruction( + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + + // should fail, already set an authorized voter earlier for leader_schedule_epoch == 2 + transaction_accounts[0] = (vote_pubkey, accounts[0].clone()); + process_instruction( + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(VoteError::TooSoonToReauthorize.into()), + ); + + // should pass, verify authorized_voter_pubkey can authorize authorized_voter_pubkey ;) + instruction_accounts[0].is_signer = false; + instruction_accounts.push(AccountMeta { + pubkey: authorized_voter_pubkey, + is_signer: true, + is_writable: false, + }); + let clock = Clock { + // The authorized voter was set when leader_schedule_epoch == 2, so will + // take effect when epoch == 3 + epoch: 3, + leader_schedule_epoch: 4, + ..Clock::default() + }; + let clock_account = account::create_account_shared_data_for_test(&clock); + transaction_accounts[1] = (sysvar::clock::id(), clock_account); + process_instruction( + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + instruction_accounts[0].is_signer = true; + instruction_accounts.pop(); + + // should fail, not signed by authorized voter + let vote = Vote::new(vec![1], Hash::default()); + let slot_hashes = SlotHashes::new(&[(*vote.slots.last().unwrap(), vote.hash)]); + let slot_hashes_account = account::create_account_shared_data_for_test(&slot_hashes); + let instruction_data = serialize(&VoteInstruction::Vote(vote)).unwrap(); + transaction_accounts.push((sysvar::slot_hashes::id(), slot_hashes_account)); + instruction_accounts.insert( + 1, + AccountMeta { + pubkey: sysvar::slot_hashes::id(), + is_signer: false, + is_writable: false, + }, + ); + process_instruction( + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(InstructionError::MissingRequiredSignature), + ); + + // should pass, signed by authorized voter + instruction_accounts.push(AccountMeta { + pubkey: authorized_voter_pubkey, + is_signer: true, + is_writable: false, + }); + process_instruction( + &instruction_data, + transaction_accounts, + instruction_accounts, + Ok(()), + ); + } + + #[test] + fn test_authorize_withdrawer() { + let (vote_pubkey, vote_account) = create_test_account(); + let authorized_withdrawer_pubkey = solana_sdk::pubkey::new_rand(); + let instruction_data = serialize(&VoteInstruction::Authorize( + authorized_withdrawer_pubkey, + VoteAuthorize::Withdrawer, + )) + .unwrap(); + let mut transaction_accounts = vec![ + (vote_pubkey, vote_account), + (sysvar::clock::id(), create_default_clock_account()), + (authorized_withdrawer_pubkey, AccountSharedData::default()), + ]; + let mut instruction_accounts = vec![ + AccountMeta { + pubkey: vote_pubkey, + is_signer: true, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + ]; + + // should fail, unsigned + instruction_accounts[0].is_signer = false; + process_instruction( + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(InstructionError::MissingRequiredSignature), + ); + instruction_accounts[0].is_signer = true; + + // should pass + let accounts = process_instruction( + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + + // should pass, verify authorized_withdrawer can authorize authorized_withdrawer ;) + instruction_accounts[0].is_signer = false; + instruction_accounts.push(AccountMeta { + pubkey: authorized_withdrawer_pubkey, + is_signer: true, + is_writable: false, + }); + transaction_accounts[0] = (vote_pubkey, accounts[0].clone()); + process_instruction( + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + + // should pass, verify authorized_withdrawer can authorize a new authorized_voter + let authorized_voter_pubkey = solana_sdk::pubkey::new_rand(); + transaction_accounts.push((authorized_voter_pubkey, AccountSharedData::default())); + let instruction_data = serialize(&VoteInstruction::Authorize( + authorized_voter_pubkey, + VoteAuthorize::Voter, + )) + .unwrap(); + process_instruction( + &instruction_data, + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + + // should fail, if vote_withdraw_authority_may_change_authorized_voter is disabled + process_instruction_disabled_features( + &instruction_data, + transaction_accounts, + instruction_accounts, + Err(InstructionError::MissingRequiredSignature), + ); + } + + #[test] + fn test_vote_withdraw() { + let (vote_pubkey, vote_account) = create_test_account(); + let lamports = vote_account.lamports(); + let authorized_withdrawer_pubkey = solana_sdk::pubkey::new_rand(); + let mut transaction_accounts = vec![ + (vote_pubkey, vote_account.clone()), + (sysvar::clock::id(), create_default_clock_account()), + (sysvar::rent::id(), create_default_rent_account()), + (authorized_withdrawer_pubkey, AccountSharedData::default()), + ]; + let mut instruction_accounts = vec![ + AccountMeta { + pubkey: vote_pubkey, + is_signer: true, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + ]; + + // should pass, withdraw using authorized_withdrawer to authorized_withdrawer's account + let accounts = process_instruction( + &serialize(&VoteInstruction::Authorize( + authorized_withdrawer_pubkey, + VoteAuthorize::Withdrawer, + )) + .unwrap(), + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + instruction_accounts[0].is_signer = false; + instruction_accounts[1] = AccountMeta { + pubkey: authorized_withdrawer_pubkey, + is_signer: true, + is_writable: false, + }; + transaction_accounts[0] = (vote_pubkey, accounts[0].clone()); + let accounts = process_instruction( + &serialize(&VoteInstruction::Withdraw(lamports)).unwrap(), + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + assert_eq!(accounts[0].lamports(), 0); + assert_eq!(accounts[3].lamports(), lamports); + let post_state: VoteStateVersions = accounts[0].state().unwrap(); + // State has been deinitialized since balance is zero + assert!(post_state.is_uninitialized()); + + // should fail, unsigned + transaction_accounts[0] = (vote_pubkey, vote_account); + process_instruction( + &serialize(&VoteInstruction::Withdraw(lamports)).unwrap(), + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(InstructionError::MissingRequiredSignature), + ); + instruction_accounts[0].is_signer = true; + + // should pass + process_instruction( + &serialize(&VoteInstruction::Withdraw(lamports)).unwrap(), + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + + // should fail, insufficient funds + process_instruction( + &serialize(&VoteInstruction::Withdraw(lamports + 1)).unwrap(), + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(InstructionError::InsufficientFunds), + ); + + // should pass, partial withdraw + let withdraw_lamports = 42; + let accounts = process_instruction( + &serialize(&VoteInstruction::Withdraw(withdraw_lamports)).unwrap(), + transaction_accounts, + instruction_accounts, + Ok(()), + ); + assert_eq!(accounts[0].lamports(), lamports - withdraw_lamports); + assert_eq!(accounts[3].lamports(), withdraw_lamports); + } + + #[test] + fn test_vote_state_withdraw() { + let authorized_withdrawer_pubkey = solana_sdk::pubkey::new_rand(); + let (vote_pubkey_1, vote_account_with_epoch_credits_1) = + create_test_account_with_epoch_credits(&[2, 1]); + let (vote_pubkey_2, vote_account_with_epoch_credits_2) = + create_test_account_with_epoch_credits(&[2, 1, 3]); + let clock = Clock { + epoch: 3, + ..Clock::default() + }; + let clock_account = account::create_account_shared_data_for_test(&clock); + let rent_sysvar = Rent::default(); + let minimum_balance = rent_sysvar + .minimum_balance(vote_account_with_epoch_credits_1.data().len()) + .max(1); + let lamports = vote_account_with_epoch_credits_1.lamports(); + let transaction_accounts = vec![ + (vote_pubkey_1, vote_account_with_epoch_credits_1), + (vote_pubkey_2, vote_account_with_epoch_credits_2), + (sysvar::clock::id(), clock_account), + ( + sysvar::rent::id(), + account::create_account_shared_data_for_test(&rent_sysvar), + ), + (authorized_withdrawer_pubkey, AccountSharedData::default()), + ]; + let mut instruction_accounts = vec![ + AccountMeta { + pubkey: vote_pubkey_1, + is_signer: true, + is_writable: false, + }, + AccountMeta { + pubkey: sysvar::clock::id(), + is_signer: false, + is_writable: false, + }, + ]; + + // non rent exempt withdraw, with 0 credit epoch + instruction_accounts[0].pubkey = vote_pubkey_1; + process_instruction( + &serialize(&VoteInstruction::Withdraw(lamports - minimum_balance + 1)).unwrap(), + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(InstructionError::InsufficientFunds), + ); + + // non rent exempt withdraw, without 0 credit epoch + instruction_accounts[0].pubkey = vote_pubkey_2; + process_instruction( + &serialize(&VoteInstruction::Withdraw(lamports - minimum_balance + 1)).unwrap(), + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(InstructionError::InsufficientFunds), + ); + + // full withdraw, with 0 credit epoch + instruction_accounts[0].pubkey = vote_pubkey_1; + process_instruction( + &serialize(&VoteInstruction::Withdraw(lamports)).unwrap(), + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + + // full withdraw, without 0 credit epoch + instruction_accounts[0].pubkey = vote_pubkey_2; + process_instruction( + &serialize(&VoteInstruction::Withdraw(lamports)).unwrap(), + transaction_accounts.clone(), + instruction_accounts.clone(), + Err(InstructionError::ActiveVoteAccountClose), + ); + + // Both features disabled: + // reject_non_rent_exempt_vote_withdraws + // reject_vote_account_close_unless_zero_credit_epoch + + // non rent exempt withdraw, with 0 credit epoch + instruction_accounts[0].pubkey = vote_pubkey_1; + process_instruction_disabled_features( + &serialize(&VoteInstruction::Withdraw(lamports - minimum_balance + 1)).unwrap(), + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + + // non rent exempt withdraw, without 0 credit epoch + instruction_accounts[0].pubkey = vote_pubkey_2; + process_instruction_disabled_features( + &serialize(&VoteInstruction::Withdraw(lamports - minimum_balance + 1)).unwrap(), + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + + // full withdraw, with 0 credit epoch + instruction_accounts[0].pubkey = vote_pubkey_1; + process_instruction_disabled_features( + &serialize(&VoteInstruction::Withdraw(lamports)).unwrap(), + transaction_accounts.clone(), + instruction_accounts.clone(), + Ok(()), + ); + + // full withdraw, without 0 credit epoch + instruction_accounts[0].pubkey = vote_pubkey_2; + process_instruction_disabled_features( + &serialize(&VoteInstruction::Withdraw(lamports)).unwrap(), + transaction_accounts, + instruction_accounts, + Ok(()), + ); + } + #[test] fn test_spoofed_vote() { process_instruction_as_one_arg( diff --git a/programs/vote/src/vote_state/mod.rs b/programs/vote/src/vote_state/mod.rs index 8168f7c682..ba9a1a1a70 100644 --- a/programs/vote/src/vote_state/mod.rs +++ b/programs/vote/src/vote_state/mod.rs @@ -317,7 +317,7 @@ pub struct VoteState { /// history of how many credits earned by the end of each epoch /// each tuple is (Epoch, credits, prev_credits) - epoch_credits: Vec<(Epoch, u64, u64)>, + pub(crate) epoch_credits: Vec<(Epoch, u64, u64)>, /// most recent timestamp submitted with a vote pub last_timestamp: BlockTimestamp, @@ -1433,12 +1433,7 @@ mod tests { use { super::*, crate::vote_state, - solana_sdk::{ - account::AccountSharedData, - account_utils::StateMut, - hash::hash, - keyed_account::{get_signers, keyed_account_at_index}, - }, + solana_sdk::{account::AccountSharedData, account_utils::StateMut, hash::hash}, std::cell::RefCell, }; @@ -1458,80 +1453,6 @@ mod tests { } } - #[test] - fn test_initialize_vote_account() { - let vote_account_pubkey = solana_sdk::pubkey::new_rand(); - let vote_account = AccountSharedData::new_ref(100, VoteState::size_of(), &id()); - let vote_account = KeyedAccount::new(&vote_account_pubkey, false, &vote_account); - - let node_pubkey = solana_sdk::pubkey::new_rand(); - let node_account = RefCell::new(AccountSharedData::default()); - let keyed_accounts = &[]; - let signers: HashSet = get_signers(keyed_accounts); - - //init should fail, node_pubkey didn't sign the transaction - let res = initialize_account( - &vote_account, - &VoteInit { - node_pubkey, - authorized_voter: vote_account_pubkey, - authorized_withdrawer: vote_account_pubkey, - commission: 0, - }, - &signers, - &Clock::default(), - ); - assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); - - let keyed_accounts = &[KeyedAccount::new(&node_pubkey, true, &node_account)]; - let signers: HashSet = get_signers(keyed_accounts); - - //init should pass - let res = initialize_account( - &vote_account, - &VoteInit { - node_pubkey, - authorized_voter: vote_account_pubkey, - authorized_withdrawer: vote_account_pubkey, - commission: 0, - }, - &signers, - &Clock::default(), - ); - assert_eq!(res, Ok(())); - - // reinit should fail - let res = initialize_account( - &vote_account, - &VoteInit { - node_pubkey, - authorized_voter: vote_account_pubkey, - authorized_withdrawer: vote_account_pubkey, - commission: 0, - }, - &signers, - &Clock::default(), - ); - assert_eq!(res, Err(InstructionError::AccountAlreadyInitialized)); - - //init should fail, account is too big - let large_vote_account = AccountSharedData::new_ref(100, 2 * VoteState::size_of(), &id()); - let large_vote_account = - KeyedAccount::new(&vote_account_pubkey, false, &large_vote_account); - let res = initialize_account( - &large_vote_account, - &VoteInit { - node_pubkey, - authorized_voter: vote_account_pubkey, - authorized_withdrawer: vote_account_pubkey, - commission: 0, - }, - &signers, - &Clock::default(), - ); - assert_eq!(res, Err(InstructionError::InvalidAccountData)); - } - fn create_test_account() -> (Pubkey, RefCell) { let rent = Rent::default(); let balance = VoteState::get_rent_exempt_reserve(&rent); @@ -1547,98 +1468,6 @@ mod tests { ) } - fn create_test_account_with_authorized() -> (Pubkey, Pubkey, Pubkey, RefCell) - { - let vote_pubkey = solana_sdk::pubkey::new_rand(); - let authorized_voter = solana_sdk::pubkey::new_rand(); - let authorized_withdrawer = solana_sdk::pubkey::new_rand(); - - ( - vote_pubkey, - authorized_voter, - authorized_withdrawer, - RefCell::new(vote_state::create_account_with_authorized( - &solana_sdk::pubkey::new_rand(), - &authorized_voter, - &authorized_withdrawer, - 0, - 100, - )), - ) - } - - fn create_test_account_with_epoch_credits( - credits_to_append: &[u64], - ) -> (Pubkey, RefCell) { - let (vote_pubkey, vote_account) = create_test_account(); - let vote_account_space = vote_account.borrow().data().len(); - - let mut vote_state = VoteState::from(&*vote_account.borrow_mut()).unwrap(); - vote_state.authorized_withdrawer = vote_pubkey; - - vote_state.epoch_credits = Vec::new(); - - let mut current_epoch_credits = 0; - let mut previous_epoch_credits = 0; - for (epoch, credits) in credits_to_append.iter().enumerate() { - current_epoch_credits += credits; - vote_state.epoch_credits.push(( - u64::try_from(epoch).unwrap(), - current_epoch_credits, - previous_epoch_credits, - )); - previous_epoch_credits = current_epoch_credits; - } - - let lamports = vote_account.borrow().lamports(); - let mut vote_account_with_epoch_credits = - AccountSharedData::new(lamports, vote_account_space, &vote_pubkey); - let versioned = VoteStateVersions::new_current(vote_state); - VoteState::to(&versioned, &mut vote_account_with_epoch_credits); - let ref_vote_account_with_epoch_credits = RefCell::new(vote_account_with_epoch_credits); - - (vote_pubkey, ref_vote_account_with_epoch_credits) - } - - fn simulate_process_vote( - vote_pubkey: &Pubkey, - vote_account: &RefCell, - vote: &Vote, - slot_hashes: &[SlotHash], - epoch: Epoch, - ) -> Result { - let keyed_accounts = &[KeyedAccount::new(vote_pubkey, true, vote_account)]; - let signers: HashSet = get_signers(keyed_accounts); - process_vote( - &keyed_accounts[0], - slot_hashes, - &Clock { - epoch, - ..Clock::default() - }, - vote, - &signers, - &FeatureSet::default(), - )?; - StateMut::::state(&*vote_account.borrow()) - .map(|versioned| versioned.convert_to_current()) - } - - /// exercises all the keyed accounts stuff - fn simulate_process_vote_unchecked( - vote_pubkey: &Pubkey, - vote_account: &RefCell, - vote: &Vote, - ) -> Result { - simulate_process_vote( - vote_pubkey, - vote_account, - vote, - &[(*vote.slots.last().unwrap(), vote.hash)], - 0, - ) - } - #[test] fn test_vote_serialize() { let mut buffer: Vec = vec![0; VoteState::size_of()]; @@ -1671,384 +1500,6 @@ mod tests { assert!(vote_state.votes.is_empty()); } - #[test] - fn test_vote() { - let (vote_pubkey, vote_account) = create_test_account(); - - let vote = Vote::new(vec![1], Hash::default()); - let vote_state = - simulate_process_vote_unchecked(&vote_pubkey, &vote_account, &vote).unwrap(); - assert_eq!( - vote_state.votes, - vec![Lockout::new(*vote.slots.last().unwrap())] - ); - assert_eq!(vote_state.credits(), 0); - } - - #[test] - fn test_vote_slot_hashes() { - let (vote_pubkey, vote_account) = create_test_account(); - - let hash = hash(&[0u8]); - let vote = Vote::new(vec![0], hash); - - // wrong hash - assert_eq!( - simulate_process_vote( - &vote_pubkey, - &vote_account, - &vote, - &[(0, Hash::default())], - 0, - ), - Err(VoteError::SlotHashMismatch.into()) - ); - - // wrong slot - assert_eq!( - simulate_process_vote(&vote_pubkey, &vote_account, &vote, &[(1, hash)], 0), - Err(VoteError::SlotsMismatch.into()) - ); - - // empty slot_hashes - assert_eq!( - simulate_process_vote(&vote_pubkey, &vote_account, &vote, &[], 0), - Err(VoteError::VoteTooOld.into()) - ); - } - - #[test] - fn test_vote_update_validator_identity() { - let (vote_pubkey, _authorized_voter, authorized_withdrawer, vote_account) = - create_test_account_with_authorized(); - - let node_pubkey = solana_sdk::pubkey::new_rand(); - let node_account = RefCell::new(AccountSharedData::default()); - let authorized_withdrawer_account = RefCell::new(AccountSharedData::default()); - - let keyed_accounts = &[ - KeyedAccount::new(&vote_pubkey, true, &vote_account), - KeyedAccount::new(&node_pubkey, false, &node_account), - KeyedAccount::new(&authorized_withdrawer, true, &authorized_withdrawer_account), - ]; - let signers: HashSet = get_signers(keyed_accounts); - let res = update_validator_identity(&keyed_accounts[0], &node_pubkey, &signers); - assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); - - let keyed_accounts = &[ - KeyedAccount::new(&vote_pubkey, true, &vote_account), - KeyedAccount::new(&node_pubkey, true, &node_account), - KeyedAccount::new( - &authorized_withdrawer, - false, - &authorized_withdrawer_account, - ), - ]; - let signers: HashSet = get_signers(keyed_accounts); - let res = update_validator_identity(&keyed_accounts[0], &node_pubkey, &signers); - assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); - let vote_state: VoteState = StateMut::::state(&*vote_account.borrow()) - .unwrap() - .convert_to_current(); - assert!(vote_state.node_pubkey != node_pubkey); - - let keyed_accounts = &[ - KeyedAccount::new(&vote_pubkey, true, &vote_account), - KeyedAccount::new(&node_pubkey, true, &node_account), - KeyedAccount::new(&authorized_withdrawer, true, &authorized_withdrawer_account), - ]; - let signers: HashSet = get_signers(keyed_accounts); - let res = update_validator_identity(&keyed_accounts[0], &node_pubkey, &signers); - assert_eq!(res, Ok(())); - let vote_state: VoteState = StateMut::::state(&*vote_account.borrow()) - .unwrap() - .convert_to_current(); - assert_eq!(vote_state.node_pubkey, node_pubkey); - } - - #[test] - fn test_vote_update_commission() { - let (vote_pubkey, _authorized_voter, authorized_withdrawer, vote_account) = - create_test_account_with_authorized(); - - let authorized_withdrawer_account = RefCell::new(AccountSharedData::default()); - - let keyed_accounts = &[ - KeyedAccount::new(&vote_pubkey, true, &vote_account), - KeyedAccount::new( - &authorized_withdrawer, - false, - &authorized_withdrawer_account, - ), - ]; - let signers: HashSet = get_signers(keyed_accounts); - let res = update_commission(&keyed_accounts[0], 42, &signers); - assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); - - let keyed_accounts = &[ - KeyedAccount::new(&vote_pubkey, true, &vote_account), - KeyedAccount::new(&authorized_withdrawer, true, &authorized_withdrawer_account), - ]; - let signers: HashSet = get_signers(keyed_accounts); - let res = update_commission(&keyed_accounts[0], 42, &signers); - assert_eq!(res, Ok(())); - let vote_state: VoteState = StateMut::::state(&*vote_account.borrow()) - .unwrap() - .convert_to_current(); - assert_eq!(vote_state.commission, 42); - - let keyed_accounts = &[ - KeyedAccount::new(&vote_pubkey, true, &vote_account), - KeyedAccount::new(&authorized_withdrawer, true, &authorized_withdrawer_account), - ]; - let signers: HashSet = get_signers(keyed_accounts); - let res = update_commission(&keyed_accounts[0], u8::MAX, &signers); - assert_eq!(res, Ok(())); - let vote_state: VoteState = StateMut::::state(&*vote_account.borrow()) - .unwrap() - .convert_to_current(); - assert_eq!(vote_state.commission, u8::MAX); - } - - #[test] - fn test_vote_signature() { - let (vote_pubkey, vote_account) = create_test_account(); - let vote = Vote::new(vec![1], Hash::default()); - - // unsigned - let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, false, &vote_account)]; - let signers: HashSet = get_signers(keyed_accounts); - let res = process_vote( - &keyed_accounts[0], - &[(*vote.slots.last().unwrap(), vote.hash)], - &Clock { - epoch: 1, - leader_schedule_epoch: 2, - ..Clock::default() - }, - &vote, - &signers, - &FeatureSet::default(), - ); - assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); - - // signed - let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let signers: HashSet = get_signers(keyed_accounts); - let res = process_vote( - &keyed_accounts[0], - &[(*vote.slots.last().unwrap(), vote.hash)], - &Clock { - epoch: 1, - leader_schedule_epoch: 2, - ..Clock::default() - }, - &vote, - &signers, - &FeatureSet::default(), - ); - assert_eq!(res, Ok(())); - - // another voter, unsigned - let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, false, &vote_account)]; - let signers: HashSet = get_signers(keyed_accounts); - let authorized_voter_pubkey = solana_sdk::pubkey::new_rand(); - let res = authorize( - &keyed_accounts[0], - &authorized_voter_pubkey, - VoteAuthorize::Voter, - &signers, - &Clock { - epoch: 1, - leader_schedule_epoch: 2, - ..Clock::default() - }, - &FeatureSet::default(), - ); - assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); - - let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let signers: HashSet = get_signers(keyed_accounts); - let res = authorize( - &keyed_accounts[0], - &authorized_voter_pubkey, - VoteAuthorize::Voter, - &signers, - &Clock { - epoch: 1, - leader_schedule_epoch: 2, - ..Clock::default() - }, - &FeatureSet::default(), - ); - assert_eq!(res, Ok(())); - - // Already set an authorized voter earlier for leader_schedule_epoch == 2 - let signers: HashSet = get_signers(keyed_accounts); - let res = authorize( - &keyed_accounts[0], - &authorized_voter_pubkey, - VoteAuthorize::Voter, - &signers, - &Clock { - epoch: 1, - leader_schedule_epoch: 2, - ..Clock::default() - }, - &FeatureSet::default(), - ); - assert_eq!(res, Err(VoteError::TooSoonToReauthorize.into())); - - // verify authorized_voter_pubkey can authorize authorized_voter_pubkey ;) - let authorized_voter_account = RefCell::new(AccountSharedData::default()); - let keyed_accounts = &[ - KeyedAccount::new(&vote_pubkey, false, &vote_account), - KeyedAccount::new(&authorized_voter_pubkey, true, &authorized_voter_account), - ]; - let signers: HashSet = get_signers(keyed_accounts); - let res = authorize( - &keyed_accounts[0], - &authorized_voter_pubkey, - VoteAuthorize::Voter, - &signers, - &Clock { - // The authorized voter was set when leader_schedule_epoch == 2, so will - // take effect when epoch == 3 - epoch: 3, - leader_schedule_epoch: 4, - ..Clock::default() - }, - &FeatureSet::default(), - ); - assert_eq!(res, Ok(())); - - // authorize another withdrawer - // another voter - let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let signers: HashSet = get_signers(keyed_accounts); - let authorized_withdrawer_pubkey = solana_sdk::pubkey::new_rand(); - let res = authorize( - &keyed_accounts[0], - &authorized_withdrawer_pubkey, - VoteAuthorize::Withdrawer, - &signers, - &Clock { - epoch: 3, - leader_schedule_epoch: 4, - ..Clock::default() - }, - &FeatureSet::default(), - ); - assert_eq!(res, Ok(())); - - // verify authorized_withdrawer can authorize authorized_withdrawer ;) - let withdrawer_account = RefCell::new(AccountSharedData::default()); - let keyed_accounts = &[ - KeyedAccount::new(&vote_pubkey, false, &vote_account), - KeyedAccount::new(&authorized_withdrawer_pubkey, true, &withdrawer_account), - ]; - let signers: HashSet = get_signers(keyed_accounts); - let res = authorize( - &keyed_accounts[0], - &authorized_withdrawer_pubkey, - VoteAuthorize::Withdrawer, - &signers, - &Clock { - epoch: 3, - leader_schedule_epoch: 4, - ..Clock::default() - }, - &FeatureSet::default(), - ); - assert_eq!(res, Ok(())); - - // not signed by authorized voter - let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let signers: HashSet = get_signers(keyed_accounts); - let vote = Vote::new(vec![2], Hash::default()); - let res = process_vote( - &keyed_accounts[0], - &[(*vote.slots.last().unwrap(), vote.hash)], - &Clock { - epoch: 3, - leader_schedule_epoch: 4, - ..Clock::default() - }, - &vote, - &signers, - &FeatureSet::default(), - ); - assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); - - // signed by authorized voter - let authorized_voter_account = RefCell::new(AccountSharedData::default()); - let keyed_accounts = &[ - KeyedAccount::new(&vote_pubkey, false, &vote_account), - KeyedAccount::new(&authorized_voter_pubkey, true, &authorized_voter_account), - ]; - let signers: HashSet = get_signers(keyed_accounts); - let vote = Vote::new(vec![2], Hash::default()); - let res = process_vote( - &keyed_accounts[0], - &[(*vote.slots.last().unwrap(), vote.hash)], - &Clock { - epoch: 3, - leader_schedule_epoch: 4, - ..Clock::default() - }, - &vote, - &signers, - &FeatureSet::default(), - ); - assert_eq!(res, Ok(())); - - // verify authorized_withdrawer can authorize a new authorized_voter when - // `feature_set::vote_withdraw_authority_may_change_authorized_voter` is enabled - let keyed_accounts = &[ - KeyedAccount::new(&vote_pubkey, false, &vote_account), - KeyedAccount::new(&authorized_withdrawer_pubkey, true, &withdrawer_account), - ]; - let another_authorized_voter_pubkey = solana_sdk::pubkey::new_rand(); - let signers: HashSet = get_signers(keyed_accounts); - - for (feature_set, expected_res) in [ - ( - FeatureSet::default(), - Err(InstructionError::MissingRequiredSignature), - ), - (FeatureSet::all_enabled(), Ok(())), - ] - .into_iter() - { - let res = authorize( - &keyed_accounts[0], - &another_authorized_voter_pubkey, - VoteAuthorize::Voter, - &signers, - &Clock { - epoch: 4, - leader_schedule_epoch: 5, - ..Clock::default() - }, - &feature_set, - ); - assert_eq!(res, expected_res) - } - } - - #[test] - fn test_vote_without_initialization() { - let vote_pubkey = solana_sdk::pubkey::new_rand(); - let vote_account = RefCell::new(AccountSharedData::new(100, VoteState::size_of(), &id())); - - let res = simulate_process_vote_unchecked( - &vote_pubkey, - &vote_account, - &Vote::new(vec![1], Hash::default()), - ); - assert_eq!(res, Err(InstructionError::UninitializedAccount)); - } - #[test] fn test_vote_lockout() { let (_vote_pubkey, vote_account) = create_test_account(); @@ -2383,464 +1834,6 @@ mod tests { assert_eq!((voter_portion, staker_portion, was_split), (5, 5, true)); } - #[test] - fn test_vote_state_withdraw() { - let (vote_pubkey, vote_account) = create_test_account(); - let credits_through_epoch_1: Vec = vec![2, 1]; - let credits_through_epoch_2: Vec = vec![2, 1, 3]; - - let clock_epoch_3 = &Clock { - epoch: 3, - ..Clock::default() - }; - - // unsigned request - let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, false, &vote_account)]; - let signers: HashSet = get_signers(keyed_accounts); - let res = withdraw( - &keyed_accounts[0], - 0, - &KeyedAccount::new( - &solana_sdk::pubkey::new_rand(), - false, - &RefCell::new(AccountSharedData::default()), - ), - &signers, - None, - None, - ); - assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); - - // insufficient funds - let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let lamports = vote_account.borrow().lamports(); - let signers: HashSet = get_signers(keyed_accounts); - let res = withdraw( - &keyed_accounts[0], - lamports + 1, - &KeyedAccount::new( - &solana_sdk::pubkey::new_rand(), - false, - &RefCell::new(AccountSharedData::default()), - ), - &signers, - None, - Some(&Clock::default()), - ); - assert_eq!(res, Err(InstructionError::InsufficientFunds)); - - // non rent exempt withdraw, before 7txXZZD6 feature activation - // without 0 credit epoch, before ALBk3EWd feature activation - { - let (vote_pubkey, vote_account_with_epoch_credits) = - create_test_account_with_epoch_credits(&credits_through_epoch_2); - let keyed_accounts = &[KeyedAccount::new( - &vote_pubkey, - true, - &vote_account_with_epoch_credits, - )]; - let lamports = vote_account_with_epoch_credits.borrow().lamports(); - let rent_sysvar = Rent::default(); - let minimum_balance = rent_sysvar - .minimum_balance(vote_account_with_epoch_credits.borrow().data().len()) - .max(1); - assert!(minimum_balance <= lamports); - let signers: HashSet = get_signers(keyed_accounts); - let res = withdraw( - &keyed_accounts[0], - lamports - minimum_balance + 1, - &KeyedAccount::new( - &solana_sdk::pubkey::new_rand(), - false, - &RefCell::new(AccountSharedData::default()), - ), - &signers, - None, - None, - ); - assert_eq!(res, Ok(())); - } - - // non rent exempt withdraw, before 7txXZZD6 feature activation - // with 0 credit epoch, before ALBk3EWd feature activation - { - let (vote_pubkey, vote_account_with_epoch_credits) = - create_test_account_with_epoch_credits(&credits_through_epoch_1); - let keyed_accounts = &[KeyedAccount::new( - &vote_pubkey, - true, - &vote_account_with_epoch_credits, - )]; - let lamports = vote_account_with_epoch_credits.borrow().lamports(); - let rent_sysvar = Rent::default(); - let minimum_balance = rent_sysvar - .minimum_balance(vote_account_with_epoch_credits.borrow().data().len()) - .max(1); - assert!(minimum_balance <= lamports); - let signers: HashSet = get_signers(keyed_accounts); - let res = withdraw( - &keyed_accounts[0], - lamports - minimum_balance + 1, - &KeyedAccount::new( - &solana_sdk::pubkey::new_rand(), - false, - &RefCell::new(AccountSharedData::default()), - ), - &signers, - None, - None, - ); - assert_eq!(res, Ok(())); - } - - // non rent exempt withdraw, before 7txXZZD6 feature activation - // without 0 credit epoch, after ALBk3EWd feature activation - { - let (vote_pubkey, vote_account_with_epoch_credits) = - create_test_account_with_epoch_credits(&credits_through_epoch_2); - let keyed_accounts = &[KeyedAccount::new( - &vote_pubkey, - true, - &vote_account_with_epoch_credits, - )]; - let lamports = vote_account_with_epoch_credits.borrow().lamports(); - let rent_sysvar = Rent::default(); - let minimum_balance = rent_sysvar - .minimum_balance(vote_account_with_epoch_credits.borrow().data().len()) - .max(1); - assert!(minimum_balance <= lamports); - let signers: HashSet = get_signers(keyed_accounts); - let res = withdraw( - &keyed_accounts[0], - lamports - minimum_balance + 1, - &KeyedAccount::new( - &solana_sdk::pubkey::new_rand(), - false, - &RefCell::new(AccountSharedData::default()), - ), - &signers, - None, - Some(clock_epoch_3), - ); - assert_eq!(res, Ok(())); - } - - // non rent exempt withdraw, before 7txXZZD6 feature activation - // with 0 credit epoch, after ALBk3EWd activation - { - let (vote_pubkey, vote_account_with_epoch_credits) = - create_test_account_with_epoch_credits(&credits_through_epoch_1); - let keyed_accounts = &[KeyedAccount::new( - &vote_pubkey, - true, - &vote_account_with_epoch_credits, - )]; - let lamports = vote_account_with_epoch_credits.borrow().lamports(); - let rent_sysvar = Rent::default(); - let minimum_balance = rent_sysvar - .minimum_balance(vote_account_with_epoch_credits.borrow().data().len()) - .max(1); - assert!(minimum_balance <= lamports); - let signers: HashSet = get_signers(keyed_accounts); - let res = withdraw( - &keyed_accounts[0], - lamports - minimum_balance + 1, - &KeyedAccount::new( - &solana_sdk::pubkey::new_rand(), - false, - &RefCell::new(AccountSharedData::default()), - ), - &signers, - None, - Some(clock_epoch_3), - ); - assert_eq!(res, Ok(())); - } - - // non rent exempt withdraw, after 7txXZZD6 feature activation - // with 0 credit epoch, before ALBk3EWd feature activation - { - let (vote_pubkey, vote_account_with_epoch_credits) = - create_test_account_with_epoch_credits(&credits_through_epoch_1); - let keyed_accounts = &[KeyedAccount::new( - &vote_pubkey, - true, - &vote_account_with_epoch_credits, - )]; - let lamports = vote_account_with_epoch_credits.borrow().lamports(); - let rent_sysvar = Rent::default(); - let minimum_balance = rent_sysvar - .minimum_balance(vote_account_with_epoch_credits.borrow().data().len()) - .max(1); - assert!(minimum_balance <= lamports); - let signers: HashSet = get_signers(keyed_accounts); - let res = withdraw( - &keyed_accounts[0], - lamports - minimum_balance + 1, - &KeyedAccount::new( - &solana_sdk::pubkey::new_rand(), - false, - &RefCell::new(AccountSharedData::default()), - ), - &signers, - Some(&rent_sysvar), - None, - ); - assert_eq!(res, Err(InstructionError::InsufficientFunds)); - } - - // non rent exempt withdraw, after 7txXZZD6 feature activation - // without 0 credit epoch, before ALBk3EWd feature activation - { - let (vote_pubkey, vote_account_with_epoch_credits) = - create_test_account_with_epoch_credits(&credits_through_epoch_2); - let keyed_accounts = &[KeyedAccount::new( - &vote_pubkey, - true, - &vote_account_with_epoch_credits, - )]; - let lamports = vote_account_with_epoch_credits.borrow().lamports(); - let rent_sysvar = Rent::default(); - let minimum_balance = rent_sysvar - .minimum_balance(vote_account_with_epoch_credits.borrow().data().len()) - .max(1); - assert!(minimum_balance <= lamports); - let signers: HashSet = get_signers(keyed_accounts); - let res = withdraw( - &keyed_accounts[0], - lamports - minimum_balance + 1, - &KeyedAccount::new( - &solana_sdk::pubkey::new_rand(), - false, - &RefCell::new(AccountSharedData::default()), - ), - &signers, - Some(&rent_sysvar), - None, - ); - assert_eq!(res, Err(InstructionError::InsufficientFunds)); - } - - // non rent exempt withdraw, after 7txXZZD6 feature activation - // with 0 credit epoch, after ALBk3EWd feature activation - { - let (vote_pubkey, vote_account_with_epoch_credits) = - create_test_account_with_epoch_credits(&credits_through_epoch_1); - let keyed_accounts = &[KeyedAccount::new( - &vote_pubkey, - true, - &vote_account_with_epoch_credits, - )]; - let lamports = vote_account_with_epoch_credits.borrow().lamports(); - let rent_sysvar = Rent::default(); - let minimum_balance = rent_sysvar - .minimum_balance(vote_account_with_epoch_credits.borrow().data().len()) - .max(1); - assert!(minimum_balance <= lamports); - let signers: HashSet = get_signers(keyed_accounts); - let res = withdraw( - &keyed_accounts[0], - lamports - minimum_balance + 1, - &KeyedAccount::new( - &solana_sdk::pubkey::new_rand(), - false, - &RefCell::new(AccountSharedData::default()), - ), - &signers, - Some(&rent_sysvar), - Some(clock_epoch_3), - ); - assert_eq!(res, Err(InstructionError::InsufficientFunds)); - } - - // non rent exempt withdraw, after 7txXZZD6 feature activation - // without 0 credit epoch, after ALBk3EWd feature activation - { - let (vote_pubkey, vote_account_with_epoch_credits) = - create_test_account_with_epoch_credits(&credits_through_epoch_2); - let keyed_accounts = &[KeyedAccount::new( - &vote_pubkey, - true, - &vote_account_with_epoch_credits, - )]; - let lamports = vote_account_with_epoch_credits.borrow().lamports(); - let rent_sysvar = Rent::default(); - let minimum_balance = rent_sysvar - .minimum_balance(vote_account_with_epoch_credits.borrow().data().len()) - .max(1); - assert!(minimum_balance <= lamports); - let signers: HashSet = get_signers(keyed_accounts); - let res = withdraw( - &keyed_accounts[0], - lamports - minimum_balance + 1, - &KeyedAccount::new( - &solana_sdk::pubkey::new_rand(), - false, - &RefCell::new(AccountSharedData::default()), - ), - &signers, - Some(&rent_sysvar), - Some(clock_epoch_3), - ); - assert_eq!(res, Err(InstructionError::InsufficientFunds)); - } - - // partial valid withdraw, after 7txXZZD6 feature activation - { - let to_account = RefCell::new(AccountSharedData::default()); - let (vote_pubkey, vote_account) = create_test_account(); - let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let lamports = vote_account.borrow().lamports(); - let rent_sysvar = Rent::default(); - let minimum_balance = rent_sysvar - .minimum_balance(vote_account.borrow().data().len()) - .max(1); - assert!(minimum_balance <= lamports); - let withdraw_lamports = lamports - minimum_balance; - let signers: HashSet = get_signers(keyed_accounts); - let res = withdraw( - &keyed_accounts[0], - withdraw_lamports, - &KeyedAccount::new(&solana_sdk::pubkey::new_rand(), false, &to_account), - &signers, - Some(&rent_sysvar), - Some(&Clock::default()), - ); - assert_eq!(res, Ok(())); - assert_eq!( - vote_account.borrow().lamports(), - lamports - withdraw_lamports - ); - assert_eq!(to_account.borrow().lamports(), withdraw_lamports); - } - - // full withdraw, before/after 7txXZZD6 feature activation - // with/without 0 credit epoch, before ALBk3EWd feature activation - { - let rent_sysvar = Rent::default(); - for rent_sysvar in [None, Some(&rent_sysvar)] { - for credits in [&credits_through_epoch_1, &credits_through_epoch_2] { - let to_account = RefCell::new(AccountSharedData::default()); - let (vote_pubkey, vote_account) = - create_test_account_with_epoch_credits(credits); - let lamports = vote_account.borrow().lamports(); - let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let signers: HashSet = get_signers(keyed_accounts); - let res = withdraw( - &keyed_accounts[0], - lamports, - &KeyedAccount::new(&solana_sdk::pubkey::new_rand(), false, &to_account), - &signers, - rent_sysvar, - None, - ); - assert_eq!(res, Ok(())); - assert_eq!(vote_account.borrow().lamports(), 0); - assert_eq!(to_account.borrow().lamports(), lamports); - let post_state: VoteStateVersions = vote_account.borrow().state().unwrap(); - // State has been deinitialized since balance is zero - assert!(post_state.is_uninitialized()); - } - } - } - - // full withdraw, before/after 7txXZZD6 feature activation - // with 0 credit epoch, after ALBk3EWd feature activation - { - let rent_sysvar = Rent::default(); - for rent_sysvar in [None, Some(&rent_sysvar)] { - let to_account = RefCell::new(AccountSharedData::default()); - // let (vote_pubkey, vote_account) = create_test_account(); - let (vote_pubkey, vote_account) = - create_test_account_with_epoch_credits(&credits_through_epoch_1); - let lamports = vote_account.borrow().lamports(); - let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let signers: HashSet = get_signers(keyed_accounts); - let res = withdraw( - &keyed_accounts[0], - lamports, - &KeyedAccount::new(&solana_sdk::pubkey::new_rand(), false, &to_account), - &signers, - rent_sysvar, - Some(clock_epoch_3), - ); - assert_eq!(res, Ok(())); - assert_eq!(vote_account.borrow().lamports(), 0); - assert_eq!(to_account.borrow().lamports(), lamports); - let post_state: VoteStateVersions = vote_account.borrow().state().unwrap(); - // State has been deinitialized since balance is zero - assert!(post_state.is_uninitialized()); - } - } - - // full withdraw, before/after 7txXZZD6 feature activation - // without 0 credit epoch, after ALBk3EWd feature activation - { - let rent_sysvar = Rent::default(); - for rent_sysvar in [None, Some(&rent_sysvar)] { - let to_account = RefCell::new(AccountSharedData::default()); - // let (vote_pubkey, vote_account) = create_test_account(); - let (vote_pubkey, vote_account) = - create_test_account_with_epoch_credits(&credits_through_epoch_2); - let lamports = vote_account.borrow().lamports(); - let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let signers: HashSet = get_signers(keyed_accounts); - let res = withdraw( - &keyed_accounts[0], - lamports, - &KeyedAccount::new(&solana_sdk::pubkey::new_rand(), false, &to_account), - &signers, - rent_sysvar, - Some(clock_epoch_3), - ); - assert_eq!(res, Err(InstructionError::ActiveVoteAccountClose)); - assert_eq!(vote_account.borrow().lamports(), lamports); - assert_eq!(to_account.borrow().lamports(), 0); - let post_state: VoteStateVersions = vote_account.borrow().state().unwrap(); - // State is still initialized - assert!(!post_state.is_uninitialized()); - } - } - - // authorize authorized_withdrawer - let authorized_withdrawer_pubkey = solana_sdk::pubkey::new_rand(); - let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let signers: HashSet = get_signers(keyed_accounts); - let res = authorize( - &keyed_accounts[0], - &authorized_withdrawer_pubkey, - VoteAuthorize::Withdrawer, - &signers, - &Clock::default(), - &FeatureSet::default(), - ); - assert_eq!(res, Ok(())); - - // withdraw using authorized_withdrawer to authorized_withdrawer's account - let withdrawer_account = RefCell::new(AccountSharedData::default()); - let keyed_accounts = &[ - KeyedAccount::new(&vote_pubkey, false, &vote_account), - KeyedAccount::new(&authorized_withdrawer_pubkey, true, &withdrawer_account), - ]; - let signers: HashSet = get_signers(keyed_accounts); - let vote_keyed_account = keyed_account_at_index(keyed_accounts, 0).unwrap(); - let withdrawer_keyed_account = keyed_account_at_index(keyed_accounts, 1).unwrap(); - let res = withdraw( - vote_keyed_account, - lamports, - withdrawer_keyed_account, - &signers, - None, - None, - ); - assert_eq!(res, Ok(())); - assert_eq!(vote_account.borrow().lamports(), 0); - assert_eq!(withdrawer_account.borrow().lamports(), lamports); - let post_state: VoteStateVersions = vote_account.borrow().state().unwrap(); - // State has been deinitialized since balance is zero - assert!(post_state.is_uninitialized()); - } - #[test] fn test_vote_state_epoch_credits() { let mut vote_state = VoteState::default();