Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 97 additions & 37 deletions controller/core/src/svc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,60 +70,57 @@ impl InfCalc {
/// If withheld is nonzero, then zero will be returned;
/// value should be nonzero again after waiting some time.
#[inline]
pub const fn sol_to_inf(&self, sol: u64) -> Option<u64> {
let r = match self.supply_over_lp_due() {
pub const fn inf_to_sol(&self, inf: u64) -> Option<u64> {
let r = match self.lp_due_over_supply() {
None => return None,
Some(r) => r,
};
let r = if r.n == 0 || (r.d == 0 && *self.pool_lamports.withheld() == 0) {
Ratio::<u64, u64>::ONE
} else {
r
};
Floor(r).apply(sol)
r.apply(inf)
}

/// Edge cases: same as [`Self::sol_to_inf`]
/// Edge cases: same as [`Self::inf_to_sol`]
#[inline]
pub const fn inf_to_sol(&self, inf: u64) -> Option<u64> {
pub const fn sol_to_inf(&self, sol: u64) -> Option<RangeInclusive<u64>> {
let r = match self.lp_due_over_supply() {
None => return None,
Some(r) => r,
};
let r = if r.d == 0 || (r.n == 0 && *self.pool_lamports.withheld() == 0) {
Ratio::<u64, u64>::ONE
} else {
r
let range = match r.reverse_est(sol) {
None => return None,
Some(x) => x,
};
Floor(r).apply(inf)
Some(if *range.start() > *range.end() {
*range.end()..=*range.start()
} else {
range
})
}

/// # Returns
/// (inf_supply / lamports_due_to_lp)
/// (lamports_due_to_lp / inf_supply), with the following special-cases
/// that returns 1.0:
/// - LP supply = 0
/// - LP due sol value = 0 & withheld = 0
///
/// `None` if pool is insolvent for LPers (lp_due is negative)
#[inline]
pub const fn supply_over_lp_due(&self) -> Option<Ratio<u64, u64>> {
match self.pool_lamports.lp_due_checked() {
None => None,
Some(d) => Some(Ratio {
n: self.mint_supply,
d,
}),
}
}

/// # Returns
/// (lamports_due_to_lp / inf_supply)
/// See doc on [`Self::inf_to_sol`] on why.
///
/// `None` if pool is insolvent for LPers (lp_due is negative)
#[inline]
pub const fn lp_due_over_supply(&self) -> Option<Ratio<u64, u64>> {
match self.supply_over_lp_due() {
None => None,
// TODO: add .inv() to sanctum-u64-ratio
Some(Ratio { n, d }) => Some(Ratio { n: d, d: n }),
}
pub const fn lp_due_over_supply(&self) -> Option<Floor<Ratio<u64, u64>>> {
let r = match self.pool_lamports.lp_due_checked() {
None => return None,
Some(n) => Ratio {
n,
d: self.mint_supply,
},
};
Some(Floor(
if r.d == 0 || (r.n == 0 && *self.pool_lamports.withheld() == 0) {
Ratio::<u64, u64>::ONE
} else {
r
},
))
}
}

Expand Down Expand Up @@ -166,7 +163,7 @@ impl InfCalc {
pub const fn svc_sol_to_lst(&self, sol: u64) -> Result<RangeInclusive<u64>, InfCalcErr> {
match self.sol_to_inf(sol) {
None => Err(InfCalcErr::Math),
Some(x) => Ok(x..=x),
Some(x) => Ok(x),
}
}
}
Expand Down Expand Up @@ -234,3 +231,66 @@ impl SolValCalcAccs for InfDummyCalcAccs {
self.svc_suf_is_signer()
}
}

#[cfg(test)]
mod tests {
use std::convert::identity;

use proptest::prelude::*;

use crate::typedefs::pool_sv::test_utils::pool_sv_lamports_invar_strat;

use super::*;

/// - PoolSvLamports solvency invariant respected
fn any_calc() -> impl Strategy<Value = InfCalc> {
(
any::<u64>(),
any::<u64>()
.prop_map(pool_sv_lamports_invar_strat)
.prop_flat_map(identity),
)
.prop_map(|(mint_supply, pool_lamports)| InfCalc {
pool_lamports,
mint_supply,
})
}

proptest! {
#[test]
fn lp_due_over_supply_is_never_zero(calc in any_calc()) {
let r = calc.lp_due_over_supply().unwrap();
assert!(!r.0.is_zero());
}
}

// make sure we've handled the `Ratio::reverse_est`
// case correctly
fn assert_valid_range(r: &RangeInclusive<u64>) {
assert!(r.start() <= r.end(), "{r:?}");
}

fn assert_err_bound(val: u64, r: &RangeInclusive<u64>) {
assert!(r.contains(&val), "{val} {r:?}");
}

proptest! {
#[test]
fn sol_to_inf_rt_errbound(
val: u64,
calc in any_calc(),
) {
if let Some(inf) = calc.sol_to_inf(val) {
let [min_rt, max_rt] =
[inf.start(), inf.end()].map(|x| calc.inf_to_sol(*x).unwrap());
assert_err_bound(val, &(min_rt..=max_rt));
assert_valid_range(&inf);
}
if let Some(sol) = calc.inf_to_sol(val) {
let rt = calc.sol_to_inf(sol).unwrap();
assert_err_bound(val, &rt);
assert_valid_range(&rt);
}
}
}
}
23 changes: 23 additions & 0 deletions controller/core/src/typedefs/pool_sv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,26 @@ impl PoolSvMutRefs<'_> {
self.0.iter_mut().zip(vals.0).for_each(|(r, x)| **r = x);
}
}

#[cfg(test)]
pub mod test_utils {
use inf1_test_utils::bals_from_supply;
use proptest::prelude::*;

use super::*;

/// Gens PoolSvLamports where the invariant
///
/// total_sol_value >= protocol_fee_lamports + withheld_lamports
///
/// holds
pub fn pool_sv_lamports_invar_strat(tsv: u64) -> impl Strategy<Value = PoolSvLamports> {
bals_from_supply(tsv).prop_map(move |([withheld, protocol_fee], _rem)| {
NewPoolSvBuilder::start()
.with_protocol_fee(protocol_fee)
.with_withheld(withheld)
.with_total(tsv)
.build()
})
}
}
18 changes: 1 addition & 17 deletions controller/core/src/yields/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,28 +56,12 @@ impl UpdateYield {

#[cfg(test)]
mod tests {
use inf1_test_utils::bals_from_supply;
use proptest::prelude::*;

use crate::typedefs::pool_sv::NewPoolSvBuilder;
use crate::typedefs::pool_sv::test_utils::pool_sv_lamports_invar_strat;

use super::*;

/// Gens PoolSvLamports where the invariant
///
/// total_sol_value >= protocol_fee_lamports + withheld_lamports
///
/// holds
fn pool_sv_lamports_invar_strat(tsv: u64) -> impl Strategy<Value = PoolSvLamports> {
bals_from_supply(tsv).prop_map(move |([withheld, protocol_fee], _rem)| {
NewPoolSvBuilder::start()
.with_protocol_fee(protocol_fee)
.with_withheld(withheld)
.with_total(tsv)
.build()
})
}

fn any_update_yield_strat() -> impl Strategy<Value = UpdateYield> {
any::<u64>()
.prop_flat_map(|old_tsv| (any::<u64>(), pool_sv_lamports_invar_strat(old_tsv)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,10 @@ pub fn process_withdraw_protocol_fees_v2(
mint_supply: inf_token_supply,
};

let inf_to_mint = inf_calc
let inf_to_mint = *inf_calc
.sol_to_inf(protocol_fee_lamports)
.ok_or(Inf1CtlCustomProgErr(Inf1CtlErr::MathError))?;
.ok_or(Inf1CtlCustomProgErr(Inf1CtlErr::MathError))?
.start();

if inf_to_mint == 0 {
return Ok(());
Expand Down
6 changes: 5 additions & 1 deletion controller/program/tests/common/mollusk.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use std::cell::RefCell;

use inf1_test_utils::mollusk_inf_local_ctl;
use mollusk_svm::Mollusk;

thread_local! {
pub static SVM: Mollusk = mollusk_inf_local_ctl()
pub static SVM: Mollusk = mollusk_inf_local_ctl();

pub static SVM_MUT: RefCell<Mollusk> = RefCell::new(mollusk_inf_local_ctl());
}
4 changes: 0 additions & 4 deletions controller/program/tests/tests/admin/add_lst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,6 @@ fn assert_correct_add(
token_program: &[u8; 32],
expected_sol_value_calculator: &[u8; 32],
) {
let lamports_bef: u128 = bef.values().map(|acc| acc.lamports as u128).sum();
let lamports_aft: u128 = aft.values().map(|acc| acc.lamports as u128).sum();
assert_eq!(lamports_bef, lamports_aft);

let (_, pool_reserves_bump) = find_pool_reserves_ata(token_program, mint);
let (_, protocol_fee_accumulator_bump) = find_protocol_fee_accumulator_ata(token_program, mint);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,10 @@ fn withdraw_protocol_fees_v2_test(
pool_lamports: PoolSvLamports::from_pool_state_v2(&pool_state_bef),
mint_supply: inf_mint_bef.supply(),
};
let expected_minted = inf_calc
let expected_minted = *inf_calc
.sol_to_inf(pool_state_bef.protocol_fee_lamports)
.unwrap();
.unwrap()
.start();

assert_token_acc_diffs(
withdraw_to_bef,
Expand Down
12 changes: 12 additions & 0 deletions controller/program/tests/tests/swap/common/asserts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,12 @@ fn assert_accs_swap(
out_svc.neg(),
tsv_inc
);

// sum sol value = total_sol_value invariant
assert_eq!(
list_aft.iter().map(|s| s.sol_value).sum::<u64>(),
ps_aft.total_sol_value
);
}

fn assert_pool_token_movements_add_liq(
Expand Down Expand Up @@ -310,6 +316,12 @@ fn assert_accs_liq(

// assert everything else other than sol value didnt change
assert_diffs_lst_state_list(list_diffs.build(), list_aft_hla, list_aft);

// sum sol value = total_sol_value invariant
assert_eq!(
list_aft.iter().map(|s| s.sol_value).sum::<u64>(),
ps_aft.total_sol_value
);
}

/// assert redemption rate of INF did not decrease after add/remove liq
Expand Down
2 changes: 2 additions & 0 deletions controller/program/tests/tests/swap/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod accounts;
mod asserts;
mod derives;
mod strats;

pub use accounts::*;
pub use asserts::*;
pub use derives::*;
pub use strats::*;
60 changes: 60 additions & 0 deletions controller/program/tests/tests/swap/common/strats.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use inf1_ctl_jiminy::{
accounts::{lst_state_list::LstStatePackedList, pool_state::PoolStateV2},
typedefs::pool_sv::PoolSvMutRefs,
};
use inf1_svc_ag_core::inf1_svc_wsol_core;
use inf1_test_utils::{
any_lst_state, any_lst_state_list, any_pool_state_v2, pool_sv_lamports_solvent_strat,
AnyLstStateArgs, LstStateListData, LstStatePks, NewLstStatePksBuilder, PoolStateV2FtaStrat,
WSOL_MINT,
};
use proptest::prelude::*;

pub fn wsol_lst_state_pks() -> LstStatePks<Option<BoxedStrategy<[u8; 32]>>> {
LstStatePks(
NewLstStatePksBuilder::start()
.with_mint(WSOL_MINT.to_bytes())
.with_sol_value_calculator(inf1_svc_wsol_core::ID)
.build()
.0
.map(|x| Some(Just(x).boxed())),
)
}

/// ps_args.u64s is ignored for pool sv lamport fields
/// in order to maintain invariant that sum of lst_state_list.sol_value
/// = pool_state.total_sol_value
///
/// Currently does not include other entries in lst_state_list to not have to
/// deal with sum of total sol values overflowing u64
pub fn swap_prog_accs_strat<const N: usize>(
lst_args: [AnyLstStateArgs; N],
ps_args: PoolStateV2FtaStrat,
) -> impl Strategy<Value = ([usize; N], LstStateListData, PoolStateV2)> {
(
lst_args.map(|a| any_lst_state(a, None)),
any_lst_state_list(Default::default(), None, 0..=0),
any_pool_state_v2(ps_args),
)
.prop_flat_map(|(lsds, mut lsl, ps)| {
let idxs = lsds.map(|lsd| lsl.upsert(lsd));

let tsv = LstStatePackedList::of_acc_data(&lsl.lst_state_list)
.unwrap()
.0
.iter()
.map(|s| s.into_lst_state().sol_value)
.sum::<u64>();

(
pool_sv_lamports_solvent_strat(tsv),
Just(idxs),
Just(lsl),
Just(ps),
)
})
.prop_map(move |(psv, idxs, lsl, mut ps)| {
PoolSvMutRefs::from_pool_state_v2(&mut ps).update(psv);
(idxs, lsl, ps)
})
}
8 changes: 4 additions & 4 deletions controller/program/tests/tests/swap/v1/add_liq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ fn jupsol_add_liq_fixtures() -> IxPreAccs<(Pubkey, Account)> {

#[test]
fn add_liq_jupsol_fixture() {
let amount = 12_049;
let amount = 10_000;

let prefix_am = jupsol_add_liq_fixtures();
let prefix_keys = IxPreAccs(prefix_am.0.each_ref().map(|(addr, _)| addr.to_bytes()));
Expand Down Expand Up @@ -166,9 +166,9 @@ fn add_liq_jupsol_fixture() {

expect![[r#"
(
12049,
10002,
121,
10000,
4950,
101,
)
"#]]
.assert_debug_eq(&(inp, out, fee));
Expand Down
Loading
Loading