diff --git a/controller/core/src/instructions/mod.rs b/controller/core/src/instructions/mod.rs index dff3bc4..ae9370a 100644 --- a/controller/core/src/instructions/mod.rs +++ b/controller/core/src/instructions/mod.rs @@ -6,5 +6,6 @@ pub mod generic; pub mod liquidity; pub mod protocol_fee; pub mod rebalance; +pub mod rps; pub mod swap; pub mod sync_sol_value; diff --git a/controller/core/src/instructions/rps/mod.rs b/controller/core/src/instructions/rps/mod.rs new file mode 100644 index 0000000..262a3d4 --- /dev/null +++ b/controller/core/src/instructions/rps/mod.rs @@ -0,0 +1 @@ +pub mod set_rps; diff --git a/controller/core/src/instructions/rps/set_rps.rs b/controller/core/src/instructions/rps/set_rps.rs new file mode 100644 index 0000000..25324a3 --- /dev/null +++ b/controller/core/src/instructions/rps/set_rps.rs @@ -0,0 +1,70 @@ +use generic_array_struct::generic_array_struct; + +use crate::instructions::internal_utils::caba; + +// Accounts + +#[generic_array_struct(builder pub)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct SetRpsIxAccs { + /// The pool's state singleton PDA + pub pool_state: T, + + /// The pool's RPS authority + pub rps_auth: T, +} + +impl SetRpsIxAccs { + #[inline] + pub const fn memset(val: T) -> Self { + Self([val; SET_RPS_IX_ACCS_LEN]) + } +} + +pub type SetRpsIxKeys<'a> = SetRpsIxAccs<&'a [u8; 32]>; + +pub type SetRpsIxKeysOwned = SetRpsIxAccs<[u8; 32]>; + +pub type SetRpsIxAccFlags = SetRpsIxAccs; + +pub const SET_RPS_IX_IS_WRITER: SetRpsIxAccFlags = + SetRpsIxAccFlags::memset(false).const_with_pool_state(true); + +pub const SET_RPS_IX_IS_SIGNER: SetRpsIxAccFlags = + SetRpsIxAccFlags::memset(false).const_with_rps_auth(true); + +// Data + +pub const SET_RPS_IX_DISCM: u8 = 26; + +pub const SET_RPS_IX_DATA_LEN: usize = 9; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct SetRpsIxData([u8; SET_RPS_IX_DATA_LEN]); + +impl SetRpsIxData { + #[inline] + pub const fn new(rps: u64) -> Self { + const A: usize = SET_RPS_IX_DATA_LEN; + + let mut d = [0u8; A]; + + d = caba::(d, &[SET_RPS_IX_DISCM]); + d = caba::(d, &rps.to_le_bytes()); + + Self(d) + } + + #[inline] + pub const fn as_buf(&self) -> &[u8; SET_RPS_IX_DATA_LEN] { + &self.0 + } + + /// Returns `rps` arg, the new RPS to set to + #[inline] + pub const fn parse_no_discm(data: &[u8; 8]) -> u64 { + u64::from_le_bytes(*data) + } +} diff --git a/controller/program/src/instructions/mod.rs b/controller/program/src/instructions/mod.rs index 945c6ed..8216975 100644 --- a/controller/program/src/instructions/mod.rs +++ b/controller/program/src/instructions/mod.rs @@ -2,5 +2,6 @@ pub mod admin; pub mod disable_pool; pub mod protocol_fee; pub mod rebalance; +pub mod rps; pub mod swap; pub mod sync_sol_value; diff --git a/controller/program/src/instructions/protocol_fee/withdraw_protocol_fees/v2.rs b/controller/program/src/instructions/protocol_fee/withdraw_protocol_fees/v2.rs index 5e92669..12caec4 100644 --- a/controller/program/src/instructions/protocol_fee/withdraw_protocol_fees/v2.rs +++ b/controller/program/src/instructions/protocol_fee/withdraw_protocol_fees/v2.rs @@ -13,7 +13,7 @@ use inf1_ctl_jiminy::{ }; use jiminy_cpi::{ account::{Abr, AccountHandle}, - program_error::{ProgramError, NOT_ENOUGH_ACCOUNT_KEYS}, + program_error::ProgramError, Cpi, }; use jiminy_sysvar_clock::Clock; @@ -24,6 +24,7 @@ use sanctum_spl_token_jiminy::{ use crate::{ token::checked_mint_of, + utils::accs_split_first_chunk, verify::{verify_not_rebalancing_and_not_disabled, verify_pks, verify_signers}, }; @@ -34,8 +35,8 @@ pub fn withdraw_protocol_fees_v2_checked<'acc>( abr: &Abr, accs: &[AccountHandle<'acc>], ) -> Result, ProgramError> { - let accs = accs.first_chunk().ok_or(NOT_ENOUGH_ACCOUNT_KEYS)?; - let accs = WithdrawProtocolFeesV2IxAccs(*accs); + let (ix_prefix, _) = accs_split_first_chunk(accs)?; + let accs = WithdrawProtocolFeesV2IxAccs(*ix_prefix); let pool = pool_state_v2_checked(abr.get(*accs.pool_state()))?; let mint_acc = abr.get(*accs.inf_mint()); @@ -62,7 +63,7 @@ pub fn withdraw_protocol_fees_v2_checked<'acc>( pub fn process_withdraw_protocol_fees_v2( abr: &mut Abr, cpi: &mut Cpi, - accs: WithdrawProtocolFeesV2IxAccounts, + accs: &WithdrawProtocolFeesV2IxAccounts, clock: &Clock, ) -> Result<(), ProgramError> { let pool = pool_state_v2_checked_mut(abr.get_mut(*accs.pool_state()))?; diff --git a/controller/program/src/instructions/rps/mod.rs b/controller/program/src/instructions/rps/mod.rs new file mode 100644 index 0000000..262a3d4 --- /dev/null +++ b/controller/program/src/instructions/rps/mod.rs @@ -0,0 +1 @@ +pub mod set_rps; diff --git a/controller/program/src/instructions/rps/set_rps.rs b/controller/program/src/instructions/rps/set_rps.rs new file mode 100644 index 0000000..4242eab --- /dev/null +++ b/controller/program/src/instructions/rps/set_rps.rs @@ -0,0 +1,67 @@ +use inf1_ctl_jiminy::{ + account_utils::{pool_state_v2_checked, pool_state_v2_checked_mut}, + instructions::rps::set_rps::{ + NewSetRpsIxAccsBuilder, SetRpsIxAccs, SetRpsIxData, SET_RPS_IX_IS_SIGNER, + }, + keys::POOL_STATE_ID, + program_err::Inf1CtlCustomProgErr, + typedefs::{rps::Rps, uq0f63::UQ0F63}, +}; +use jiminy_cpi::{ + account::{Abr, AccountHandle}, + program_error::{ProgramError, INVALID_INSTRUCTION_DATA}, +}; +use jiminy_sysvar_clock::Clock; + +use crate::{ + utils::{accs_split_first_chunk, ix_data_as_arr}, + verify::{verify_not_rebalancing_and_not_disabled, verify_pks, verify_signers}, +}; + +type SetRpsIxAccounts<'acc> = SetRpsIxAccs>; + +#[inline] +pub fn set_rps_checked<'acc>( + abr: &mut Abr, + accs: &[AccountHandle<'acc>], + ix_data_no_discm: &[u8], +) -> Result<(SetRpsIxAccounts<'acc>, Rps), ProgramError> { + let (ix_prefix, _) = accs_split_first_chunk(accs)?; + let accs = SetRpsIxAccs(*ix_prefix); + + let new_rps_raw = SetRpsIxData::parse_no_discm(ix_data_as_arr(ix_data_no_discm)?); + + // Validate raw RPS value + let uq_rps = UQ0F63::new(new_rps_raw).map_err(|_| INVALID_INSTRUCTION_DATA)?; + let new_rps = Rps::new(uq_rps).map_err(|_| INVALID_INSTRUCTION_DATA)?; + + let pool = pool_state_v2_checked(abr.get(*accs.pool_state()))?; + + let expected_pks = NewSetRpsIxAccsBuilder::start() + .with_pool_state(&POOL_STATE_ID) + .with_rps_auth(&pool.rps_authority) + .build(); + verify_pks(abr, &accs.0, &expected_pks.0)?; + + verify_signers(abr, &accs.0, &SET_RPS_IX_IS_SIGNER.0)?; + + verify_not_rebalancing_and_not_disabled(pool)?; + + Ok((accs, new_rps)) +} + +#[inline] +pub fn process_set_rps( + abr: &mut Abr, + accs: &SetRpsIxAccounts, + new_rps: Rps, + clock: &Clock, +) -> Result<(), ProgramError> { + let pool = pool_state_v2_checked_mut(abr.get_mut(*accs.pool_state()))?; + pool.release_yield(clock.slot) + .map_err(Inf1CtlCustomProgErr)?; + + pool.rps = *new_rps.as_raw(); + + Ok(()) +} diff --git a/controller/program/src/lib.rs b/controller/program/src/lib.rs index 3432f4a..ae9f5c5 100644 --- a/controller/program/src/lib.rs +++ b/controller/program/src/lib.rs @@ -30,6 +30,7 @@ use inf1_ctl_jiminy::instructions::{ set_rebal_auth::SET_REBAL_AUTH_IX_DISCM, start::{StartRebalanceIxData, START_REBALANCE_IX_DISCM}, }, + rps::set_rps::SET_RPS_IX_DISCM, swap::{ parse_swap_ix_args, v1::{exact_in::SWAP_EXACT_IN_IX_DISCM, exact_out::SWAP_EXACT_OUT_IX_DISCM}, @@ -88,6 +89,7 @@ use crate::{ set_rebal_auth::{process_set_rebal_auth, set_rebal_auth_accs_checked}, start::process_start_rebalance, }, + rps::set_rps::{process_set_rps, set_rps_checked}, swap::{ v1::{ add_liq_split_v1_accs_into_v2, conv_add_liq_args, conv_rem_liq_args, @@ -281,13 +283,6 @@ fn process_ix( let accs = set_rebal_auth_accs_checked(abr, accounts)?; process_set_rebal_auth(abr, accs) } - // v2 withdraw protocol fees - (&WITHDRAW_PROTOCOL_FEES_V2_IX_DISCM, _) => { - sol_log("WithdrawProtocolFeesV2"); - let accs = withdraw_protocol_fees_v2_checked(abr, accounts)?; - let clock = Clock::write_to(&mut clock)?; - process_withdraw_protocol_fees_v2(abr, cpi, accs, clock) - } // v2 swap (&SWAP_EXACT_IN_V2_IX_DISCM, data) => { sol_log("SwapExactInV2"); @@ -305,6 +300,20 @@ fn process_ix( verify_swap_v2(abr, &accs, &args, clock)?; process_swap_exact_out_v2(abr, cpi, &accs, &args, clock) } + // v2 withdraw protocol fees + (&WITHDRAW_PROTOCOL_FEES_V2_IX_DISCM, _) => { + sol_log("WithdrawProtocolFeesV2"); + let accs = withdraw_protocol_fees_v2_checked(abr, accounts)?; + let clock = Clock::write_to(&mut clock)?; + process_withdraw_protocol_fees_v2(abr, cpi, &accs, clock) + } + // v2 set rps + (&SET_RPS_IX_DISCM, data) => { + sol_log("SetRps"); + let (accs, rps) = set_rps_checked(abr, accounts, data)?; + let clock = Clock::write_to(&mut clock)?; + process_set_rps(abr, &accs, rps, clock) + } _ => Err(INVALID_INSTRUCTION_DATA.into()), } } diff --git a/controller/program/tests/common/pool_state.rs b/controller/program/tests/common/pool_state.rs index 5fb5aca..256d87e 100644 --- a/controller/program/tests/common/pool_state.rs +++ b/controller/program/tests/common/pool_state.rs @@ -3,6 +3,7 @@ use std::borrow::Borrow; use inf1_ctl_jiminy::{ accounts::pool_state::PoolStateV2, sync_sol_val::SyncSolVal, typedefs::snap::NewSnapBuilder, }; +use inf1_svc_ag_core::calc::SvcCalcAg; use inf1_svc_jiminy::traits::SolValCalc; /// Calc, balance, sol value @@ -44,3 +45,8 @@ where pub fn assert_lp_solvent_invar(ps: &PoolStateV2) { assert!(ps.total_sol_value >= ps.withheld_lamports + ps.protocol_fee_lamports); } + +/// Lookahead to after release_yield with no LST updates +pub fn header_lookahead_no_lsts(ps: PoolStateV2, curr_slot: u64) -> PoolStateV2 { + header_lookahead(ps, &[] as &[Cbs], curr_slot) +} diff --git a/controller/program/tests/tests/mod.rs b/controller/program/tests/tests/mod.rs index a9adadb..ae5afd8 100644 --- a/controller/program/tests/tests/mod.rs +++ b/controller/program/tests/tests/mod.rs @@ -2,6 +2,6 @@ mod admin; mod disable_pool; mod protocol_fee; mod rebalance; -mod sync_sol_value; - +mod rps; mod swap; +mod sync_sol_value; diff --git a/controller/program/tests/tests/protocol_fee/withdraw_protocol_fees/v2.rs b/controller/program/tests/tests/protocol_fee/withdraw_protocol_fees/v2.rs index d5212ee..65351ff 100644 --- a/controller/program/tests/tests/protocol_fee/withdraw_protocol_fees/v2.rs +++ b/controller/program/tests/tests/protocol_fee/withdraw_protocol_fees/v2.rs @@ -16,7 +16,7 @@ use inf1_ctl_jiminy::{ svc::InfCalc, typedefs::pool_sv::PoolSvLamports, }; -use inf1_svc_ag_core::{calc::SvcCalcAg, inf1_svc_lido_core::solido_legacy_core::TOKENKEG_PROGRAM}; +use inf1_svc_ag_core::inf1_svc_lido_core::solido_legacy_core::TOKENKEG_PROGRAM; use inf1_test_utils::{ acc_bef_aft, any_normal_pk, any_pool_state_v2, assert_diffs_pool_state_v2, assert_jiminy_prog_err, assert_token_acc_diffs, keys_signer_writable_to_metas, @@ -35,7 +35,7 @@ use sanctum_u64_ratio::Ratio; use solana_instruction::Instruction; use solana_pubkey::Pubkey; -use crate::common::{header_lookahead, Cbs, SVM}; +use crate::common::{header_lookahead_no_lsts, SVM}; use jiminy_cpi::program_error::{ProgramError, INVALID_ARGUMENT, MISSING_REQUIRED_SIGNATURE}; const INF_MINT_ID: [u8; 32] = INF_MINT.to_bytes(); @@ -44,11 +44,6 @@ const INF_MINT_ID: [u8; 32] = INF_MINT.to_bytes(); /// when protocol_fee_lamports * inf_mint_supply const SAFE_MUL_U64_MAX: u64 = u32::MAX as u64; -/// Lookahead to after release_yield with no LST updates -fn pool_state_header_lookahead(ps: PoolStateV2, curr_slot: u64) -> PoolStateV2 { - header_lookahead(ps, &[] as &[Cbs], curr_slot) -} - fn withdraw_protocol_fees_v2_ix(keys: &WithdrawProtocolFeesV2IxKeysOwned) -> Instruction { let accounts = keys_signer_writable_to_metas( keys.0.iter(), @@ -120,8 +115,7 @@ fn withdraw_protocol_fees_v2_test( }) }; - let pool_state_bef = - pool_state_header_lookahead(pool_state_bef, svm.sysvars.clock.slot); + let pool_state_bef = header_lookahead_no_lsts(pool_state_bef, svm.sysvars.clock.slot); let [withdraw_to_bef, withdraw_to_aft] = { acc_bef_aft(&withdraw_to_pk, bef, &aft) diff --git a/controller/program/tests/tests/rps/mod.rs b/controller/program/tests/tests/rps/mod.rs new file mode 100644 index 0000000..262a3d4 --- /dev/null +++ b/controller/program/tests/tests/rps/mod.rs @@ -0,0 +1 @@ +pub mod set_rps; diff --git a/controller/program/tests/tests/rps/set_rps.rs b/controller/program/tests/tests/rps/set_rps.rs new file mode 100644 index 0000000..caa7a0c --- /dev/null +++ b/controller/program/tests/tests/rps/set_rps.rs @@ -0,0 +1,333 @@ +use inf1_ctl_jiminy::{ + accounts::pool_state::{ + PoolStateV2, PoolStateV2Addrs, PoolStateV2FtaVals, PoolStateV2Packed, PoolStateV2U64s, + }, + err::Inf1CtlErr, + instructions::rps::set_rps::{ + NewSetRpsIxAccsBuilder, SetRpsIxData, SetRpsIxKeysOwned, SET_RPS_IX_ACCS_IDX_POOL_STATE, + SET_RPS_IX_ACCS_IDX_RPS_AUTH, SET_RPS_IX_IS_SIGNER, SET_RPS_IX_IS_WRITER, + }, + keys::POOL_STATE_ID, + program_err::Inf1CtlCustomProgErr, + typedefs::{ + rps::{Rps, MIN_RPS_RAW}, + uq0f63::UQ0F63, + }, + ID, +}; +use inf1_test_utils::{ + acc_bef_aft, any_normal_pk, any_pool_state_v2, any_rps_strat, assert_diffs_pool_state_v2, + assert_jiminy_prog_err, keys_signer_writable_to_metas, mock_sys_acc, mollusk_exec, + pool_state_v2_account, pool_state_v2_u8_bools_normal_strat, silence_mollusk_logs, AccountMap, + Diff, DiffsPoolStateV2, PoolStateV2FtaStrat, +}; +use jiminy_cpi::program_error::{ + ProgramError, INVALID_ARGUMENT, INVALID_INSTRUCTION_DATA, MISSING_REQUIRED_SIGNATURE, +}; +use proptest::prelude::*; +use solana_instruction::Instruction; +use solana_pubkey::Pubkey; + +use mollusk_svm::Mollusk; + +use crate::common::{header_lookahead_no_lsts, SVM}; + +fn set_rps_ix(keys: SetRpsIxKeysOwned, rps: u64) -> Instruction { + let accounts = keys_signer_writable_to_metas( + keys.0.iter(), + SET_RPS_IX_IS_SIGNER.0.iter(), + SET_RPS_IX_IS_WRITER.0.iter(), + ); + Instruction { + program_id: Pubkey::new_from_array(ID), + accounts, + data: SetRpsIxData::new(rps).as_buf().into(), + } +} + +fn set_rps_ix_test_accs(keys: SetRpsIxKeysOwned, pool: PoolStateV2) -> AccountMap { + const LAMPORTS: u64 = 1_000_000_000; + + let accs = NewSetRpsIxAccsBuilder::start() + .with_pool_state(pool_state_v2_account(pool)) + .with_rps_auth(mock_sys_acc(LAMPORTS)) + .build(); + keys.0.into_iter().map(Into::into).zip(accs.0).collect() +} + +fn set_rps_test( + svm: &Mollusk, + ix: Instruction, + bef: &AccountMap, + new_rps: u64, + expected_err: Option>, +) { + let pool_pk = ix.accounts[SET_RPS_IX_ACCS_IDX_POOL_STATE].pubkey; + let result = mollusk_exec(svm, &[ix], bef); + + match expected_err { + None => { + let aft: AccountMap = result.unwrap().resulting_accounts; + + let [pool_state_bef, pool_state_aft] = { + acc_bef_aft(&pool_pk, bef, &aft).map(|acc| { + PoolStateV2Packed::of_acc_data(&acc.data) + .unwrap() + .into_pool_state_v2() + }) + }; + + let pool_state_bef_lookahead = + header_lookahead_no_lsts(pool_state_bef, svm.sysvars.clock.slot); + + assert_eq!(pool_state_aft.rps, new_rps); + + assert_diffs_pool_state_v2( + &DiffsPoolStateV2 { + u64s: PoolStateV2U64s::default() + .with_withheld_lamports(Diff::Changed( + pool_state_bef.withheld_lamports, + pool_state_bef_lookahead.withheld_lamports, + )) + .with_protocol_fee_lamports(Diff::Changed( + pool_state_bef.protocol_fee_lamports, + pool_state_bef_lookahead.protocol_fee_lamports, + )) + .with_last_release_slot(Diff::Changed( + pool_state_bef.last_release_slot, + pool_state_bef_lookahead.last_release_slot, + )), + rps: Diff::Changed( + Rps::new(UQ0F63::new(pool_state_bef.rps).unwrap()).unwrap(), + Rps::new(UQ0F63::new(new_rps).unwrap()).unwrap(), + ), + ..Default::default() + }, + &pool_state_bef, + &pool_state_aft, + ); + } + Some(e) => { + assert_jiminy_prog_err(&result.unwrap_err(), e); + } + } +} + +#[test] +fn set_rps_correct_basic() { + const NEW_RPS_RAW: u64 = *Rps::DEFAULT.as_inner().as_raw() + 1; + + // 69 + to avoid colliding with system prog + let [rps_auth] = core::array::from_fn(|i| [69 + u8::try_from(i).unwrap(); 32]); + + let pool = PoolStateV2FtaVals { + addrs: PoolStateV2Addrs::default().with_rps_authority(rps_auth), + rps: Rps::DEFAULT, + ..Default::default() + } + .into_pool_state_v2(); + + let keys = NewSetRpsIxAccsBuilder::start() + .with_pool_state(POOL_STATE_ID) + .with_rps_auth(rps_auth) + .build(); + + SVM.with(|svm| { + set_rps_test( + svm, + set_rps_ix(keys, NEW_RPS_RAW), + &set_rps_ix_test_accs(keys, pool), + NEW_RPS_RAW, + Option::::None, + ); + }); +} + +fn args_ps_with_correct_keys( + (new_rps_raw, ps): (u64, PoolStateV2), +) -> (SetRpsIxKeysOwned, u64, PoolStateV2) { + ( + NewSetRpsIxAccsBuilder::start() + .with_pool_state(POOL_STATE_ID) + .with_rps_auth(ps.rps_authority) + .build(), + new_rps_raw, + ps, + ) +} + +fn to_inp( + (keys, new_rps_raw, ps): (SetRpsIxKeysOwned, u64, PoolStateV2), +) -> (Instruction, AccountMap, u64) { + ( + set_rps_ix(keys, new_rps_raw), + set_rps_ix_test_accs(keys, ps), + new_rps_raw, + ) +} + +fn correct_strat() -> impl Strategy { + ( + any_rps_strat().prop_map(|r| *r.as_raw()), + any_pool_state_v2(PoolStateV2FtaStrat { + u8_bools: pool_state_v2_u8_bools_normal_strat(), + u64s: PoolStateV2U64s::default().with_last_release_slot(Some(Just(0).boxed())), + ..Default::default() + }), + ) + .prop_map(args_ps_with_correct_keys) + .prop_map(to_inp) +} + +proptest! { + #[test] + fn set_rps_correct_pt( + (ix, bef, new_rps) in correct_strat(), + ) { + silence_mollusk_logs(); + SVM.with(|svm| { + set_rps_test(svm, ix, &bef, new_rps, Option::::None); + }); + } +} + +fn invalid_rps_strat() -> impl Strategy { + ( + prop_oneof![ + 0..MIN_RPS_RAW, // below min + (*UQ0F63::ONE.as_raw() + 1)..=u64::MAX, // above max + ], + any_pool_state_v2(PoolStateV2FtaStrat { + u8_bools: pool_state_v2_u8_bools_normal_strat(), + u64s: PoolStateV2U64s::default().with_last_release_slot(Some(Just(0).boxed())), + ..Default::default() + }), + ) + .prop_map(args_ps_with_correct_keys) + .prop_map(to_inp) +} + +proptest! { + #[test] + fn set_rps_invalid_rps_pt( + (ix, bef, new_rps) in invalid_rps_strat(), + ) { + silence_mollusk_logs(); + SVM.with(|svm| { + set_rps_test(svm, ix, &bef, new_rps, Some(INVALID_INSTRUCTION_DATA)); + }); + } +} + +fn unauthorized_strat() -> impl Strategy { + ( + any_rps_strat().prop_map(|r| *r.as_raw()), + any_pool_state_v2(PoolStateV2FtaStrat { + u8_bools: pool_state_v2_u8_bools_normal_strat(), + u64s: PoolStateV2U64s::default().with_last_release_slot(Some(Just(0).boxed())), + ..Default::default() + }), + ) + .prop_flat_map(|(new_rps_raw, ps)| { + ( + any_normal_pk() + .prop_filter("wrong rps authority", move |pk| *pk != ps.rps_authority), + Just(new_rps_raw), + Just(ps), + ) + }) + .prop_map(|(wrong_rps_auth, new_rps_raw, ps)| { + ( + NewSetRpsIxAccsBuilder::start() + .with_pool_state(POOL_STATE_ID) + .with_rps_auth(wrong_rps_auth) + .build(), + new_rps_raw, + ps, + ) + }) + .prop_map(to_inp) +} + +proptest! { + #[test] + fn set_rps_unauthorized_pt( + (ix, bef, new_rps) in unauthorized_strat(), + ) { + silence_mollusk_logs(); + SVM.with(|svm| { + set_rps_test(svm, ix, &bef, new_rps, Some(INVALID_ARGUMENT)); + }); + } +} + +fn missing_sig_strat() -> impl Strategy { + correct_strat().prop_map(|(mut ix, bef, new_rps)| { + ix.accounts[SET_RPS_IX_ACCS_IDX_RPS_AUTH].is_signer = false; + (ix, bef, new_rps) + }) +} + +proptest! { + #[test] + fn set_rps_missing_sig_pt( + (ix, bef, new_rps) in missing_sig_strat(), + ) { + silence_mollusk_logs(); + SVM.with(|svm| { + set_rps_test(svm, ix, &bef, new_rps, Some(MISSING_REQUIRED_SIGNATURE)); + }); + } +} + +fn disabled_strat() -> impl Strategy { + ( + any_rps_strat().prop_map(|r| *r.as_raw()), + any_pool_state_v2(PoolStateV2FtaStrat { + u8_bools: pool_state_v2_u8_bools_normal_strat() + .with_is_disabled(Some(Just(true).boxed())), + u64s: PoolStateV2U64s::default().with_last_release_slot(Some(Just(0).boxed())), + ..Default::default() + }), + ) + .prop_map(args_ps_with_correct_keys) + .prop_map(to_inp) +} + +proptest! { + #[test] + fn set_rps_disabled_pt( + (ix, bef, new_rps) in disabled_strat(), + ) { + silence_mollusk_logs(); + SVM.with(|svm| { + set_rps_test(svm, ix, &bef, new_rps, Some(Inf1CtlCustomProgErr(Inf1CtlErr::PoolDisabled))); + }); + } +} + +fn rebalancing_strat() -> impl Strategy { + ( + any_rps_strat().prop_map(|r| *r.as_raw()), + any_pool_state_v2(PoolStateV2FtaStrat { + u8_bools: pool_state_v2_u8_bools_normal_strat() + .with_is_rebalancing(Some(Just(true).boxed())), + u64s: PoolStateV2U64s::default().with_last_release_slot(Some(Just(0).boxed())), + ..Default::default() + }), + ) + .prop_map(args_ps_with_correct_keys) + .prop_map(to_inp) +} + +proptest! { + #[test] + fn set_rps_rebalancing_pt( + (ix, bef, new_rps) in rebalancing_strat(), + ) { + silence_mollusk_logs(); + SVM.with(|svm| { + set_rps_test(svm, ix, &bef, new_rps, Some(Inf1CtlCustomProgErr(Inf1CtlErr::PoolRebalancing))); + }); + } +} diff --git a/docs/v2/README.md b/docs/v2/README.md index c3eafcd..d012382 100644 --- a/docs/v2/README.md +++ b/docs/v2/README.md @@ -76,6 +76,7 @@ For the following instructions that are affected by yield release events and hav - SwapExactInV2 (new) - SwapExactOutV2 (new) - WithdrawProtocolFeesV2 (new) +- SetRps (new) Immediately after verification, before running anything else, the instruction will run a `release_yield` subroutine which: