jup_lend_sdk/oracle/
read.rs1use anchor_client::{
2 solana_sdk::{
3 borsh1::try_from_slice_unchecked, commitment_config::CommitmentConfig,
4 stake::state::StakeStateV2,
5 },
6 Client, Cluster,
7};
8use anchor_lang::{prelude::Pubkey, AccountDeserialize};
9use anyhow::Ok;
10use pyth_solana_receiver_sdk::price_update::{PriceUpdateV2, VerificationLevel};
11use solana_program_pack::Pack;
12use spl_token::{solana_program::native_token::LAMPORTS_PER_SOL, state::Mint};
13use std::sync::Arc;
14
15use crate::{
16 math::{casting::Cast, safe_math::SafeMath, u256::safe_multiply_divide},
17 oracle::{marinade, OraclePriceLiquidate, ORACLE_PROGRAM_ID},
18 programs::oracle::{
19 self,
20 types::{SourceType, Sources},
21 },
22 ReadKeypair,
23};
24use spl_stake_pool::state::StakePool;
25
26const RATE_OUTPUT_DECIMALS: u32 = 15;
27const CONFIDENCE_SCALE_FACTOR_LIQUIDATE: u64 = 25; pub struct SourceAccounts {
32 pub accounts: Vec<Pubkey>,
33 pub source: Sources,
34}
35
36pub fn load_sources(source_infos: &Vec<Sources>) -> anyhow::Result<Vec<SourceAccounts>> {
37 let mut source_accounts = vec![];
38 let mut i = 0;
39 while i < source_infos.len() {
40 let source_info = source_infos[i].clone();
41
42 if matches!(source_info.source_type, SourceType::SinglePool) {
44 if !matches!(source_infos[i + 1].source_type, SourceType::SinglePool) {
45 return Err(anyhow::anyhow!("Invalid source"));
46 }
47
48 let next_account = source_infos[i + 1].clone();
49
50 source_accounts.push(SourceAccounts {
51 accounts: vec![source_info.source, next_account.source],
52 source: source_infos[i].clone(),
53 });
54
55 i += 3;
56 } else {
57 source_accounts.push(SourceAccounts {
58 accounts: vec![source_info.source],
59 source: source_infos[i].clone(),
60 });
61
62 i += 1;
63 }
64 }
65
66 Ok(source_accounts)
67}
68
69pub async fn get_oracle_price_liquidate(
70 oracle: Pubkey,
71 cluster: Cluster,
72) -> anyhow::Result<OraclePriceLiquidate> {
73 let payer = ReadKeypair::new();
74 let provider = Client::new_with_options(
75 cluster.clone(),
76 Arc::new(payer.clone()),
77 CommitmentConfig::processed(),
78 );
79 let program = provider.program(ORACLE_PROGRAM_ID)?;
80 let oracle_data: oracle::accounts::Oracle = program.account(oracle).await?;
81
82 if oracle_data.sources.len() == 0 {
83 return Err(anyhow::anyhow!("No sources found"));
84 }
85
86 let mut rate: u128 = 10u128.pow(RATE_OUTPUT_DECIMALS);
87 let source_accounts = load_sources(&oracle_data.sources)?;
88
89 let futures = source_accounts.iter().map(|source_account| {
90 let program = &program;
91 let source = source_account.source;
92 async move {
93 let price = match source.source_type {
94 oracle::types::SourceType::Pyth => {
95 let rpc = program.rpc();
96
97 let account = rpc.get_account(&source.source).await?;
98
99 let mut price_update_data: &[u8] = account.data.iter().as_slice();
100
101 let price_update =
102 PriceUpdateV2::try_deserialize(&mut price_update_data).unwrap();
103
104 if !price_update.verification_level.gte(VerificationLevel::Full) {
105 return Err(anyhow::anyhow!("Price is not valid"));
106 }
107
108 if price_update.price_message.price <= 0
109 || price_update.price_message.exponent > 0
110 {
111 return Err(anyhow::anyhow!("Price is not valid"));
112 }
113
114 if price_update
115 .price_message
116 .conf
117 .safe_mul(CONFIDENCE_SCALE_FACTOR_LIQUIDATE)?
118 > price_update.price_message.price.abs() as u64
119 {
120 return Err(anyhow::anyhow!("Price confidence not sufficient"));
121 }
122
123 let multiplier: u32 = price_update.price_message.exponent.abs().cast()?;
124 let delta = 10u128.pow(RATE_OUTPUT_DECIMALS.safe_sub(multiplier)?);
125 let mut rate = price_update.price_message.price.cast::<u128>()?;
126
127 rate = rate.safe_mul(delta)?.saturating_div(source.divisor);
128
129 if rate > 0 && source.invert {
130 rate = 10u128.pow(RATE_OUTPUT_DECIMALS * 2).saturating_div(rate);
131 }
132
133 rate
134 }
135 oracle::types::SourceType::StakePool => {
136 const FACTOR: u128 = 10u128.pow(RATE_OUTPUT_DECIMALS);
137
138 let rpc = program.rpc();
139 let data = rpc.get_account_data(&source.source).await?;
140 let stake_pool: StakePool = try_from_slice_unchecked(&data)?;
141
142 let scaled_pool_lamports: u128 =
143 FACTOR.safe_mul(stake_pool.total_lamports.cast()?)?;
144 let total_supply: u128 = stake_pool.pool_token_supply.cast()?;
145
146 scaled_pool_lamports.safe_div(total_supply)?
147 }
148
149 oracle::types::SourceType::MsolPool => {
150 const FACTOR: u128 = 10u128.pow(RATE_OUTPUT_DECIMALS);
151
152 let rpc = program.rpc();
153 let data = rpc.get_account_data(&source.source).await?;
154 #[allow(deprecated)]
155 let msol_pool: marinade::State =
156 anchor_lang::solana_program::borsh0_10::try_from_slice_unchecked(
157 &data[8..],
158 )?;
159
160 let pending_unstake_lamports = msol_pool
161 .stake_system
162 .delayed_unstake_cooling_down
163 .safe_add(msol_pool.emergency_cooling_down)?
164 .cast()?;
165
166 let total_controlled_lamports = msol_pool
167 .validator_system
168 .total_active_balance
169 .safe_add(pending_unstake_lamports)?
170 .safe_add(msol_pool.available_reserve_balance)?;
171
172 let effective_staked_lamports = total_controlled_lamports
173 .saturating_sub(msol_pool.circulating_ticket_balance)
174 .cast::<u128>()?;
175
176 let scaled_pool_lamports = effective_staked_lamports.safe_mul(FACTOR)?;
177 let total_supply: u128 = msol_pool.msol_supply.cast()?;
178
179 scaled_pool_lamports.safe_div(total_supply)?
180 }
181
182 oracle::types::SourceType::SinglePool => {
183 const FACTOR: u128 = 10u128.pow(RATE_OUTPUT_DECIMALS);
184
185 let rpc = program.rpc();
186 let (single_pool_stake_account, single_pool_mint, minimum_pool_balance) = futures::try_join!(
187 rpc.get_account_data(&source_account.accounts[0]),
188 rpc.get_account_data(&source_account.accounts[1]),
189 rpc.get_stake_minimum_delegation()
190 )?;
191
192
193 let mint = Mint::unpack_from_slice(&single_pool_mint)?;
194
195 let token_supply: u64 = mint.supply;
196
197 if token_supply == 0 {
198 return Err(anyhow::anyhow!("Single Pool token supply is zero"));
199 }
200
201 let stake_state: StakeStateV2 =
202 try_from_slice_unchecked(&single_pool_stake_account)?;
203
204 let stake = match stake_state.stake() {
205 Some(s) => s,
206 None => {
207 return Err(anyhow::anyhow!("Invalid Stake Account for Single Pool"));
208 }
209 };
210
211 let delegation_stake = stake.delegation.stake;
212 let minimum_pool_balance = std::cmp::max(
213 minimum_pool_balance,
214 LAMPORTS_PER_SOL,
215 );
216
217
218 let active_stake: u128 =
219 delegation_stake.safe_sub(minimum_pool_balance)?.cast()?;
220
221 active_stake
222 .safe_mul(FACTOR)?
223 .safe_div(token_supply.cast()?)?
224 }
225
226 _ => {
227 return Err(anyhow::anyhow!("Unsupported source {}", source.source));
228 }
229 };
230
231 Ok(price)
232 }
233 });
234
235 let prices = futures::future::try_join_all(futures).await?;
236
237 for price in prices {
238 rate = safe_multiply_divide(price, rate, 10u128.pow(RATE_OUTPUT_DECIMALS))?;
239 }
240
241 Ok(OraclePriceLiquidate {
242 price: rate,
243 sources: oracle_data.sources,
244 })
245}