use vipers::{assert_keys, unwrap_int};
use crate::{
accounting::Accountant, StakePoolMeta, ASOL, MIN_LIQUIDITY_FOR_EXACT_CALCULATION, SOL,
};
use anchor_lang::{prelude::*, solana_program::native_token::LAMPORTS_PER_SOL};
use num_traits::ToPrimitive;
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, Default, PartialEq, Eq)]
pub struct Snapshot {
pub balance_sol: SOL,
pub supply: ASOL,
pub stake_pools: Vec<StakePoolSnapshot>,
}
#[derive(AnchorSerialize, AnchorDeserialize, Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct StakePoolSnapshot {
pub pool_mint: Pubkey,
pub pool_balance: u64,
pub sol_for_1e9: SOL,
}
impl Snapshot {
pub fn compute_asol_amount_from_sol(&self, sol_amount: SOL) -> Result<ASOL, ProgramError> {
if self.balance_sol.amount <= MIN_LIQUIDITY_FOR_EXACT_CALCULATION {
return Ok(ASOL::from(sol_amount.amount));
}
Ok(unwrap_int!(
sol_amount.checked_mul_asol(self.supply, self.balance_sol)
))
}
}
impl StakePoolSnapshot {
pub fn pool_balance_sol(&self) -> Option<u64> {
self.sol_for_1e9
.to_u128()
.checked_mul(self.pool_balance.into())?
.checked_div(LAMPORTS_PER_SOL.into())?
.to_u64()
}
pub fn try_from_accountant<'info, T: Accountant<'info>>(
pool: &StakePoolMeta,
accountant: &T,
) -> Result<StakePoolSnapshot, ProgramError> {
assert_keys!(
pool.mint,
accountant.crate_reserves().mint,
format!("incorrect pool mint for {:?}", T::METHOD)
);
Self::try_from_accountant_unchecked(accountant)
}
fn try_from_accountant_unchecked<'info, T: Accountant<'info>>(
accountant: &T,
) -> Result<StakePoolSnapshot, ProgramError> {
let reserves = accountant.crate_reserves();
Ok(StakePoolSnapshot {
pool_mint: reserves.mint,
pool_balance: reserves.amount,
sol_for_1e9: accountant.sol_value(LAMPORTS_PER_SOL)?,
})
}
}
#[cfg(test)]
mod tests {
use crate::{stake_pool_mints::*, *};
use anchor_lang::solana_program::native_token::LAMPORTS_PER_SOL;
#[test]
fn test_compute_output_mint_amount() {
let agg = Snapshot {
balance_sol: SOL::from(2_200_000),
supply: ASOL::from(2_000_000),
stake_pools: vec![
StakePoolSnapshot {
pool_mint: lido_stsol::ID,
pool_balance: 1_000_000,
sol_for_1e9: SOL::from(1_100_000),
},
StakePoolSnapshot {
pool_mint: marinade_msol::ID,
pool_balance: 1_000_000,
sol_for_1e9: SOL::from(1_100_000),
},
],
};
let output = agg
.compute_asol_amount_from_sol(SOL::from(1_100_000))
.unwrap();
assert_eq!(output, ASOL::from(1_100_000));
}
#[test]
fn test_compute_output_mint_amount_imbalanced() {
let agg = Snapshot {
balance_sol: SOL::from(3_300_000),
supply: ASOL::from(3_000_000),
stake_pools: vec![
StakePoolSnapshot {
pool_mint: lido_stsol::ID,
pool_balance: 1_000_000,
sol_for_1e9: SOL::from(1_100_000),
},
StakePoolSnapshot {
pool_mint: marinade_msol::ID,
pool_balance: 1_000_000,
sol_for_1e9: SOL::from(2_200_000),
},
],
};
let output = agg
.compute_asol_amount_from_sol(SOL::from(1_100_000))
.unwrap();
assert_eq!(output, ASOL::from(1_100_000));
}
#[test]
fn test_compute_output_mint_amount_above_minimum_imbalanced() {
let agg = Snapshot {
balance_sol: SOL::from(3_300_000_000),
supply: ASOL::from(3_000_000_000),
stake_pools: vec![
StakePoolSnapshot {
pool_mint: lido_stsol::ID,
pool_balance: 1_000_000_000,
sol_for_1e9: SOL::from(1_100_000),
},
StakePoolSnapshot {
pool_mint: marinade_msol::ID,
pool_balance: 1_000_000_000,
sol_for_1e9: SOL::from(2_200_000),
},
],
};
let output = agg
.compute_asol_amount_from_sol(SOL::from(1_100_000))
.unwrap();
assert_eq!(output, ASOL::from(1_000_000));
}
#[test]
fn test_pool_balance_sol_empty() {
let snap = StakePoolSnapshot {
pool_balance: 0,
sol_for_1e9: SOL::from(LAMPORTS_PER_SOL),
..Default::default()
};
assert_eq!(snap.pool_balance_sol().unwrap(), 0);
}
#[test]
fn test_pool_balance_sol_nonempty() {
let snap = StakePoolSnapshot {
pool_balance: 1_000_000,
sol_for_1e9: SOL::from(LAMPORTS_PER_SOL),
..Default::default()
};
assert_eq!(snap.pool_balance_sol().unwrap(), 1_000_000);
}
#[test]
fn test_pool_balance_sol_large() {
let snap = StakePoolSnapshot {
pool_balance: 1_000_000,
sol_for_1e9: SOL::from(1_100_000_000),
..Default::default()
};
assert_eq!(snap.pool_balance_sol().unwrap(), 1_100_000);
}
#[test]
fn test_pool_balance_zero_price() {
let snap = StakePoolSnapshot {
pool_balance: 1_000_000,
sol_for_1e9: SOL::from(0),
..Default::default()
};
assert_eq!(snap.pool_balance_sol().unwrap(), 0);
}
}