Skip to content

Commit bac9ce1

Browse files
feat: add some missing tests that were already planned via // TODO (#470)
* feat: add TODO validator removal tests * feat: add 2 missing add validator tests * feat: update validator_list helper * style: rm mut fmt inconsistency * fix: rustfmt + clippy program
1 parent 6d49d29 commit bac9ce1

File tree

3 files changed

+183
-5
lines changed

3 files changed

+183
-5
lines changed

program/tests/helpers/mod.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2668,6 +2668,33 @@ pub async fn setup_for_withdraw(
26682668
)
26692669
}
26702670

2671+
pub async fn set_validator_list_to_uninitialized_account(
2672+
context: &mut ProgramTestContext,
2673+
stake_pool_accounts: &StakePoolAccounts,
2674+
) {
2675+
// Get the rent-exempt minimum for the account size
2676+
let rent = context.banks_client.get_rent().await.unwrap();
2677+
let account_size = std::mem::size_of::<state::ValidatorList>();
2678+
let minimum_balance = rent.minimum_balance(account_size);
2679+
2680+
// Create an uninitialized account at the SAME validator list address
2681+
// This simulates the validator list account being corrupted/uninitialized
2682+
// while keeping the same pubkey that the stake pool expects
2683+
let uninitialized_account = solana_sdk::account::Account {
2684+
lamports: minimum_balance,
2685+
data: vec![0u8; account_size], // All zeros - truly uninitialized
2686+
owner: id(), // Owned by stake pool program
2687+
executable: false,
2688+
rent_epoch: 0,
2689+
};
2690+
2691+
// Set the uninitialized account at the original validator list address
2692+
context.set_account(
2693+
&stake_pool_accounts.validator_list.pubkey(),
2694+
&uninitialized_account.into(),
2695+
);
2696+
}
2697+
26712698
#[derive(Copy, Clone, Debug, PartialEq)]
26722699
pub enum DecreaseInstruction {
26732700
Additional,

program/tests/vsa_add.rs

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,46 @@ async fn setup(
7272
)
7373
}
7474

75+
// Setup function that returns ProgramTestContext for tests that need epoch warping
76+
async fn setup_with_context(
77+
num_validators: u64,
78+
) -> (ProgramTestContext, StakePoolAccounts, ValidatorStakeAccount) {
79+
let mut context = program_test().start_with_context().await;
80+
let rent = context.banks_client.get_rent().await.unwrap();
81+
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake::state::StakeStateV2>());
82+
let current_minimum_delegation = stake_pool_get_minimum_delegation(
83+
&mut context.banks_client,
84+
&context.payer,
85+
&context.last_blockhash,
86+
)
87+
.await;
88+
let minimum_for_validator = stake_rent + current_minimum_delegation;
89+
90+
let stake_pool_accounts = StakePoolAccounts::default();
91+
stake_pool_accounts
92+
.initialize_stake_pool(
93+
&mut context.banks_client,
94+
&context.payer,
95+
&context.last_blockhash,
96+
MINIMUM_RESERVE_LAMPORTS + num_validators * minimum_for_validator,
97+
)
98+
.await
99+
.unwrap();
100+
101+
let validator_stake =
102+
ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey(), None, 0);
103+
create_vote(
104+
&mut context.banks_client,
105+
&context.payer,
106+
&context.last_blockhash,
107+
&validator_stake.validator,
108+
&validator_stake.vote,
109+
)
110+
.await;
111+
112+
(context, stake_pool_accounts, validator_stake)
113+
}
114+
75115
#[tokio::test]
76116
async fn success() {
77117
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
@@ -501,10 +541,66 @@ async fn fail_add_too_many_validator_stake_accounts() {
501541
}
502542

503543
#[tokio::test]
504-
async fn fail_with_unupdated_stake_pool() {} // TODO
544+
async fn fail_with_not_updated_stake_pool() {
545+
let (mut context, stake_pool_accounts, validator_stake) = setup_with_context(1).await;
546+
547+
// Move to next epoch
548+
let first_normal_slot = context.genesis_config().epoch_schedule.first_normal_slot;
549+
let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
550+
let slot = first_normal_slot + slots_per_epoch + 1;
551+
context.warp_to_slot(slot).unwrap();
552+
553+
// Do not update stake pool
554+
555+
let transaction_error = stake_pool_accounts
556+
.add_validator_to_pool(
557+
&mut context.banks_client,
558+
&context.payer,
559+
&context.last_blockhash,
560+
&validator_stake.stake_account,
561+
&validator_stake.vote.pubkey(),
562+
validator_stake.validator_stake_seed,
563+
)
564+
.await;
565+
let transaction_error = transaction_error.unwrap();
566+
let program_error = StakePoolError::StakeListAndPoolOutOfDate as u32;
567+
match transaction_error {
568+
TransportError::TransactionError(TransactionError::InstructionError(_, error)) => {
569+
assert_eq!(error, InstructionError::Custom(program_error));
570+
}
571+
_ => panic!("Wrong error occurs while trying to add validator to outdated stake pool"),
572+
}
573+
}
505574

506575
#[tokio::test]
507-
async fn fail_with_uninitialized_validator_list_account() {} // TODO
576+
async fn fail_with_uninitialized_validator_list_account() {
577+
let (mut context, stake_pool_accounts, validator_stake) = setup_with_context(1).await;
578+
579+
// Set the validator list to an uninitialized account
580+
set_validator_list_to_uninitialized_account(&mut context, &stake_pool_accounts).await;
581+
582+
// Attempt to add validator to pool with uninitialized validator list
583+
let transaction_error = stake_pool_accounts
584+
.add_validator_to_pool(
585+
&mut context.banks_client,
586+
&context.payer,
587+
&context.last_blockhash,
588+
&validator_stake.stake_account,
589+
&validator_stake.vote.pubkey(),
590+
validator_stake.validator_stake_seed,
591+
)
592+
.await;
593+
let transaction_error = transaction_error.unwrap();
594+
let program_error = StakePoolError::InvalidState as u32;
595+
match transaction_error {
596+
TransportError::TransactionError(TransactionError::InstructionError(_, error)) => {
597+
assert_eq!(error, InstructionError::Custom(program_error));
598+
}
599+
_ => panic!(
600+
"Wrong error occurs while trying to add validator with uninitialized validator list"
601+
),
602+
}
603+
}
508604

509605
#[tokio::test]
510606
async fn fail_on_non_vote_account() {

program/tests/vsa_remove.rs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ mod helpers;
33

44
use {
55
bincode::deserialize,
6-
helpers::*,
6+
helpers::{set_validator_list_to_uninitialized_account, *},
77
solana_program::{
88
borsh1::try_from_slice_unchecked,
99
instruction::{AccountMeta, Instruction, InstructionError},
@@ -843,10 +843,65 @@ async fn success_with_hijacked_transient_account() {
843843
}
844844

845845
#[tokio::test]
846-
async fn fail_not_updated_stake_pool() {} // TODO
846+
async fn fail_not_updated_stake_pool() {
847+
let (mut context, stake_pool_accounts, validator_stake) = setup().await;
848+
849+
// move to next epoch
850+
let first_normal_slot = context.genesis_config().epoch_schedule.first_normal_slot;
851+
let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
852+
let slot = first_normal_slot + slots_per_epoch + 1;
853+
context.warp_to_slot(slot).unwrap();
854+
855+
// do not update stake pool
856+
857+
let transaction_error = stake_pool_accounts
858+
.remove_validator_from_pool(
859+
&mut context.banks_client,
860+
&context.payer,
861+
&context.last_blockhash,
862+
&validator_stake.stake_account,
863+
&validator_stake.transient_stake_account,
864+
)
865+
.await;
866+
let transaction_error = transaction_error.unwrap();
867+
match transaction_error {
868+
TransportError::TransactionError(TransactionError::InstructionError(_, error)) => {
869+
let program_error = StakePoolError::StakeListAndPoolOutOfDate as u32;
870+
assert_eq!(error, InstructionError::Custom(program_error));
871+
}
872+
_ => panic!("Wrong error occurs while trying to remove validator from outdated stake pool"),
873+
}
874+
}
847875

848876
#[tokio::test]
849-
async fn fail_with_uninitialized_validator_list_account() {} // TODO
877+
async fn fail_with_uninitialized_validator_list_account() {
878+
let (mut context, stake_pool_accounts, validator_stake) = setup().await;
879+
880+
// Set the validator list to an uninitialized account
881+
set_validator_list_to_uninitialized_account(&mut context, &stake_pool_accounts).await;
882+
883+
// Attempt to remove validator from pool with uninitialized validator list
884+
let transaction_error = stake_pool_accounts
885+
.remove_validator_from_pool(
886+
&mut context.banks_client,
887+
&context.payer,
888+
&context.last_blockhash,
889+
&validator_stake.stake_account,
890+
&validator_stake.transient_stake_account,
891+
)
892+
.await;
893+
894+
let transaction_error = transaction_error.unwrap();
895+
let program_error = StakePoolError::InvalidState as u32;
896+
match transaction_error {
897+
TransportError::TransactionError(TransactionError::InstructionError(_, error)) => {
898+
assert_eq!(error, InstructionError::Custom(program_error));
899+
}
900+
_ => panic!(
901+
"Wrong error occurs while trying to remove validator with uninitialized validator list"
902+
),
903+
}
904+
}
850905

851906
#[tokio::test]
852907
async fn update_no_merge_after_removal() {

0 commit comments

Comments
 (0)