use anchor_lang::prelude::*;
use anchor_spl::{token::Mint, token_interface};
use gmsol_model::utils::market_token_amount_to_usd;
use gmsol_utils::swap::SwapActionParams;
use crate::{
events::{EventEmitter, GlvTokenValue},
ops::glv::get_glv_value_for_market_with_new_index_price,
states::{Glv, Market, MaxAgeValidator, Oracle, Store, TokenMapHeader, TokenMapLoader},
CoreError,
};
#[event_cpi]
#[derive(Accounts)]
pub struct GetGlvTokenValue<'info> {
pub authority: Signer<'info>,
#[account(has_one = token_map)]
pub store: AccountLoader<'info, Store>,
#[account(has_one = store)]
pub token_map: AccountLoader<'info, TokenMapHeader>,
#[account(mut, has_one = store, has_one = authority)]
pub oracle: AccountLoader<'info, Oracle>,
#[account(has_one = store)]
pub glv: AccountLoader<'info, Glv>,
#[account(constraint = glv.load()?.glv_token == glv_token.key() @ CoreError::TokenMintMismatched)]
pub glv_token: Box<InterfaceAccount<'info, token_interface::Mint>>,
}
impl<'info> GetGlvTokenValue<'info> {
pub(crate) fn invoke(
ctx: Context<'_, '_, 'info, 'info, Self>,
amount: u64,
maximize: bool,
max_age: u32,
emit_event: bool,
) -> Result<u128> {
let accounts = ctx.accounts;
accounts.evaluate(
amount,
maximize,
max_age,
emit_event.then_some(ctx.bumps.event_authority),
ctx.remaining_accounts,
)
}
fn evaluate(
&self,
amount: u64,
maximize: bool,
max_age: u32,
emit_event: Option<u8>,
remaining_accounts: &'info [AccountInfo<'info>],
) -> Result<u128> {
let splitted = {
let token_map = self.token_map.load_token_map()?;
self.glv.load()?.validate_and_split_remaining_accounts(
&self.store.key(),
remaining_accounts,
None::<&SwapActionParams>,
&token_map,
)?
};
let glv = self.glv.load()?;
self.oracle.load_mut()?.with_prices(
&self.store,
&self.token_map,
&splitted.tokens,
splitted.remaining_accounts,
|oracle, _| {
oracle.validate_time(&MaxAgeValidator::new(max_age))?;
let mut prices = None;
let glv_value: u128 = splitted
.markets
.iter()
.zip(splitted.market_tokens)
.map(|(market, market_token)| {
let key = market_token.key();
let balance = u128::from(
glv.market_config(&key)
.ok_or_else(|| error!(CoreError::NotFound))?
.balance(),
);
let market = AccountLoader::<Market>::try_from(market)?;
let mint = Account::<Mint>::try_from(market_token)?;
let market = market.load()?;
let market = market.as_liquidity_market(&mint);
let prices = match prices.as_mut() {
Some(prices) => prices,
None => {
let oracle_prices = oracle.market_prices(&market)?;
prices.get_or_insert(oracle_prices)
}
};
let market_token_value = get_glv_value_for_market_with_new_index_price(
oracle, prices, &market, balance, maximize,
)?
.market_token_value_in_glv;
Result::<_>::Ok(market_token_value)
})
.sum::<Result<_>>()?;
let supply = self.glv_token.supply;
let value = market_token_amount_to_usd(
&u128::from(amount),
&glv_value,
&u128::from(supply),
)
.ok_or_else(|| error!(CoreError::FailedToCalculateGlvValueForMarket))?;
if let Some(bump) = emit_event {
let event_emitter = EventEmitter::new(&self.event_authority, bump);
event_emitter.emit_cpi(&GlvTokenValue {
glv_token: self.glv_token.key(),
supply,
is_value_maximized: maximize,
glv_value,
amount,
value,
})?;
}
Ok(value)
},
)
}
}