diff --git a/.gitignore b/.gitignore index dbe4b7e2..1fcfbf16 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ test-ledger **/targets **/credentials **/config -**/*.env \ No newline at end of file +**/*.env +/scripts \ No newline at end of file diff --git a/programs/steward/README.md b/programs/steward/README.md index 0c565aa6..6a80e989 100644 --- a/programs/steward/README.md +++ b/programs/steward/README.md @@ -207,7 +207,7 @@ Administrators can: | `instant_unstake_epoch_progress` | 0.90 | Point in epoch progress before instant unstake can be computed | | `instant_unstake_inputs_epoch_progress` | 0.50 | Inputs to “Compute Instant Unstake” need to be updated past this point in epoch progress | | `num_epochs_between_scoring` | 10 | Cycle length - Number of epochs to run the Monitor->Rebalance loop | -| `minimum_stake_lamports` | 5,000,000,000 | Minimum number of stake lamports for a validator to be considered for the pool | +| `minimum_stake_lamports` | 5,000,000,000,000 (5000 SOL) | Minimum number of stake lamports for a validator to be considered for the pool | | `minimum_voting_epochs` | 5 | Minimum number of consecutive epochs a validator has to vote before it can be considered for the pool | ## Code and Tests diff --git a/programs/steward/src/instructions/initialize_config.rs b/programs/steward/src/instructions/initialize_config.rs index b4d9bdb3..7b22765c 100644 --- a/programs/steward/src/instructions/initialize_config.rs +++ b/programs/steward/src/instructions/initialize_config.rs @@ -1,6 +1,6 @@ use anchor_lang::{prelude::*, solana_program::program::invoke}; -use crate::{utils::StakePool, Config, Staker}; +use crate::{utils::StakePool, Config, Staker, UpdateParametersArgs}; #[derive(Accounts)] pub struct InitializeConfig<'info> { @@ -39,11 +39,28 @@ pub struct InitializeConfig<'info> { pub signer: Signer<'info>, } -pub fn handler(ctx: Context, authority: Pubkey) -> Result<()> { +pub fn handler( + ctx: Context, + authority: Pubkey, + update_parameters_args: &UpdateParametersArgs, +) -> Result<()> { let mut config = ctx.accounts.config.load_init()?; config.stake_pool = ctx.accounts.stake_pool.key(); config.authority = authority; + // Set Initial Parameters + let max_slots_in_epoch = EpochSchedule::get()?.slots_per_epoch; + let current_epoch = Clock::get()?.epoch; + + let initial_parameters = config.parameters.get_valid_updated_parameters( + update_parameters_args, + current_epoch, + max_slots_in_epoch, + )?; + + config.parameters = initial_parameters; + + // Set the staker account ctx.accounts.staker.bump = ctx.bumps.staker; invoke( &spl_stake_pool::instruction::set_staker( diff --git a/programs/steward/src/instructions/update_parameters.rs b/programs/steward/src/instructions/update_parameters.rs index 9e3525e7..97032210 100644 --- a/programs/steward/src/instructions/update_parameters.rs +++ b/programs/steward/src/instructions/update_parameters.rs @@ -14,9 +14,17 @@ pub fn handler( ctx: Context, update_parameters_args: &UpdateParametersArgs, ) -> Result<()> { - let mut parameters = ctx.accounts.config.load_mut()?.parameters; + let mut config = ctx.accounts.config.load_mut()?; let max_slots_in_epoch = EpochSchedule::get()?.slots_per_epoch; let current_epoch = Clock::get()?.epoch; - parameters.update(update_parameters_args, current_epoch, max_slots_in_epoch)?; + + let new_parameters = config.parameters.get_valid_updated_parameters( + update_parameters_args, + current_epoch, + max_slots_in_epoch, + )?; + + config.parameters = new_parameters; + Ok(()) } diff --git a/programs/steward/src/lib.rs b/programs/steward/src/lib.rs index 2b10c52f..c668bc0b 100644 --- a/programs/steward/src/lib.rs +++ b/programs/steward/src/lib.rs @@ -57,8 +57,12 @@ pub mod steward { // Initializes Config and Staker accounts. Must be called before any other instruction // Requires Pool to be initialized - pub fn initialize_config(ctx: Context, authority: Pubkey) -> Result<()> { - instructions::initialize_config::handler(ctx, authority) + pub fn initialize_config( + ctx: Context, + authority: Pubkey, + update_parameters_args: UpdateParametersArgs, + ) -> Result<()> { + instructions::initialize_config::handler(ctx, authority, &update_parameters_args) } /// Creates state account diff --git a/programs/steward/src/state/parameters.rs b/programs/steward/src/state/parameters.rs index 8d573899..0bfaa01f 100644 --- a/programs/steward/src/state/parameters.rs +++ b/programs/steward/src/state/parameters.rs @@ -10,7 +10,7 @@ use crate::{ errors::StewardError, }; -#[derive(BorshSerialize, BorshDeserialize, Debug, Default)] +#[derive(BorshSerialize, BorshDeserialize, Debug, Default, Clone)] pub struct UpdateParametersArgs { // Scoring parameters pub mev_commission_range: Option, @@ -101,13 +101,13 @@ pub struct Parameters { } impl Parameters { - /// Update parameters that are present in the args struct and validates them - pub fn update( - &mut self, + /// Merges the updated parameters with the current parameters and validates them + pub fn get_valid_updated_parameters( + self, args: &UpdateParametersArgs, current_epoch: u64, slots_per_epoch: u64, - ) -> Result<()> { + ) -> Result { // Updates parameters and validates them let UpdateParametersArgs { mev_commission_range, @@ -130,7 +130,7 @@ impl Parameters { minimum_voting_epochs, } = *args; - let mut new_parameters = *self; + let mut new_parameters = self; if let Some(mev_commission_range) = mev_commission_range { new_parameters.mev_commission_range = mev_commission_range; @@ -209,9 +209,10 @@ impl Parameters { new_parameters.minimum_voting_epochs = minimum_voting_epochs; } + // Validation will throw an error if any of the parameters are invalid new_parameters.validate(current_epoch, slots_per_epoch)?; - *self = new_parameters; - Ok(()) + + Ok(new_parameters) } /// Validate reasonable bounds on parameters diff --git a/tests/src/steward_fixtures.rs b/tests/src/steward_fixtures.rs index 72ce9014..f7a97489 100644 --- a/tests/src/steward_fixtures.rs +++ b/tests/src/steward_fixtures.rs @@ -15,6 +15,7 @@ use jito_steward::{ constants::{MAX_VALIDATORS, SORTED_INDEX_DEFAULT, STAKE_POOL_WITHDRAW_SEED}, utils::StakePool, Config, Delegation, Parameters, Staker, StewardState, StewardStateAccount, StewardStateEnum, + UpdateParametersArgs, }; use solana_program_test::*; use solana_sdk::{ @@ -231,7 +232,29 @@ impl TestFixture { } } - pub async fn initialize_config(&self) { + pub async fn initialize_config(&self, parameters: Option) { + // Default parameters from JIP + let update_parameters_args = parameters.unwrap_or(UpdateParametersArgs { + mev_commission_range: Some(0), // Set to pass validation, where epochs starts at 0 + epoch_credits_range: Some(0), // Set to pass validation, where epochs starts at 0 + commission_range: Some(0), // Set to pass validation, where epochs starts at 0 + scoring_delinquency_threshold_ratio: Some(0.85), + instant_unstake_delinquency_threshold_ratio: Some(0.70), + mev_commission_bps_threshold: Some(1000), + commission_threshold: Some(5), + historical_commission_threshold: Some(50), + num_delegation_validators: Some(200), + scoring_unstake_cap_bps: Some(750), + instant_unstake_cap_bps: Some(10), + stake_deposit_unstake_cap_bps: Some(10), + instant_unstake_epoch_progress: Some(0.90), + compute_score_slot_range: Some(1000), + instant_unstake_inputs_epoch_progress: Some(0.50), + num_epochs_between_scoring: Some(10), + minimum_stake_lamports: Some(5_000_000_000), + minimum_voting_epochs: Some(0), // Set to pass validation, where epochs starts at 0 + }); + let instruction = Instruction { program_id: jito_steward::id(), accounts: jito_steward::accounts::InitializeConfig { @@ -245,6 +268,7 @@ impl TestFixture { .to_account_metas(None), data: jito_steward::instruction::InitializeConfig { authority: self.keypair.pubkey(), + update_parameters_args, } .data(), }; @@ -579,6 +603,15 @@ pub fn new_vote_account( } } +pub fn closed_vote_account() -> Account { + Account { + lamports: 0, + data: vec![0; VoteState::size_of()], + owner: anchor_lang::system_program::ID, // Close the account + ..Account::default() + } +} + // TODO write a function to serialize any account with T: AnchorSerialize pub fn serialized_validator_list_account( validator_list: ValidatorList, diff --git a/tests/tests/steward/mod.rs b/tests/tests/steward/mod.rs index fd951ae8..85b64831 100644 --- a/tests/tests/steward/mod.rs +++ b/tests/tests/steward/mod.rs @@ -1,5 +1,6 @@ mod test_algorithms; mod test_integration; +mod test_parameters; mod test_spl_passthrough; mod test_state_methods; mod test_state_transitions; diff --git a/tests/tests/steward/test_integration.rs b/tests/tests/steward/test_integration.rs index ea3ded46..b1a231ad 100644 --- a/tests/tests/steward/test_integration.rs +++ b/tests/tests/steward/test_integration.rs @@ -8,7 +8,7 @@ use anchor_lang::{ use jito_steward::{ constants::{MAX_VALIDATORS, SORTED_INDEX_DEFAULT}, utils::{StakePool, ValidatorList}, - Config, Delegation, StewardStateAccount, StewardStateEnum, + Config, Delegation, StewardStateAccount, StewardStateEnum, UpdateParametersArgs, }; use solana_program_test::*; use solana_sdk::{ @@ -36,7 +36,7 @@ async fn test_compute_delegations() { let fixture = TestFixture::new().await; let ctx = &fixture.ctx; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture.initialize_config(None).await; fixture.initialize_steward_state().await; let clock: Clock = fixture.get_sysvar().await; @@ -173,7 +173,7 @@ async fn test_compute_scores() { let fixture = TestFixture::new().await; let ctx = &fixture.ctx; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture.initialize_config(None).await; fixture.initialize_steward_state().await; let epoch_credits = vec![(0, 1, 0), (1, 2, 1), (2, 3, 2), (3, 4, 3), (4, 5, 4)]; @@ -422,7 +422,28 @@ async fn test_compute_instant_unstake() { let fixture = TestFixture::new().await; let ctx = &fixture.ctx; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture + .initialize_config(Some(UpdateParametersArgs { + mev_commission_range: Some(0), // Set to pass validation, where epochs starts at 0 + epoch_credits_range: Some(0), // Set to pass validation, where epochs starts at 0 + commission_range: Some(0), // Set to pass validation, where epochs starts at 0 + scoring_delinquency_threshold_ratio: Some(0.85), + instant_unstake_delinquency_threshold_ratio: Some(0.70), + mev_commission_bps_threshold: Some(1000), + commission_threshold: Some(5), + historical_commission_threshold: Some(50), + num_delegation_validators: Some(200), + scoring_unstake_cap_bps: Some(750), + instant_unstake_cap_bps: Some(10), + stake_deposit_unstake_cap_bps: Some(10), + instant_unstake_epoch_progress: Some(0.0), // So that we don't have to increase the slots + compute_score_slot_range: Some(1000), + instant_unstake_inputs_epoch_progress: Some(0.50), + num_epochs_between_scoring: Some(10), + minimum_stake_lamports: Some(5_000_000_000), + minimum_voting_epochs: Some(0), // Set to pass validation, where epochs starts at 0 + })) + .await; fixture.initialize_steward_state().await; let epoch_credits = vec![(0, 1, 0), (1, 2, 1), (2, 3, 2), (3, 4, 3), (4, 5, 4)]; @@ -435,6 +456,7 @@ async fn test_compute_instant_unstake() { let clock: Clock = fixture.get_sysvar().await; let epoch_schedule: EpochSchedule = fixture.get_sysvar().await; + fixture.ctx.borrow_mut().set_account( &vote_account, &new_vote_account(Pubkey::new_unique(), vote_account, 100, Some(epoch_credits)).into(), @@ -635,7 +657,7 @@ async fn test_idle() { let fixture = TestFixture::new().await; let ctx = &fixture.ctx; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture.initialize_config(None).await; fixture.initialize_steward_state().await; let clock: Clock = fixture.get_sysvar().await; @@ -771,7 +793,7 @@ async fn test_rebalance_increase() { .advance_num_epochs(epoch_schedule.first_normal_epoch - clock.epoch, 10) .await; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture.initialize_config(None).await; fixture.initialize_steward_state().await; let mut steward_config: Config = fixture @@ -1009,7 +1031,7 @@ async fn test_rebalance_decrease() { .advance_num_epochs(epoch_schedule.first_normal_epoch - clock.epoch, 10) .await; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture.initialize_config(None).await; fixture.initialize_steward_state().await; let mut steward_config: Config = fixture @@ -1311,7 +1333,7 @@ async fn test_rebalance_other_cases() { .advance_num_epochs(epoch_schedule.first_normal_epoch - clock.epoch, 10) .await; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture.initialize_config(None).await; fixture.initialize_steward_state().await; let mut steward_config: Config = fixture diff --git a/tests/tests/steward/test_parameters.rs b/tests/tests/steward/test_parameters.rs new file mode 100644 index 00000000..b9861af9 --- /dev/null +++ b/tests/tests/steward/test_parameters.rs @@ -0,0 +1,894 @@ +/// Basic integration test +use anchor_lang::{solana_program::instruction::Instruction, InstructionData, ToAccountMetas}; +use jito_steward::{ + constants::{ + BASIS_POINTS_MAX, COMMISSION_MAX, COMPUTE_SCORE_SLOT_RANGE_MIN, EPOCH_PROGRESS_MAX, + MAX_VALIDATORS, NUM_EPOCHS_BETWEEN_SCORING_MAX, + }, + Config, Parameters, UpdateParametersArgs, +}; +use solana_program_test::*; +use solana_sdk::{signer::Signer, transaction::Transaction}; +use tests::steward_fixtures::TestFixture; + +// ---------- INTEGRATION TESTS ---------- +async fn _set_parameter(fixture: &TestFixture, update_parameters: &UpdateParametersArgs) { + let ctx = &fixture.ctx; + let ix = Instruction { + program_id: jito_steward::id(), + accounts: jito_steward::accounts::UpdateParameters { + config: fixture.steward_config.pubkey(), + authority: fixture.keypair.pubkey(), + } + .to_account_metas(None), + data: jito_steward::instruction::UpdateParameters { + update_parameters_args: update_parameters.clone(), + } + .data(), + }; + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&fixture.keypair.pubkey()), + &[&fixture.keypair], + ctx.borrow().last_blockhash, + ); + + fixture.submit_transaction_assert_success(tx).await; + + let config: Config = fixture + .load_and_deserialize(&fixture.steward_config.pubkey()) + .await; + + if let Some(mev_commission_range) = update_parameters.mev_commission_range { + assert_eq!( + config.parameters.mev_commission_range, mev_commission_range, + "mev_commission_range, does not match update" + ); + } + + if let Some(epoch_credits_range) = update_parameters.epoch_credits_range { + assert_eq!( + config.parameters.epoch_credits_range, epoch_credits_range, + "epoch_credits_range, does not match update" + ); + } + + if let Some(commission_range) = update_parameters.commission_range { + assert_eq!( + config.parameters.commission_range, commission_range, + "commission_range, does not match update" + ); + } + + if let Some(scoring_delinquency_threshold_ratio) = + update_parameters.scoring_delinquency_threshold_ratio + { + assert_eq!( + config.parameters.scoring_delinquency_threshold_ratio, + scoring_delinquency_threshold_ratio, + "scoring_delinquency_threshold_ratio, does not match update" + ); + } + + if let Some(instant_unstake_delinquency_threshold_ratio) = + update_parameters.instant_unstake_delinquency_threshold_ratio + { + assert_eq!( + config + .parameters + .instant_unstake_delinquency_threshold_ratio, + instant_unstake_delinquency_threshold_ratio, + "instant_unstake_delinquency_threshold_ratio, does not match update" + ); + } + + if let Some(mev_commission_bps_threshold) = update_parameters.mev_commission_bps_threshold { + assert_eq!( + config.parameters.mev_commission_bps_threshold, mev_commission_bps_threshold, + "mev_commission_bps_threshold, does not match update" + ); + } + + if let Some(commission_threshold) = update_parameters.commission_threshold { + assert_eq!( + config.parameters.commission_threshold, commission_threshold, + "commission_threshold, does not match update" + ); + } + + if let Some(num_delegation_validators) = update_parameters.num_delegation_validators { + assert_eq!( + config.parameters.num_delegation_validators, num_delegation_validators, + "num_delegation_validators, does not match update" + ); + } + + if let Some(scoring_unstake_cap_bps) = update_parameters.scoring_unstake_cap_bps { + assert_eq!( + config.parameters.scoring_unstake_cap_bps, scoring_unstake_cap_bps, + "scoring_unstake_cap_bps, does not match update" + ); + } + + if let Some(instant_unstake_cap_bps) = update_parameters.instant_unstake_cap_bps { + assert_eq!( + config.parameters.instant_unstake_cap_bps, instant_unstake_cap_bps, + "instant_unstake_cap_bps, does not match update" + ); + } + + if let Some(stake_deposit_unstake_cap_bps) = update_parameters.stake_deposit_unstake_cap_bps { + assert_eq!( + config.parameters.stake_deposit_unstake_cap_bps, stake_deposit_unstake_cap_bps, + "stake_deposit_unstake_cap_bps, does not match update" + ); + } + + if let Some(instant_unstake_epoch_progress) = update_parameters.instant_unstake_epoch_progress { + assert_eq!( + config.parameters.instant_unstake_epoch_progress, instant_unstake_epoch_progress, + "instant_unstake_epoch_progress, does not match update" + ); + } + + if let Some(compute_score_slot_range) = update_parameters.compute_score_slot_range { + assert_eq!( + config.parameters.compute_score_slot_range, compute_score_slot_range, + "compute_score_slot_range, does not match update" + ); + } + + if let Some(instant_unstake_inputs_epoch_progress) = + update_parameters.instant_unstake_inputs_epoch_progress + { + assert_eq!( + config.parameters.instant_unstake_inputs_epoch_progress, + instant_unstake_inputs_epoch_progress, + "instant_unstake_inputs_epoch_progress, does not match update" + ); + } + + if let Some(num_epochs_between_scoring) = update_parameters.num_epochs_between_scoring { + assert_eq!( + config.parameters.num_epochs_between_scoring, num_epochs_between_scoring, + "num_epochs_between_scoring, does not match update" + ); + } + + if let Some(minimum_stake_lamports) = update_parameters.minimum_stake_lamports { + assert_eq!( + config.parameters.minimum_stake_lamports, minimum_stake_lamports, + "minimum_stake_lamports, does not match update" + ); + } + + if let Some(minimum_voting_epochs) = update_parameters.minimum_voting_epochs { + assert_eq!( + config.parameters.minimum_voting_epochs, minimum_voting_epochs, + "minimum_voting_epochs, does not match update" + ); + } +} + +#[tokio::test] +async fn test_update_parameters() { + let fixture = TestFixture::new().await; + fixture.initialize_stake_pool().await; + fixture.initialize_config(None).await; + fixture.initialize_steward_state().await; + + _set_parameter( + &fixture, + &UpdateParametersArgs { + ..UpdateParametersArgs::default() + }, + ) + .await; + + fixture.advance_num_epochs(3000, 0).await; + + _set_parameter( + &fixture, + &UpdateParametersArgs { + mev_commission_range: Some(10), + epoch_credits_range: Some(20), + commission_range: Some(20), + scoring_delinquency_threshold_ratio: Some(0.875), + instant_unstake_delinquency_threshold_ratio: Some(0.1), + mev_commission_bps_threshold: Some(999), + commission_threshold: Some(10), + historical_commission_threshold: Some(34), + num_delegation_validators: Some(3), + scoring_unstake_cap_bps: Some(1000), + instant_unstake_cap_bps: Some(1000), + stake_deposit_unstake_cap_bps: Some(1000), + instant_unstake_epoch_progress: Some(0.95), + compute_score_slot_range: Some(500), + instant_unstake_inputs_epoch_progress: Some(0.3), + num_epochs_between_scoring: Some(8), + minimum_stake_lamports: Some(1), + minimum_voting_epochs: Some(1), + }, + ) + .await; + + drop(fixture); +} + +// ---------- UNIT TESTS ---------- + +fn _test_parameter( + update_parameters: &UpdateParametersArgs, + current_epoch: Option, + slots_per_epoch: Option, + valid_parameters: Option, +) -> Result { + let valid_parameters = valid_parameters.unwrap_or(Parameters { + mev_commission_range: 10, + epoch_credits_range: 30, + commission_range: 30, + scoring_delinquency_threshold_ratio: 0.85, + instant_unstake_delinquency_threshold_ratio: 0.7, + mev_commission_bps_threshold: 1000, + commission_threshold: 5, + historical_commission_threshold: 50, + num_delegation_validators: 200, + scoring_unstake_cap_bps: 10, + instant_unstake_cap_bps: 10, + stake_deposit_unstake_cap_bps: 10, + instant_unstake_epoch_progress: 0.9, + compute_score_slot_range: 1000, + instant_unstake_inputs_epoch_progress: 0.5, + num_epochs_between_scoring: 10, + minimum_stake_lamports: 5_000_000_000_000, + minimum_voting_epochs: 5, + padding0: [0; 6], + }); + + // First Valid Epoch + let current_epoch = current_epoch.unwrap_or(512); + let slots_per_epoch = slots_per_epoch.unwrap_or(432_000); + + valid_parameters.get_valid_updated_parameters(update_parameters, current_epoch, slots_per_epoch) +} + +#[test] +fn test_mev_commission_range() { + let new_value = 1; + let update_parameters = UpdateParametersArgs { + mev_commission_range: Some(new_value), + ..UpdateParametersArgs::default() + }; + + { + // Out of range + let not_okay_epoch = 0; + let result = _test_parameter(&update_parameters, Some(not_okay_epoch), None, None); + assert!(result.is_err()); + } + + { + // In range + let okay_epoch = 512; + let result = _test_parameter(&update_parameters, Some(okay_epoch), None, None); + assert!(result.is_ok()); + assert_eq!(result.unwrap().mev_commission_range, new_value); + } +} + +#[test] +fn test_epoch_credits_range() { + let new_value = 1; + let update_parameters = UpdateParametersArgs { + epoch_credits_range: Some(new_value), + ..UpdateParametersArgs::default() + }; + + { + // Out of range + let not_okay_epoch = 0; + let result = _test_parameter(&update_parameters, Some(not_okay_epoch), None, None); + assert!(result.is_err()); + } + + { + // In range + let okay_epoch = 512; + let result = _test_parameter(&update_parameters, Some(okay_epoch), None, None); + assert!(result.is_ok()); + assert_eq!(result.unwrap().epoch_credits_range, new_value); + } +} + +#[test] +fn test_commission_range() { + let new_value = 1; + let update_parameters = UpdateParametersArgs { + commission_range: Some(new_value), + ..UpdateParametersArgs::default() + }; + + { + // Out of range + let not_okay_epoch = 0; + let result = _test_parameter(&update_parameters, Some(not_okay_epoch), None, None); + assert!(result.is_err()); + } + + { + // In range + let okay_epoch = 512; + let result = _test_parameter(&update_parameters, Some(okay_epoch), None, None); + assert!(result.is_ok()); + assert_eq!(result.unwrap().commission_range, new_value); + } +} + +#[test] +fn test_scoring_delinquency_threshold_ratio() { + { + // Cannot be less than 0 + let new_value = -0.1; + let update_bad_arg = UpdateParametersArgs { + scoring_delinquency_threshold_ratio: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_bad_arg, None, None, None); + assert!(result.is_err()); + } + + { + // Cannot be greater than 1 + let new_value = 1.1; + let update_bad_arg = UpdateParametersArgs { + scoring_delinquency_threshold_ratio: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_bad_arg, None, None, None); + assert!(result.is_err()); + } + + { + // 0.0 is okay + let new_value = 0.0; + let okay_arg = UpdateParametersArgs { + scoring_delinquency_threshold_ratio: Some(new_value), + ..UpdateParametersArgs::default() + }; + + let result = _test_parameter(&okay_arg, None, None, None); + assert!(result.is_ok()); + assert_eq!( + result.unwrap().scoring_delinquency_threshold_ratio, + new_value + ); + } + + { + // 1.0 is okay + let new_value = 1.0; + let okay_arg = UpdateParametersArgs { + scoring_delinquency_threshold_ratio: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&okay_arg, None, None, None); + assert!(result.is_ok()); + assert_eq!( + result.unwrap().scoring_delinquency_threshold_ratio, + new_value + ); + } + + { + // 0.5 is okay + let new_value = 0.5; + let okay_arg = UpdateParametersArgs { + scoring_delinquency_threshold_ratio: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&okay_arg, None, None, None); + assert!(result.is_ok()); + assert_eq!( + result.unwrap().scoring_delinquency_threshold_ratio, + new_value + ); + } +} + +#[test] +fn test_instant_unstake_delinquency_threshold_ratio() { + { + // Cannot be less than 0 + let new_value = -0.1; + let update_bad_arg = UpdateParametersArgs { + instant_unstake_delinquency_threshold_ratio: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_bad_arg, None, None, None); + assert!(result.is_err()); + } + + { + // Cannot be greater than 1 + let new_value = 1.1; + let update_bad_arg = UpdateParametersArgs { + instant_unstake_delinquency_threshold_ratio: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_bad_arg, None, None, None); + assert!(result.is_err()); + } + + { + // 0.0 is okay + let new_value = 0.0; + let okay_arg = UpdateParametersArgs { + instant_unstake_delinquency_threshold_ratio: Some(new_value), + ..UpdateParametersArgs::default() + }; + + let result = _test_parameter(&okay_arg, None, None, None); + assert!(result.is_ok()); + assert_eq!( + result.unwrap().instant_unstake_delinquency_threshold_ratio, + new_value + ); + } + + { + // 1.0 is okay + let new_value = 1.0; + let okay_arg = UpdateParametersArgs { + instant_unstake_delinquency_threshold_ratio: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&okay_arg, None, None, None); + assert!(result.is_ok()); + assert_eq!( + result.unwrap().instant_unstake_delinquency_threshold_ratio, + new_value + ); + } + + { + // 0.5 is okay + let new_value = 0.5; + let okay_arg = UpdateParametersArgs { + instant_unstake_delinquency_threshold_ratio: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&okay_arg, None, None, None); + assert!(result.is_ok()); + assert_eq!( + result.unwrap().instant_unstake_delinquency_threshold_ratio, + new_value + ); + } +} + +#[test] +fn test_mev_commission_bps_threshold() { + { + // Out of range + let new_value = BASIS_POINTS_MAX + 1; + let update_parameters = UpdateParametersArgs { + mev_commission_bps_threshold: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_err()); + } + + { + // In range + let new_value = 0; + let update_parameters = UpdateParametersArgs { + mev_commission_bps_threshold: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_ok()); + assert_eq!(result.unwrap().mev_commission_bps_threshold, new_value); + } +} + +#[test] +fn test_commission_threshold() { + { + // Out of range + let new_value = COMMISSION_MAX + 1; + let update_parameters = UpdateParametersArgs { + commission_threshold: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_err()); + } + + { + // In range + let new_value = 0; + let update_parameters = UpdateParametersArgs { + commission_threshold: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_ok()); + assert_eq!(result.unwrap().commission_threshold, new_value); + } +} + +#[test] +fn test_historical_commission_threshold() { + { + // Out of range + let new_value = COMMISSION_MAX + 1; + let update_parameters = UpdateParametersArgs { + historical_commission_threshold: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_err()); + } + + { + // In range + let new_value = 0; + let update_parameters = UpdateParametersArgs { + historical_commission_threshold: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_ok()); + assert_eq!(result.unwrap().historical_commission_threshold, new_value); + } +} + +#[test] +fn test_num_delegation_validators() { + { + // Cannot be 0 + let new_value = 0; + let update_parameters = UpdateParametersArgs { + num_delegation_validators: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_err()); + } + + { + // Cannot be more than MAX_VALIDATORS + let new_value = MAX_VALIDATORS + 1; + let update_parameters = UpdateParametersArgs { + num_delegation_validators: Some(new_value as u32), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_err()); + } + + { + // In range + let new_value = 1; + let update_parameters = UpdateParametersArgs { + num_delegation_validators: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_ok()); + assert_eq!(result.unwrap().num_delegation_validators, new_value); + } +} + +#[test] +fn test_scoring_unstake_cap_bps() { + { + // Out of range + let new_value = BASIS_POINTS_MAX + 1; + let update_parameters = UpdateParametersArgs { + scoring_unstake_cap_bps: Some(new_value as u32), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_err()); + } + + { + // In range + let new_value = 0; + let update_parameters = UpdateParametersArgs { + scoring_unstake_cap_bps: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_ok()); + assert_eq!(result.unwrap().scoring_unstake_cap_bps, new_value); + } +} + +#[test] +fn test_instant_unstake_cap_bps() { + { + // Out of range + let new_value = BASIS_POINTS_MAX + 1; + let update_parameters = UpdateParametersArgs { + instant_unstake_cap_bps: Some(new_value as u32), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_err()); + } + + { + // In range + let new_value = 0; + let update_parameters = UpdateParametersArgs { + instant_unstake_cap_bps: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_ok()); + assert_eq!(result.unwrap().instant_unstake_cap_bps, new_value); + } +} + +#[test] +fn test_stake_deposit_unstake_cap_bps() { + { + // Out of range + let new_value = BASIS_POINTS_MAX + 1; + let update_parameters = UpdateParametersArgs { + stake_deposit_unstake_cap_bps: Some(new_value as u32), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_err()); + } + + { + // In range + let new_value = 0; + let update_parameters = UpdateParametersArgs { + stake_deposit_unstake_cap_bps: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_ok()); + assert_eq!(result.unwrap().stake_deposit_unstake_cap_bps, new_value); + } +} + +#[test] +fn test_instant_unstake_epoch_progress() { + { + // Cannot be less than 0 + let new_value = -0.1; + let update_bad_arg = UpdateParametersArgs { + instant_unstake_epoch_progress: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_bad_arg, None, None, None); + assert!(result.is_err()); + } + + { + // Cannot be greater than 1 + let new_value = 1.1; + let update_bad_arg = UpdateParametersArgs { + instant_unstake_epoch_progress: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_bad_arg, None, None, None); + assert!(result.is_err()); + } + + { + // 0.0 is okay + let new_value = 0.0; + let okay_arg = UpdateParametersArgs { + instant_unstake_epoch_progress: Some(new_value), + ..UpdateParametersArgs::default() + }; + + let result = _test_parameter(&okay_arg, None, None, None); + assert!(result.is_ok()); + assert_eq!(result.unwrap().instant_unstake_epoch_progress, new_value); + } + + { + // EPOCH_PROGRESS_MAX is okay + let new_value = EPOCH_PROGRESS_MAX; + let okay_arg = UpdateParametersArgs { + instant_unstake_epoch_progress: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&okay_arg, None, None, None); + assert!(result.is_ok()); + assert_eq!(result.unwrap().instant_unstake_epoch_progress, new_value); + } + + { + // 0.5 is okay + let new_value = 0.5; + let okay_arg = UpdateParametersArgs { + instant_unstake_epoch_progress: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&okay_arg, None, None, None); + assert!(result.is_ok()); + assert_eq!(result.unwrap().instant_unstake_epoch_progress, new_value); + } +} + +#[test] +fn test_instant_inputs_epoch_progress() { + { + // Cannot be less than 0 + let new_value = -0.1; + let update_bad_arg = UpdateParametersArgs { + instant_unstake_inputs_epoch_progress: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_bad_arg, None, None, None); + assert!(result.is_err()); + } + + { + // Cannot be greater than 1 + let new_value = 1.1; + let update_bad_arg = UpdateParametersArgs { + instant_unstake_inputs_epoch_progress: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_bad_arg, None, None, None); + assert!(result.is_err()); + } + + { + // 0.0 is okay + let new_value = 0.0; + let okay_arg = UpdateParametersArgs { + instant_unstake_inputs_epoch_progress: Some(new_value), + ..UpdateParametersArgs::default() + }; + + let result = _test_parameter(&okay_arg, None, None, None); + assert!(result.is_ok()); + assert_eq!( + result.unwrap().instant_unstake_inputs_epoch_progress, + new_value + ); + } + + { + // EPOCH_PROGRESS_MAX is okay + let new_value = EPOCH_PROGRESS_MAX; + let okay_arg = UpdateParametersArgs { + instant_unstake_inputs_epoch_progress: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&okay_arg, None, None, None); + assert!(result.is_ok()); + assert_eq!( + result.unwrap().instant_unstake_inputs_epoch_progress, + new_value + ); + } + + { + // 0.5 is okay + let new_value = 0.5; + let okay_arg = UpdateParametersArgs { + instant_unstake_inputs_epoch_progress: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&okay_arg, None, None, None); + assert!(result.is_ok()); + assert_eq!( + result.unwrap().instant_unstake_inputs_epoch_progress, + new_value + ); + } +} + +#[test] +fn test_minimum_voting_epochs() { + let new_value = 1; + let update_parameters = UpdateParametersArgs { + minimum_voting_epochs: Some(new_value), + ..UpdateParametersArgs::default() + }; + + { + // Out of range + let not_okay_epoch = 0; + let result = _test_parameter(&update_parameters, Some(not_okay_epoch), None, None); + assert!(result.is_err()); + } + + { + // In range + let okay_epoch = 512; + let result = _test_parameter(&update_parameters, Some(okay_epoch), None, None); + assert!(result.is_ok()); + assert_eq!(result.unwrap().minimum_voting_epochs, new_value); + } +} + +#[test] +fn test_compute_score_slot_range() { + let min_slots_per_epoch = COMPUTE_SCORE_SLOT_RANGE_MIN; + let slots_per_epoch = 432_000; + + { + // Cannot be below min_slots_per_epoch + let new_value = min_slots_per_epoch - 1; + let update_parameters = UpdateParametersArgs { + compute_score_slot_range: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, Some(slots_per_epoch), None); + assert!(result.is_err()); + } + + { + // Cannot be above slots_per_epoch + let new_value = slots_per_epoch + 1; + let update_parameters = UpdateParametersArgs { + compute_score_slot_range: Some(new_value as usize), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, Some(slots_per_epoch), None); + assert!(result.is_err()); + } + + { + // In range + let new_value = COMPUTE_SCORE_SLOT_RANGE_MIN + 1; + let update_parameters = UpdateParametersArgs { + compute_score_slot_range: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, Some(slots_per_epoch), None); + assert!(result.is_ok()); + assert_eq!(result.unwrap().compute_score_slot_range, new_value); + } +} + +#[test] +fn test_num_epochs_between_scoring() { + { + // Cannot be 0 + let new_value = 0; + let update_parameters = UpdateParametersArgs { + num_epochs_between_scoring: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_err()); + } + + { + // Cannot be above + let new_value = NUM_EPOCHS_BETWEEN_SCORING_MAX + 1; + let update_parameters = UpdateParametersArgs { + num_epochs_between_scoring: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_err()); + } + + { + // In range + let new_value = 1; + let update_parameters = UpdateParametersArgs { + num_epochs_between_scoring: Some(new_value), + ..UpdateParametersArgs::default() + }; + let result = _test_parameter(&update_parameters, None, None, None); + assert!(result.is_ok()); + assert_eq!(result.unwrap().num_epochs_between_scoring, new_value); + } +} diff --git a/tests/tests/steward/test_spl_passthrough.rs b/tests/tests/steward/test_spl_passthrough.rs index ceddc3f3..8347119b 100644 --- a/tests/tests/steward/test_spl_passthrough.rs +++ b/tests/tests/steward/test_spl_passthrough.rs @@ -491,7 +491,7 @@ async fn test_add_validator_to_pool() { // Set up the test fixture let fixture = TestFixture::new().await; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture.initialize_config(None).await; fixture.initialize_steward_state().await; { @@ -514,7 +514,7 @@ async fn test_remove_validator_from_pool() { // Set up the test fixture let fixture = TestFixture::new().await; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture.initialize_config(None).await; fixture.initialize_steward_state().await; // Setup the steward state @@ -609,7 +609,7 @@ async fn test_set_preferred_validator() { // Set up the test fixture let fixture = TestFixture::new().await; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture.initialize_config(None).await; fixture.initialize_steward_state().await; // Assert the validator was added to the validator list @@ -667,7 +667,7 @@ async fn test_increase_validator_stake() { let fixture = TestFixture::new().await; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture.initialize_config(None).await; fixture.initialize_steward_state().await; // Assert the validator was added to the validator list @@ -689,7 +689,7 @@ async fn test_decrease_validator_stake() { let fixture = TestFixture::new().await; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture.initialize_config(None).await; fixture.initialize_steward_state().await; _add_test_validator(&fixture, Pubkey::new_unique()).await; @@ -769,7 +769,7 @@ async fn test_increase_additional_validator_stake() { let fixture = TestFixture::new().await; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture.initialize_config(None).await; fixture.initialize_steward_state().await; // Assert the validator was added to the validator list @@ -792,7 +792,7 @@ async fn test_decrease_additional_validator_stake() { let fixture = TestFixture::new().await; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture.initialize_config(None).await; fixture.initialize_steward_state().await; _add_test_validator(&fixture, Pubkey::new_unique()).await; @@ -878,7 +878,7 @@ async fn test_set_staker() { // Set up the test fixture let fixture = TestFixture::new().await; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture.initialize_config(None).await; fixture.initialize_steward_state().await; let new_staker = Keypair::new(); diff --git a/tests/tests/steward/test_steward.rs b/tests/tests/steward/test_steward.rs index 5729a7b7..5e7673d1 100644 --- a/tests/tests/steward/test_steward.rs +++ b/tests/tests/steward/test_steward.rs @@ -1,28 +1,21 @@ /// Basic integration test use anchor_lang::{ solana_program::{instruction::Instruction, pubkey::Pubkey, stake, sysvar}, - AnchorDeserialize, InstructionData, ToAccountMetas, + InstructionData, ToAccountMetas, }; -use jito_steward::{constants::STAKE_POOL_WITHDRAW_SEED, utils::ValidatorList, Config}; +use jito_steward::{utils::ValidatorList, Config, StewardStateAccount}; use solana_program_test::*; use solana_sdk::{signature::Keypair, signer::Signer, transaction::Transaction}; -use spl_stake_pool::find_stake_program_address; use tests::steward_fixtures::{ - new_vote_account, serialized_validator_history_account, system_account, - validator_history_default, TestFixture, + closed_vote_account, new_vote_account, serialized_steward_state_account, + serialized_validator_history_account, system_account, validator_history_default, TestFixture, }; use validator_history::{ValidatorHistory, ValidatorHistoryEntry}; -#[tokio::test] -async fn test_auto_add_validator_to_pool() { - let fixture = TestFixture::new().await; +async fn _auto_add_validator_to_pool(fixture: &TestFixture, vote_account: &Pubkey) { let ctx = &fixture.ctx; - fixture.initialize_stake_pool().await; - fixture.initialize_config().await; - fixture.initialize_steward_state().await; - + let vote_account = *vote_account; let epoch_credits = vec![(0, 1, 0), (1, 2, 1), (2, 3, 2), (3, 4, 3), (4, 5, 4)]; - let vote_account = Pubkey::new_unique(); let validator_history_account = Pubkey::find_program_address( &[ValidatorHistory::SEED, vote_account.as_ref()], &validator_history::id(), @@ -37,29 +30,13 @@ async fn test_auto_add_validator_to_pool() { &serialized_validator_history_account(validator_history_default(vote_account, 0)).into(), ); - let stake_pool_account = fixture - .get_account(&fixture.stake_pool_meta.stake_pool) - .await; - - let stake_pool = - spl_stake_pool::state::StakePool::deserialize(&mut stake_pool_account.data.as_slice()) - .unwrap(); - - let (pool_stake_account, _) = find_stake_program_address( - &spl_stake_pool::id(), - &vote_account, - &fixture.stake_pool_meta.stake_pool, - None, + let (validator_history_account, _) = Pubkey::find_program_address( + &[ValidatorHistory::SEED, vote_account.as_ref()], + &validator_history::id(), ); - let withdraw_authority = Pubkey::create_program_address( - &[ - &fixture.stake_pool_meta.stake_pool.as_ref(), - STAKE_POOL_WITHDRAW_SEED, - &[stake_pool.stake_withdraw_bump_seed], - ], - &spl_stake_pool::id(), - ) - .unwrap(); + + let (stake_account_address, _, withdraw_authority) = + fixture.stake_accounts_for_validator(vote_account).await; let add_validator_to_pool_ix = Instruction { program_id: jito_steward::id(), @@ -72,7 +49,7 @@ async fn test_auto_add_validator_to_pool() { reserve_stake: fixture.stake_pool_meta.reserve, withdraw_authority, validator_list: fixture.stake_pool_meta.validator_list, - stake_account: pool_stake_account, + stake_account: stake_account_address, vote_account, rent: sysvar::rent::id(), clock: sysvar::clock::id(), @@ -92,14 +69,16 @@ async fn test_auto_add_validator_to_pool() { ctx.borrow().last_blockhash, ); fixture - .submit_transaction_assert_error(tx.clone(), "ValidatorBelowLivenessMinimum.") + .submit_transaction_assert_error(tx.clone(), "ValidatorBelowLivenessMinimum") .await; + // fixture. let mut validator_history = validator_history_default(vote_account, 0); for i in 0..20 { validator_history.history.push(ValidatorHistoryEntry { epoch: i, epoch_credits: 400000, + vote_account_last_update_slot: 100, ..ValidatorHistoryEntry::default() }); } @@ -121,6 +100,103 @@ async fn test_auto_add_validator_to_pool() { assert!( validator_list.validators[validator_stake_info_idx].vote_account_address == vote_account ); +} + +#[tokio::test] +async fn test_auto_add_validator_to_pool() { + let fixture = TestFixture::new().await; + + fixture.initialize_stake_pool().await; + fixture.initialize_config(None).await; + fixture.initialize_steward_state().await; + + _auto_add_validator_to_pool(&fixture, &Pubkey::new_unique()).await; + + drop(fixture); +} + +#[tokio::test] +async fn test_auto_remove() { + let fixture = TestFixture::new().await; + + fixture.initialize_stake_pool().await; + fixture.initialize_config(None).await; + fixture.initialize_steward_state().await; + + let vote_account = Pubkey::new_unique(); + + let (validator_history_account, _) = Pubkey::find_program_address( + &[ValidatorHistory::SEED, vote_account.as_ref()], + &validator_history::id(), + ); + + let (stake_account_address, transient_stake_account_address, withdraw_authority) = + fixture.stake_accounts_for_validator(vote_account).await; + + // Add vote account + _auto_add_validator_to_pool(&fixture, &vote_account).await; + + let auto_remove_validator_ix = Instruction { + program_id: jito_steward::id(), + accounts: jito_steward::accounts::AutoRemoveValidator { + validator_history_account, + config: fixture.steward_config.pubkey(), + state_account: fixture.steward_state, + stake_pool_program: spl_stake_pool::id(), + stake_pool: fixture.stake_pool_meta.stake_pool, + staker: fixture.staker, + reserve_stake: fixture.stake_pool_meta.reserve, + withdraw_authority, + validator_list: fixture.stake_pool_meta.validator_list, + stake_account: stake_account_address, + transient_stake_account: transient_stake_account_address, + vote_account, + rent: sysvar::rent::id(), + clock: sysvar::clock::id(), + stake_history: sysvar::stake_history::id(), + stake_config: stake::config::ID, + stake_program: stake::program::id(), + system_program: solana_program::system_program::id(), + signer: fixture.keypair.pubkey(), + } + .to_account_metas(None), + data: jito_steward::instruction::AutoRemoveValidatorFromPool { + validator_list_index: 0, + } + .data(), + }; + + let mut steward_state_account: StewardStateAccount = + fixture.load_and_deserialize(&fixture.steward_state).await; + + // Fake add vote account to state + steward_state_account.state.num_pool_validators = 1; + steward_state_account.state.sorted_score_indices[0] = 0; + steward_state_account.state.sorted_yield_score_indices[0] = 0; + + fixture.ctx.borrow_mut().set_account( + &fixture.steward_state, + &serialized_steward_state_account(steward_state_account).into(), + ); + + let tx = Transaction::new_signed_with_payer( + &[auto_remove_validator_ix], + Some(&fixture.keypair.pubkey()), + &[&fixture.keypair], + fixture.ctx.borrow().last_blockhash, + ); + + fixture + .submit_transaction_assert_error(tx.clone(), "ValidatorNotRemovable") + .await; + + // "Close" vote account + fixture + .ctx + .borrow_mut() + .set_account(&vote_account, &closed_vote_account().into()); + + fixture.submit_transaction_assert_success(tx).await; drop(fixture); } @@ -130,7 +206,7 @@ async fn test_pause() { let fixture = TestFixture::new().await; let ctx = &fixture.ctx; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture.initialize_config(None).await; let ix = Instruction { program_id: jito_steward::id(), @@ -189,7 +265,7 @@ async fn test_blacklist() { let fixture = TestFixture::new().await; let ctx = &fixture.ctx; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture.initialize_config(None).await; let ix = Instruction { program_id: jito_steward::id(), @@ -246,7 +322,7 @@ async fn test_set_new_authority() { let fixture = TestFixture::new().await; let ctx = &fixture.ctx; fixture.initialize_stake_pool().await; - fixture.initialize_config().await; + fixture.initialize_config(None).await; // Regular test let new_authority = Keypair::new();