use anchor_client::{
solana_sdk::{
borsh1::try_from_slice_unchecked, commitment_config::CommitmentConfig,
stake::state::StakeStateV2,
},
Client, Cluster,
};
use anchor_lang::{prelude::Pubkey, AccountDeserialize};
use anyhow::Ok;
use pyth_solana_receiver_sdk::price_update::{PriceUpdateV2, VerificationLevel};
use solana_program_pack::Pack;
use spl_token::{solana_program::native_token::LAMPORTS_PER_SOL, state::Mint};
use std::sync::Arc;
use crate::{
math::{casting::Cast, safe_math::SafeMath, u256::safe_multiply_divide},
oracle::{marinade, OraclePriceLiquidate, ORACLE_PROGRAM_ID},
programs::oracle::{
self,
types::{SourceType, Sources},
},
ReadKeypair,
};
use spl_stake_pool::state::StakePool;
const RATE_OUTPUT_DECIMALS: u32 = 15;
const CONFIDENCE_SCALE_FACTOR_LIQUIDATE: u64 = 25;
pub struct SourceAccounts {
pub accounts: Vec<Pubkey>,
pub source: Sources,
}
pub fn load_sources(source_infos: &Vec<Sources>) -> anyhow::Result<Vec<SourceAccounts>> {
let mut source_accounts = vec![];
let mut i = 0;
while i < source_infos.len() {
let source_info = source_infos[i].clone();
if matches!(source_info.source_type, SourceType::SinglePool) {
if !matches!(source_infos[i + 1].source_type, SourceType::SinglePool) {
return Err(anyhow::anyhow!("Invalid source"));
}
let next_account = source_infos[i + 1].clone();
source_accounts.push(SourceAccounts {
accounts: vec![source_info.source, next_account.source],
source: source_infos[i].clone(),
});
i += 3;
} else {
source_accounts.push(SourceAccounts {
accounts: vec![source_info.source],
source: source_infos[i].clone(),
});
i += 1;
}
}
Ok(source_accounts)
}
pub async fn get_oracle_price_liquidate(
oracle: Pubkey,
cluster: Cluster,
) -> anyhow::Result<OraclePriceLiquidate> {
let payer = ReadKeypair::new();
let provider = Client::new_with_options(
cluster.clone(),
Arc::new(payer.clone()),
CommitmentConfig::confirmed(),
);
let program = provider.program(ORACLE_PROGRAM_ID)?;
let oracle_data: oracle::accounts::Oracle = program.account(oracle).await?;
if oracle_data.sources.len() == 0 {
return Err(anyhow::anyhow!("No sources found"));
}
let mut rate: u128 = 10u128.pow(RATE_OUTPUT_DECIMALS);
let source_accounts = load_sources(&oracle_data.sources)?;
let futures = source_accounts.iter().map(|source_account| {
let program = &program;
let source = source_account.source;
async move {
let price = match source.source_type {
oracle::types::SourceType::Pyth => {
let rpc = program.rpc();
let account = rpc.get_account(&source.source).await?;
let mut price_update_data: &[u8] = account.data.iter().as_slice();
let price_update =
PriceUpdateV2::try_deserialize(&mut price_update_data).unwrap();
if !price_update.verification_level.gte(VerificationLevel::Full) {
return Err(anyhow::anyhow!("Price is not valid"));
}
if price_update.price_message.price <= 0
|| price_update.price_message.exponent > 0
{
return Err(anyhow::anyhow!("Price is not valid"));
}
if price_update
.price_message
.conf
.safe_mul(CONFIDENCE_SCALE_FACTOR_LIQUIDATE)?
> price_update.price_message.price.abs() as u64
{
return Err(anyhow::anyhow!("Price confidence not sufficient"));
}
let multiplier: u32 = price_update.price_message.exponent.abs().cast()?;
let delta = 10u128.pow(RATE_OUTPUT_DECIMALS.safe_sub(multiplier)?);
let mut rate = price_update.price_message.price.cast::<u128>()?;
rate = rate.safe_mul(delta)?.saturating_div(source.divisor);
if rate > 0 && source.invert {
rate = 10u128.pow(RATE_OUTPUT_DECIMALS * 2).saturating_div(rate);
}
rate
}
oracle::types::SourceType::StakePool => {
const FACTOR: u128 = 10u128.pow(RATE_OUTPUT_DECIMALS);
let rpc = program.rpc();
let data = rpc.get_account_data(&source.source).await?;
let stake_pool: StakePool = try_from_slice_unchecked(&data)?;
let scaled_pool_lamports: u128 =
FACTOR.safe_mul(stake_pool.total_lamports.cast()?)?;
let total_supply: u128 = stake_pool.pool_token_supply.cast()?;
scaled_pool_lamports.safe_div(total_supply)?
}
oracle::types::SourceType::MsolPool => {
const FACTOR: u128 = 10u128.pow(RATE_OUTPUT_DECIMALS);
let rpc = program.rpc();
let data = rpc.get_account_data(&source.source).await?;
#[allow(deprecated)]
let msol_pool: marinade::State =
anchor_lang::solana_program::borsh0_10::try_from_slice_unchecked(
&data[8..],
)?;
let pending_unstake_lamports = msol_pool
.stake_system
.delayed_unstake_cooling_down
.safe_add(msol_pool.emergency_cooling_down)?
.cast()?;
let total_controlled_lamports = msol_pool
.validator_system
.total_active_balance
.safe_add(pending_unstake_lamports)?
.safe_add(msol_pool.available_reserve_balance)?;
let effective_staked_lamports = total_controlled_lamports
.saturating_sub(msol_pool.circulating_ticket_balance)
.cast::<u128>()?;
let scaled_pool_lamports = effective_staked_lamports.safe_mul(FACTOR)?;
let total_supply: u128 = msol_pool.msol_supply.cast()?;
scaled_pool_lamports.safe_div(total_supply)?
}
oracle::types::SourceType::SinglePool => {
const FACTOR: u128 = 10u128.pow(RATE_OUTPUT_DECIMALS);
let rpc = program.rpc();
let (single_pool_stake_account, single_pool_mint, minimum_pool_balance) = futures::try_join!(
rpc.get_account_data(&source_account.accounts[0]),
rpc.get_account_data(&source_account.accounts[1]),
rpc.get_stake_minimum_delegation()
)?;
let mint = Mint::unpack_from_slice(&single_pool_mint)?;
let token_supply: u64 = mint.supply;
if token_supply == 0 {
return Err(anyhow::anyhow!("Single Pool token supply is zero"));
}
let stake_state: StakeStateV2 =
try_from_slice_unchecked(&single_pool_stake_account)?;
let stake = match stake_state.stake() {
Some(s) => s,
None => {
return Err(anyhow::anyhow!("Invalid Stake Account for Single Pool"));
}
};
let delegation_stake = stake.delegation.stake;
let minimum_pool_balance = std::cmp::max(
minimum_pool_balance,
LAMPORTS_PER_SOL,
);
let active_stake: u128 =
delegation_stake.safe_sub(minimum_pool_balance)?.cast()?;
active_stake
.safe_mul(FACTOR)?
.safe_div(token_supply.cast()?)?
}
_ => {
return Err(anyhow::anyhow!("Unsupported source {}", source.source));
}
};
Ok(price)
}
});
let prices = futures::future::try_join_all(futures).await?;
for price in prices {
rate = safe_multiply_divide(price, rate, 10u128.pow(RATE_OUTPUT_DECIMALS))?;
}
Ok(OraclePriceLiquidate {
price: rate,
sources: oracle_data.sources,
})
}