diff --git a/.github/workflows/rust.yaml b/.github/workflows/rust.yaml index 2efe7e22..cfed5d1a 100644 --- a/.github/workflows/rust.yaml +++ b/.github/workflows/rust.yaml @@ -6,6 +6,7 @@ on: push: branches: - master + - v2 workflow_dispatch: env: diff --git a/controller/core/src/yields/update.rs b/controller/core/src/yields/update.rs index c9f07868..cb8dc040 100644 --- a/controller/core/src/yields/update.rs +++ b/controller/core/src/yields/update.rs @@ -16,13 +16,13 @@ impl UpdateYield { let [withheld, protocol_fee] = if self.new_total_sol_value >= *self.old.total() { // unchecked-arith: bounds checked above let gains = self.new_total_sol_value - *self.old.total(); - [ - // saturation: can overflow if new_total_sol_value is large - // and norm_old_total_sol_value < old.withheld. In this case, - // rely on clamping below to ensure LP solvency invariant - self.old.withheld().saturating_add(gains), - *self.old.protocol_fee(), - ] + // since old.withheld <= old.total, + // should never overflow + let new_withheld = match self.old.withheld().checked_add(gains) { + None => return None, + Some(x) => x, + }; + [new_withheld, *self.old.protocol_fee()] } else { // unchecked-arith: bounds checked above let losses = *self.old.total() - self.new_total_sol_value; diff --git a/controller/program/src/instructions/protocol_fee/withdraw_protocol_fees/v1.rs b/controller/program/src/instructions/protocol_fee/withdraw_protocol_fees/v1.rs index a7ee3bda..a0d1b77c 100644 --- a/controller/program/src/instructions/protocol_fee/withdraw_protocol_fees/v1.rs +++ b/controller/program/src/instructions/protocol_fee/withdraw_protocol_fees/v1.rs @@ -61,7 +61,8 @@ pub fn withdraw_protocol_fees_checked<'acc>( .with_beneficiary(&pool.protocol_fee_beneficiary) .with_token_program(token_prog) .with_protocol_fee_accumulator(&expected_protocol_fee_accumulator) - // Free: the beneficiary is entitled to all balances of all ATAs of the protocol fee PDA + // Free: the beneficiary is entitled to all balances of all ATAs of the protocol fee PDA, + // including tokens that are not part of the pool // owner = token-22 or tokenkeg checked below .with_lst_mint(mint_acc.key()) // Free: the beneficiary is free to specify whatever token account to withdraw to diff --git a/controller/program/src/instructions/swap/v2/common.rs b/controller/program/src/instructions/swap/v2/common.rs index 071f286c..dca8272f 100644 --- a/controller/program/src/instructions/swap/v2/common.rs +++ b/controller/program/src/instructions/swap/v2/common.rs @@ -458,14 +458,13 @@ pub fn final_sync( let [inp, out] = [inp, out].map(|(accs, lst_idx)| { let lst_new = cpi_lst_reserves_sol_val(abr, cpi, &accs)?; update_lst_state_sol_val(abr, *accs.ix_prefix.lst_state_list(), lst_idx, lst_new) + .map(|lst_sol_val| SyncSolVal { lst_sol_val }) }); - let inp_snap = inp?; - let out_snap = out?; + let inp_sync = inp?; + let out_sync = out?; let pool = pool_state_v2_checked_mut(abr.get_mut(*accs.ix_prefix.pool_state()))?; - let [out_sync, inp_sync] = - [out_snap, inp_snap].map(|lst_sol_val| SyncSolVal { lst_sol_val }); // exec on out first to reduce odds of overflow // since out will be a decrease let new_total_sol_value = out_sync @@ -491,12 +490,13 @@ pub fn final_sync( }; let lst_new = cpi_lst_reserves_sol_val(abr, cpi, &lst_accs)?; - let lst_sol_val = - update_lst_state_sol_val(abr, *accs.ix_prefix.lst_state_list(), lst_idx, lst_new)?; + let lst_sync = + update_lst_state_sol_val(abr, *accs.ix_prefix.lst_state_list(), lst_idx, lst_new) + .map(|lst_sol_val| SyncSolVal { lst_sol_val })?; let pool = pool_state_v2_checked(abr.get(*accs.ix_prefix.pool_state()))?; - let new_total_sol_value = SyncSolVal { lst_sol_val } + let new_total_sol_value = lst_sync .exec(pool.total_sol_value) .ok_or(Inf1CtlCustomProgErr(Inf1CtlErr::MathError))?; diff --git a/controller/program/src/lib.rs b/controller/program/src/lib.rs index c46080ae..1ae20031 100644 --- a/controller/program/src/lib.rs +++ b/controller/program/src/lib.rs @@ -163,6 +163,7 @@ fn process_ix( process_sync_sol_value(abr, cpi, accounts, lst_idx, clock) } // core user-facing ixs + // v1 swap + liquidity (&SWAP_EXACT_IN_IX_DISCM, data) => { sol_log("SwapExactIn"); let args = parse_swap_ix_args(ix_data_as_arr(data)?); diff --git a/controller/program/tests/tests/admin/set_sol_value_calculator.rs b/controller/program/tests/tests/admin/set_sol_value_calculator.rs index 24670cdd..4da4218f 100644 --- a/controller/program/tests/tests/admin/set_sol_value_calculator.rs +++ b/controller/program/tests/tests/admin/set_sol_value_calculator.rs @@ -10,6 +10,7 @@ use inf1_ctl_jiminy::{ }, keys::{LST_STATE_LIST_ID, POOL_STATE_ID}, program_err::Inf1CtlCustomProgErr, + typedefs::u8bool::U8BoolMut, ID, }; @@ -28,10 +29,11 @@ use inf1_core::instructions::admin::set_sol_value_calculator::{ use inf1_test_utils::{ acc_bef_aft, any_lst_state, any_lst_state_list, any_normal_pk, any_pool_state_v2, - any_spl_stake_pool, any_wsol_lst_state, assert_diffs_lst_state_list, - assert_diffs_pool_state_v2, assert_jiminy_prog_err, find_pool_reserves_ata, - fixtures_accounts_opt_cloned, keys_signer_writable_to_metas, lst_state_list_account, mock_mint, - mock_spl_stake_pool, mock_token_acc, mollusk_exec, pool_state_v2_account, + any_pool_sv_lamports_solvent_strat, any_spl_stake_pool, any_wsol_lst_state, + assert_diffs_lst_state_list, assert_diffs_pool_state_v2, assert_jiminy_prog_err, + find_pool_reserves_ata, fixtures_accounts_opt_cloned, keys_signer_writable_to_metas, + lst_state_list_account, mock_mint, mock_spl_stake_pool, mock_token_acc, mollusk_exec, + pool_state_v2_account, pool_state_v2_u64s_just_lamports_strat, pool_state_v2_u8_bools_normal_strat, raw_mint, raw_token_acc, silence_mollusk_logs, AccountMap, AnyLstStateArgs, Diff, DiffLstStateArgs, DiffsPoolStateV2, GenStakePoolArgs, LstStateData, LstStateListChanges, LstStateListData, LstStatePks, NewLstStatePksBuilder, @@ -233,15 +235,25 @@ fn set_sol_value_calculator_proptest( Ok(()) } +fn any_normal_pool_state_v2_strat() -> impl Strategy { + any_pool_sv_lamports_solvent_strat().prop_flat_map(|ps| { + any_pool_state_v2(PoolStateV2FtaStrat { + u8_bools: pool_state_v2_u8_bools_normal_strat(), + u64s: pool_state_v2_u64s_just_lamports_strat(ps) + // TODO: run on mutable svm with configurable clock to + // test nonzero release case too + .with_last_release_slot(Some(Just(0).boxed())), + ..Default::default() + }) + }) +} + proptest! { #[test] fn set_sol_value_calculator_unauthorized_any( (pool, lsd, stake_pool_addr, stake_pool, non_admin, initial_svc_addr, new_balance) in ( - any_pool_state_v2(PoolStateV2FtaStrat { - u8_bools: pool_state_v2_u8_bools_normal_strat(), - ..Default::default() - }), + any_normal_pool_state_v2_strat(), any_normal_pk(), any::(), ).prop_flat_map( @@ -295,9 +307,9 @@ proptest! { fn set_sol_value_calculator_rebalancing_any( (pool, lsd, stake_pool_addr, stake_pool, initial_svc_addr, new_balance) in ( - any_pool_state_v2(PoolStateV2FtaStrat { - u8_bools: pool_state_v2_u8_bools_normal_strat().with_is_rebalancing(Some(Just(true).boxed())), - ..Default::default() + any_normal_pool_state_v2_strat().prop_map(|mut ps| { + U8BoolMut(&mut ps.is_rebalancing).set_true(); + ps }), any_normal_pk(), any::(), @@ -354,9 +366,9 @@ proptest! { fn set_sol_value_calculator_disabled_any( (pool, lsd, stake_pool_addr, stake_pool, initial_svc_addr, new_balance) in ( - any_pool_state_v2(PoolStateV2FtaStrat { - u8_bools: pool_state_v2_u8_bools_normal_strat().with_is_disabled(Some(Just(true).boxed())), - ..Default::default() + any_normal_pool_state_v2_strat().prop_map(|mut ps| { + U8BoolMut(&mut ps.is_disabled).set_true(); + ps }), any_normal_pk(), any::(), @@ -419,13 +431,7 @@ proptest! { #[test] fn set_sol_value_calculator_wsol_any( (pool, wsol_lsd, initial_svc_addr, new_balance) in - any_pool_state_v2(PoolStateV2FtaStrat { - u8_bools: pool_state_v2_u8_bools_normal_strat(), - // TODO: run on mutable svm with configurable clock to - // test nonzero release case too - u64s: PoolStateV2U64s::default().with_last_release_slot(Some(Just(0).boxed())), - ..Default::default() - }).prop_flat_map( + any_normal_pool_state_v2_strat().prop_flat_map( |pool| ( Just(pool), any_wsol_lst_state(AnyLstStateArgs { @@ -464,13 +470,7 @@ proptest! { fn set_sol_value_calculator_sanctum_spl_multi_any( (pool, lsd, stake_pool_addr, stake_pool, initial_svc_addr, new_balance) in ( - any_pool_state_v2(PoolStateV2FtaStrat { - u8_bools: pool_state_v2_u8_bools_normal_strat(), - // TODO: run on mutable svm with configurable clock to - // test nonzero release case too - u64s: PoolStateV2U64s::default().with_last_release_slot(Some(Just(0).boxed())), - ..Default::default() - }), + any_normal_pool_state_v2_strat(), any_normal_pk(), any::(), ).prop_flat_map( 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 e9db0d31..60e5394a 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 @@ -21,9 +21,10 @@ 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, mock_mint_with_prog, mock_sys_acc, mock_token_acc_with_prog, mollusk_exec, - pool_state_v2_account, pool_state_v2_u64s_solvent_strat, pool_state_v2_u8_bools_normal_strat, - raw_mint, raw_token_acc, silence_mollusk_logs, token_acc_bal_diff_changed, AccountMap, Diff, - DiffsPoolStateV2, PoolStateV2FtaStrat, ALL_FIXTURES, INF_MINT, + pool_state_v2_account, pool_state_v2_u64s_just_lamports_strat, + pool_state_v2_u8_bools_normal_strat, pool_sv_lamports_solvent_strat, raw_mint, raw_token_acc, + silence_mollusk_logs, token_acc_bal_diff_changed, AccountMap, Diff, DiffsPoolStateV2, + PoolStateV2FtaStrat, ALL_FIXTURES, INF_MINT, }; use mollusk_svm::Mollusk; use proptest::prelude::*; @@ -246,7 +247,7 @@ fn to_inp( fn correct_strat() -> impl Strategy { (0..=SAFE_MUL_U64_MAX) .prop_flat_map(|tsv| { - pool_state_v2_u64s_solvent_strat(tsv) + pool_sv_lamports_solvent_strat(tsv) .prop_flat_map(|solvent_u64s| { ( any_normal_pk(), @@ -254,16 +255,7 @@ fn correct_strat() -> impl Strategy { u8_bools: pool_state_v2_u8_bools_normal_strat(), addrs: PoolStateV2Addrs::default() .with_lp_token_mint(Some(Just(INF_MINT_ID).boxed())), - u64s: PoolStateV2U64s::default() - .with_total_sol_value(Some( - Just(*solvent_u64s.total_sol_value()).boxed(), - )) - .with_withheld_lamports(Some( - Just(*solvent_u64s.withheld_lamports()).boxed(), - )) - .with_protocol_fee_lamports(Some( - Just(*solvent_u64s.protocol_fee_lamports()).boxed(), - )) + u64s: pool_state_v2_u64s_just_lamports_strat(solvent_u64s) .with_last_release_slot(Some(Just(0).boxed())), ..Default::default() }), @@ -301,7 +293,7 @@ proptest! { fn zero_fees_strat() -> impl Strategy { (0..=SAFE_MUL_U64_MAX) .prop_flat_map(|tsv| { - pool_state_v2_u64s_solvent_strat(tsv) + pool_sv_lamports_solvent_strat(tsv) .prop_flat_map(|solvent_u64s| { ( any_normal_pk(), @@ -309,13 +301,7 @@ fn zero_fees_strat() -> impl Strategy { u8_bools: pool_state_v2_u8_bools_normal_strat(), addrs: PoolStateV2Addrs::default() .with_lp_token_mint(Some(Just(INF_MINT_ID).boxed())), - u64s: PoolStateV2U64s::default() - .with_total_sol_value(Some( - Just(*solvent_u64s.total_sol_value()).boxed(), - )) - .with_withheld_lamports(Some( - Just(*solvent_u64s.withheld_lamports()).boxed(), - )) + u64s: pool_state_v2_u64s_just_lamports_strat(solvent_u64s) .with_protocol_fee_lamports(Some(Just(0).boxed())) .with_last_release_slot(Some(Just(0).boxed())), ..Default::default() @@ -354,23 +340,14 @@ proptest! { fn unauthorized_strat() -> impl Strategy { (0..=SAFE_MUL_U64_MAX) .prop_flat_map(|tsv| { - pool_state_v2_u64s_solvent_strat(tsv) + pool_sv_lamports_solvent_strat(tsv) .prop_flat_map(|solvent_u64s| { ( any_pool_state_v2(PoolStateV2FtaStrat { u8_bools: pool_state_v2_u8_bools_normal_strat(), addrs: PoolStateV2Addrs::default() .with_lp_token_mint(Some(Just(INF_MINT_ID).boxed())), - u64s: PoolStateV2U64s::default() - .with_total_sol_value(Some( - Just(*solvent_u64s.total_sol_value()).boxed(), - )) - .with_withheld_lamports(Some( - Just(*solvent_u64s.withheld_lamports()).boxed(), - )) - .with_protocol_fee_lamports(Some( - Just(*solvent_u64s.protocol_fee_lamports()).boxed(), - )) + u64s: pool_state_v2_u64s_just_lamports_strat(solvent_u64s) .with_last_release_slot(Some(Just(0).boxed())), ..Default::default() }), @@ -440,7 +417,7 @@ proptest! { fn disabled_strat() -> impl Strategy { (0..=SAFE_MUL_U64_MAX) .prop_flat_map(|tsv| { - pool_state_v2_u64s_solvent_strat(tsv) + pool_sv_lamports_solvent_strat(tsv) .prop_flat_map(|solvent_u64s| { ( any_normal_pk(), @@ -449,14 +426,7 @@ fn disabled_strat() -> impl Strategy { .with_is_disabled(Some(Just(true).boxed())), addrs: PoolStateV2Addrs::default() .with_lp_token_mint(Some(Just(INF_MINT_ID).boxed())), - u64s: PoolStateV2U64s::default() - .with_total_sol_value(Some( - Just(*solvent_u64s.total_sol_value()).boxed(), - )) - .with_withheld_lamports(Some( - Just(*solvent_u64s.withheld_lamports()).boxed(), - )) - .with_protocol_fee_lamports(Some(Just(0).boxed())) + u64s: pool_state_v2_u64s_just_lamports_strat(solvent_u64s) .with_last_release_slot(Some(Just(0).boxed())), ..Default::default() }), @@ -494,7 +464,7 @@ proptest! { fn rebalancing_strat() -> impl Strategy { (0..=SAFE_MUL_U64_MAX) .prop_flat_map(|tsv| { - pool_state_v2_u64s_solvent_strat(tsv) + pool_sv_lamports_solvent_strat(tsv) .prop_flat_map(|solvent_u64s| { ( any_normal_pk(), @@ -503,14 +473,7 @@ fn rebalancing_strat() -> impl Strategy { .with_is_rebalancing(Some(Just(true).boxed())), addrs: PoolStateV2Addrs::default() .with_lp_token_mint(Some(Just(INF_MINT_ID).boxed())), - u64s: PoolStateV2U64s::default() - .with_total_sol_value(Some( - Just(*solvent_u64s.total_sol_value()).boxed(), - )) - .with_withheld_lamports(Some( - Just(*solvent_u64s.withheld_lamports()).boxed(), - )) - .with_protocol_fee_lamports(Some(Just(0).boxed())) + u64s: pool_state_v2_u64s_just_lamports_strat(solvent_u64s) .with_last_release_slot(Some(Just(0).boxed())), ..Default::default() }), @@ -548,7 +511,7 @@ proptest! { fn wrong_token_prog_strat() -> impl Strategy { (0..=SAFE_MUL_U64_MAX) .prop_flat_map(|tsv| { - pool_state_v2_u64s_solvent_strat(tsv) + pool_sv_lamports_solvent_strat(tsv) .prop_flat_map(|solvent_u64s| { ( any_normal_pk(), @@ -558,16 +521,7 @@ fn wrong_token_prog_strat() -> impl Strategy u8_bools: pool_state_v2_u8_bools_normal_strat(), addrs: PoolStateV2Addrs::default() .with_lp_token_mint(Some(Just(INF_MINT_ID).boxed())), - u64s: PoolStateV2U64s::default() - .with_total_sol_value(Some( - Just(*solvent_u64s.total_sol_value()).boxed(), - )) - .with_withheld_lamports(Some( - Just(*solvent_u64s.withheld_lamports()).boxed(), - )) - .with_protocol_fee_lamports(Some( - Just(*solvent_u64s.protocol_fee_lamports()).boxed(), - )) + u64s: pool_state_v2_u64s_just_lamports_strat(solvent_u64s) .with_last_release_slot(Some(Just(0).boxed())), ..Default::default() }), @@ -608,7 +562,7 @@ fn wrong_mint_strat() -> impl Strategy { (0..=SAFE_MUL_U64_MAX) .prop_flat_map(|tsv| { ( - pool_state_v2_u64s_solvent_strat(tsv), + pool_sv_lamports_solvent_strat(tsv), any_normal_pk().prop_filter("mint must not match", |pk| *pk != INF_MINT_ID), ) .prop_flat_map(|(solvent_u64s, wrong_mint)| { @@ -618,16 +572,7 @@ fn wrong_mint_strat() -> impl Strategy { u8_bools: pool_state_v2_u8_bools_normal_strat(), addrs: PoolStateV2Addrs::default() .with_lp_token_mint(Some(Just(wrong_mint).boxed())), - u64s: PoolStateV2U64s::default() - .with_total_sol_value(Some( - Just(*solvent_u64s.total_sol_value()).boxed(), - )) - .with_withheld_lamports(Some( - Just(*solvent_u64s.withheld_lamports()).boxed(), - )) - .with_protocol_fee_lamports(Some( - Just(*solvent_u64s.protocol_fee_lamports()).boxed(), - )) + u64s: pool_state_v2_u64s_just_lamports_strat(solvent_u64s) .with_last_release_slot(Some(Just(0).boxed())), ..Default::default() }), diff --git a/controller/program/tests/tests/sync_sol_value.rs b/controller/program/tests/tests/sync_sol_value.rs index d1632721..63cbd557 100644 --- a/controller/program/tests/tests/sync_sol_value.rs +++ b/controller/program/tests/tests/sync_sol_value.rs @@ -27,10 +27,11 @@ use inf1_svc_ag_core::{ }; use inf1_test_utils::{ acc_bef_aft, any_lst_state, any_lst_state_list, any_normal_pk, any_pool_state_ver, - any_spl_stake_pool, any_wsol_lst_state, assert_diffs_lst_state_list, - assert_diffs_pool_state_mm, assert_jiminy_prog_err, find_pool_reserves_ata, - fixtures_accounts_opt_cloned, jupsol_fixture_svc_suf_accs, keys_signer_writable_to_metas, - lst_state_list_account, mock_mint, mock_prog_acc, mock_token_acc, mollusk_exec, + any_pool_sv_lamports_solvent_strat, any_spl_stake_pool, any_wsol_lst_state, + assert_diffs_lst_state_list, assert_diffs_pool_state_mm, assert_jiminy_prog_err, + find_pool_reserves_ata, fixtures_accounts_opt_cloned, jupsol_fixture_svc_suf_accs, + keys_signer_writable_to_metas, lst_state_list_account, mock_mint, mock_prog_acc, + mock_token_acc, mollusk_exec, pool_state_v2_u64s_just_lamports_strat, pool_state_v2_u8_bools_normal_strat, raw_mint, raw_token_acc, silence_mollusk_logs, svc_accs, AccountMap, AnyLstStateArgs, AnyPoolStateArgs, Diff, DiffsPoolStateV2, GenStakePoolArgs, LstStateListChanges, LstStatePks, NewLstStatePksBuilder, NewSplStakePoolU64sBuilder, @@ -264,20 +265,23 @@ fn sync_sol_value_inp( } fn correct_pool_state_strat() -> impl Strategy { - any_pool_state_ver( - AnyPoolStateArgs { - bools: PoolStateBools::normal(), - ..Default::default() - }, - PoolStateV2FtaStrat { - u8_bools: pool_state_v2_u8_bools_normal_strat(), - // TODO: relax constraint on last_release_slot once we figure out - // how to make mollusk run fast in proptest with different sysvars. - // In the meantime we have to keep it at 0 to avoid TimeWentBackwards - u64s: PoolStateV2U64s::default().with_last_release_slot(Some(Just(0).boxed())), - ..Default::default() - }, - ) + any_pool_sv_lamports_solvent_strat().prop_flat_map(|psv| { + any_pool_state_ver( + AnyPoolStateArgs { + bools: PoolStateBools::normal(), + ..Default::default() + }, + PoolStateV2FtaStrat { + u8_bools: pool_state_v2_u8_bools_normal_strat(), + // TODO: relax constraint on last_release_slot once we figure out + // how to make mollusk run fast in proptest with different sysvars. + // In the meantime we have to keep it at 0 to avoid TimeWentBackwards + u64s: pool_state_v2_u64s_just_lamports_strat(psv) + .with_last_release_slot(Some(Just(0).boxed())), + ..Default::default() + }, + ) + }) } fn wsol_correct_strat() -> impl Strategy { diff --git a/docs/v2/README.md b/docs/v2/README.md index 160a115a..c2bc7ff2 100644 --- a/docs/v2/README.md +++ b/docs/v2/README.md @@ -21,7 +21,7 @@ Change: Add fields: - `withheld_lamports: u64`. Field that records accrued yield in units of lamports (SOL value) that have not yet been released to the pool -- `last_release_slot: u64`. Slot where yield was last released, which happens on all instructions that have writable access to the pool +- `last_release_slot: u64`. Slot where yield was last released. See [release_yield](#release_yield) - `rps: u64`. Proportion of current `withheld_lamports` that is released to the pool per slot, in the [UQ0.63]() 63-bit decimal fixed-point format - `protocol_fee_lamports: u64`. Field that accumulates unclaimed protocol fees in units of lamports (SOL value) that have not yet been claimed by the protocol fee beneficiary - `rps_auth: Address`. Authority allowed to set `rps` field. @@ -72,7 +72,6 @@ These instructions do not run the migration because they are low-frequency non-u Additionally, the following instructions that affect or are affected by yield release events must run it: -- SetRps (new) - WithdrawProtocolFeesV2 (new) - SetRps (new) @@ -120,7 +119,7 @@ For instructions that involve running at least 1 SyncSolValue procedure: Right before the end of the instruction, it will run a `update_yield` subroutine which: -- Compare `end_total_sol_value` with `start_sol_value` +- Compare `end_total_sol_value` with `start_total_sol_value` - If theres an increase (yield was observed) - Increment `withheld_lamports` by same amount - If theres a decrease (loss was observed) @@ -129,23 +128,24 @@ Right before the end of the instruction, it will run a `update_yield` subroutine - `withheld_lamports` - `protocol_fee_lamports` if `withheld_lamports` balance is not enough to cover decrement - Effect of using any previously accumulated yield and protocol fees to soften the loss + - Enforces the invariant that the pool is never insolvent for LPers - In both cases, self-CPI `LogSigned` to log data about how much yield/loss was observed Special-cases: -- Swaps that add or remove liquidity, changing the INF supply. Instead of comparing `end_total_sol_value` with `start_sol_value`, an increment = fee charged by the swap will be added directly. -- EndRebalance. Instead of comparing `end_total_sol_value` with `start_sol_value`, `end_total_sol_value` is compared with the `old_total_sol_value` stored in the `RebalanceRecord` instead. +- Swaps that add or remove liquidity, changing the INF supply. Instead of comparing `end_total_sol_value` with `start_total_sol_value`, an increment = fee charged by the swap will be added directly. +- EndRebalance. Instead of comparing `end_total_sol_value` with `start_total_sol_value`, `end_total_sol_value` is compared with the `old_total_sol_value` stored in the `RebalanceRecord` instead. #### Deferred Minting Of Protocol Fees -See new `WithdrawProtocolFeesV2` instruction below. +See [new `WithdrawProtocolFeesV2` instruction below](#withdrawprotocolfeesv2). The old `WithdrawProtocolFees` instruction will be preserved unchanged to withdraw already accumulated old protocol fees. #### Unification of Swap & Liquidity instruction -See new `SwapExactInV2` and `SwapExactOutV2` instructions below. +See new [`SwapExactInV2`](#swapexactinv2) and [`SwapExactOutV2`](#swapexactoutv2) instructions below. To preserve backward compatibility, the current swap and liquidity instructions will not change their account and instruction data inputs but their implementation will simply defer to the new V2 instructions. @@ -153,7 +153,7 @@ This also means the complete deprecation of the `PriceLpTokensToMint` and `Price #### Other Changes -- SetProtocolFee will take a single `u32` instead of 2 optional `u16`s for updating `pool_state.protocol_fee_nanos` +- `SetProtocolFee` instruction will take a single `u32` instead of 2 optional `u16`s for updating `pool_state.protocol_fee_nanos` ### Additions @@ -177,22 +177,22 @@ This also means the complete deprecation of the `PriceLpTokensToMint` and `Price Same as [v1](https://github.com/igneous-labs/S/blob/master/docs/s-controller-program/instructions.md#accounts-1), but with protocol fee accumulator account removed. -| Account | Description | Read/Write (R/W) | Signer (Y/N) | -| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ------------ | -| signer | Authority of inp_lst_acc. User making the swap. | R | Y | -| inp_mint | Mint being swapped from | R | N | -| out_mint | Mint being swapped to | R | N | -| inp_acc | user token account being swapped from | W | N | -| out_acc | user token account to swap to | W | N | -| inp_token_program | Input token program | R | N | -| out_token_program | Output token program | R | N | -| pool_state | The pool's state singleton PDA | W | N | -| lst_state_list | Dynamic list PDA of LstStates for each LST in the pool | W | N | -| inp_pool_reserves | Input LST reserves token account of the pool. INF mint if inp=INF | W | N | -| out_pool_reserves | Output LST reserves token account of the pool. INF mint if out=INF | W | N | -| inp_calc_accs | Accounts to invoke inp token's SOL value calculator program LstToSol with, excluding the interface prefix accounts. First account should be the calculator program itself. Multiple Accounts. Single unchecked filler account if inp_mint = LP mint | ... | ... | -| out_calc_accs | Accounts to invoke out token's SOL value calculator program SolToLst with, excluding the interface prefix accounts. First account should be the calculator program itself. Multiple Accounts. Single unchecked filler account if out_mint = LP mint | ... | ... | -| pricing_accs | Accounts to invoke pricing program PriceExactIn with. First account should be the pricing program itself. Multiple Accounts. | ... | ... | +| Account | Description | Read/Write (R/W) | Signer (Y/N) | +| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ------------ | +| signer | Authority of inp_lst_acc. User making the swap. | R | Y | +| inp_mint | Mint being swapped from | R | N | +| out_mint | Mint being swapped to | R | N | +| inp_acc | user token account being swapped from | W | N | +| out_acc | user token account to swap to | W | N | +| inp_token_program | Input token program | R | N | +| out_token_program | Output token program | R | N | +| pool_state | The pool's state singleton PDA | W | N | +| lst_state_list | Dynamic list PDA of LstStates for each LST in the pool | W | N | +| inp_pool_reserves | Input LST reserves token account of the pool. INF mint if inp=INF | W | N | +| out_pool_reserves | Output LST reserves token account of the pool. INF mint if out=INF | W | N | +| inp_calc_accs | Accounts to invoke inp token's SOL value calculator program LstToSol with, excluding the interface prefix accounts. First account should be the calculator program itself. Multiple Accounts. Single unchecked filler account if inp_mint=INF | ... | ... | +| out_calc_accs | Accounts to invoke out token's SOL value calculator program SolToLst with, excluding the interface prefix accounts. First account should be the calculator program itself. Multiple Accounts. Single unchecked filler account if out_mint=INF | ... | ... | +| pricing_accs | Accounts to invoke pricing program PriceExactIn with. First account should be the pricing program itself. Multiple Accounts. | ... | ... | ###### Procedure diff --git a/test-utils/src/gen/controller/pool_state/v2.rs b/test-utils/src/gen/controller/pool_state/v2.rs index 9ac6bae3..63d55662 100644 --- a/test-utils/src/gen/controller/pool_state/v2.rs +++ b/test-utils/src/gen/controller/pool_state/v2.rs @@ -3,7 +3,11 @@ use inf1_ctl_core::{ NewPoolStateV2U8BoolsBuilder, PoolStateV2, PoolStateV2Addrs, PoolStateV2Fta, PoolStateV2FtaVals, PoolStateV2U64s, PoolStateV2U8Bools, }, - typedefs::{fee_nanos::FeeNanos, rps::Rps}, + typedefs::{ + fee_nanos::FeeNanos, + pool_sv::{NewPoolSvBuilder, PoolSvLamports}, + rps::Rps, + }, }; use jiminy_sysvar_rent::Rent; use proptest::prelude::*; @@ -36,15 +40,30 @@ pub fn pool_state_v2_u8_bools_normal_strat() -> PoolStateV2U8Bools impl Strategy> { - bals_from_supply::<2>(tsv).prop_map(move |([withheld, protocol_fee], _rem)| { - PoolStateV2U64s::default() - .with_total_sol_value(tsv) - .with_withheld_lamports(withheld) - .with_protocol_fee_lamports(protocol_fee) +pub fn pool_sv_lamports_solvent_strat(tsv: u64) -> impl Strategy { + bals_from_supply(tsv).prop_map(move |([withheld, protocol_fee], _rem)| { + NewPoolSvBuilder::start() + .with_total(tsv) + .with_withheld(withheld) + .with_protocol_fee(protocol_fee) + .build() }) } +pub fn any_pool_sv_lamports_solvent_strat() -> impl Strategy { + (0..=u64::MAX).prop_flat_map(pool_sv_lamports_solvent_strat) +} + +pub fn pool_state_v2_u64s_just_lamports_strat( + x: PoolSvLamports, +) -> PoolStateV2U64s>> { + let to_sjb = |x: &u64| Some(Just(*x).boxed()); + PoolStateV2U64s::default() + .with_total_sol_value(to_sjb(x.total())) + .with_withheld_lamports(to_sjb(x.withheld())) + .with_protocol_fee_lamports(to_sjb(x.protocol_fee())) +} + pub fn any_pool_state_v2( PoolStateV2FtaStrat { addrs,