From 02d005c836e49bab56e50c8e29c96ef865869551 Mon Sep 17 00:00:00 2001 From: jgur-psyops Date: Mon, 3 Feb 2025 09:16:44 -0500 Subject: [PATCH] Fixes for staked collat pricing --- programs/marginfi/src/state/price.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/programs/marginfi/src/state/price.rs b/programs/marginfi/src/state/price.rs index f8c1396f..3eeff9ea 100644 --- a/programs/marginfi/src/state/price.rs +++ b/programs/marginfi/src/state/price.rs @@ -6,6 +6,7 @@ use enum_dispatch::enum_dispatch; use fixed::types::I80F48; use pyth_sdk_solana::{state::SolanaPriceAccount, Price, PriceFeed}; use pyth_solana_receiver_sdk::price_update::{self, FeedId, PriceUpdateV2}; +use solana_program::{borsh1::try_from_slice_unchecked, stake::state::StakeStateV2}; use switchboard_on_demand::{CurrentResult, PullFeedAccountData, SPL_TOKEN_PROGRAM_ID}; use switchboard_solana::{ AggregatorAccountData, AggregatorResolutionMode, SwitchboardDecimal, SWITCHBOARD_PROGRAM_ID, @@ -162,7 +163,20 @@ impl OraclePriceFeedAdapter { let lst_mint = Account::<'info, Mint>::try_from(&ais[1]).unwrap(); let lst_supply = lst_mint.supply; - let sol_pool_balance = ais[2].lamports(); + let stake_state = try_from_slice_unchecked::(&ais[2].data.borrow())?; + let (_, stake) = match stake_state { + StakeStateV2::Stake(meta, stake, _) => (meta, stake), + _ => panic!("unsupported stake state"), // TODO emit more specific error + }; + let sol_pool_balance = stake.delegation.stake; + // Note: When the pool is fresh, it has 1 SOL in it (an initial and non-refundable + // balance that will stay in the pool forever). We don't want to include that + // balance when reading the quantity of SOL that has been staked from actual + // depositors (i.e. the amount that can actually be redeemed again). + let lamports_per_sol: u64 = 1_000_000_000; + let sol_pool_adjusted_balance = sol_pool_balance + .checked_sub(lamports_per_sol) + .ok_or_else(math_error!())?; // Note: exchange rate is `sol_pool_balance / lst_supply`, but we will do the // division last to avoid precision loss. Division does not need to be // decimal-adjusted because both SOL and stake positions use 9 decimals @@ -188,14 +202,14 @@ impl OraclePriceFeedAdapter { max_age, )?; let adjusted_price = (feed.price.price as i128) - .checked_mul(sol_pool_balance as i128) + .checked_mul(sol_pool_adjusted_balance as i128) .ok_or_else(math_error!())? .checked_div(lst_supply as i128) .ok_or_else(math_error!())?; feed.price.price = adjusted_price.try_into().unwrap(); let adjusted_ema_price = (feed.ema_price.price as i128) - .checked_mul(sol_pool_balance as i128) + .checked_mul(sol_pool_adjusted_balance as i128) .ok_or_else(math_error!())? .checked_div(lst_supply as i128) .ok_or_else(math_error!())?; @@ -218,14 +232,14 @@ impl OraclePriceFeedAdapter { )?; let adjusted_price = (feed.price.price as i128) - .checked_mul(sol_pool_balance as i128) + .checked_mul(sol_pool_adjusted_balance as i128) .ok_or_else(math_error!())? .checked_div(lst_supply as i128) .ok_or_else(math_error!())?; feed.price.price = adjusted_price.try_into().unwrap(); let adjusted_ema_price = (feed.ema_price.price as i128) - .checked_mul(sol_pool_balance as i128) + .checked_mul(sol_pool_adjusted_balance as i128) .ok_or_else(math_error!())? .checked_div(lst_supply as i128) .ok_or_else(math_error!())?;