Skip to content

Instantly share code, notes, and snippets.

@mgild
Created May 15, 2024 22:22
Show Gist options
  • Save mgild/26aa544e4d2184206b389a440d31a721 to your computer and use it in GitHub Desktop.
Save mgild/26aa544e4d2184206b389a440d31a721 to your computer and use it in GitHub Desktop.
use crate::protos::oracle_job::*;
use crate::TaskOutput;
use crate::TaskResult;
use crate::TaskRunnerContext;
use anyhow_ext::anyhow;
use anyhow_ext::Result;
use async_recursion::async_recursion;
use jupiter_amm_interface::Amm;
use jupiter_amm_interface::SwapMode;
use lazy_static::lazy_static;
use rust_decimal::Decimal;
use s_controller_lib;
use s_jup_interface::SPoolJup;
use s_sol_val_calc_prog_aggregate::KnownLstSolValCalc;
use s_sol_val_calc_prog_aggregate::LidoLstSolValCalc;
use s_sol_val_calc_prog_aggregate::LstSolValCalc;
use s_sol_val_calc_prog_aggregate::MarinadeLstSolValCalc;
use s_sol_val_calc_prog_aggregate::MutableLstSolValCalc;
use s_sol_val_calc_prog_aggregate::SanctumSplLstSolValCalc;
use s_sol_val_calc_prog_aggregate::SanctumSplMultiLstSolValCalc;
use s_sol_val_calc_prog_aggregate::SplLstSolValCalc;
use s_sol_val_calc_prog_aggregate::SplLstSolValCalcInitKeys;
use s_sol_val_calc_prog_aggregate::WsolLstSolValCalc;
use sanctum_lst_list::PoolInfo::SPool;
use sanctum_lst_list::PoolInfo::SanctumSpl;
use sanctum_lst_list::PoolInfo::SanctumSplMulti;
use sanctum_lst_list::PoolInfo::Spl;
use sanctum_lst_list::SanctumLst;
use sanctum_lst_list::SanctumLstList;
use sanctum_lst_list::SplPoolAccounts;
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::account::Account;
use solana_sdk::account_info::AccountInfo;
use solana_sdk::native_token::LAMPORTS_PER_SOL;
use solana_sdk::pubkey::Pubkey;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::str::FromStr;
lazy_static! {
static ref INF_MINT: Pubkey =
Pubkey::from_str("5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm").unwrap();
static ref SOL_MINT: Pubkey =
Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap();
}
fn to_account_info<'a>(key: &'a Pubkey, data: &'a Account) -> AccountInfo<'a> {
let data = data as *const Account as *mut Account;
unsafe {
AccountInfo {
key: &key,
lamports: Rc::new(RefCell::new(&mut (*data).lamports)),
data: Rc::new(RefCell::new((*data).data.as_mut_slice())),
owner: &(*data).owner,
rent_epoch: (*data).rent_epoch,
executable: (*data).executable,
is_signer: false,
is_writable: false,
}
}
}
async fn get_accounts(client: &RpcClient, keys: Vec<Pubkey>) -> Result<HashMap<Pubkey, Account>> {
let mut account_datas = Vec::new();
for key_chunk in keys.chunks(100) {
let chunk: Vec<Option<Account>> = client
.get_multiple_accounts(&key_chunk)
.await
.unwrap();
account_datas.extend(chunk);
}
let mut data_map = HashMap::new();
for i in 0..keys.len() {
let key = &keys[i];
let account = &account_datas[i];
if let Some(account) = account {
data_map.insert(key.clone(), account.clone());
} else {
println!("Failed to get SPool required account {key}");
}
}
Ok(data_map)
}
pub async fn calc_from_calculator(
client: &RpcClient,
mut calc: KnownLstSolValCalc,
) -> Result<Decimal> {
let accounts = calc.get_accounts_to_update();
let account_datas: Vec<Account> = client
.get_multiple_accounts(&accounts.clone())
.await
.map_err(|_| anyhow!("Failed to get accounts"))?
.into_iter()
.filter_map(|x| x)
.collect::<Vec<_>>();
if accounts.len() != account_datas.len() {
return Err(anyhow!(
"Account data mismatch, failed to fetch all needed calc accounts"
));
}
let mut data_map = HashMap::new();
for i in 0..accounts.len() {
let key = &accounts[i];
data_map.insert(key.clone(), to_account_info(key, &account_datas[i]));
}
calc.update(&data_map)
.map_err(|_| anyhow!("Failed to update calc"))?;
let range = calc
.lst_to_sol(LAMPORTS_PER_SOL)
.map_err(|_| anyhow!("Failed to calculate lst_to_sol"))?;
// Allow up to 1 for rounding
if range.get_max() - range.get_min() > 1 {
return Err(anyhow!("SanctumCalcError: Unexpected range"));
}
Ok(Decimal::from(range.get_max()) / Decimal::from(LAMPORTS_PER_SOL))
}
pub async fn calc_from_lst(client: &RpcClient, lst: Option<&SanctumLst>) -> Result<Decimal> {
let lst = lst.ok_or(anyhow!("SanctumLstNotFound"))?;
let calc;
if let SanctumSplMulti(SplPoolAccounts { pool, .. }) = lst.pool {
calc = KnownLstSolValCalc::SanctumSplMulti(SanctumSplMultiLstSolValCalc::from_keys(
SplLstSolValCalcInitKeys {
lst_mint: lst.mint,
stake_pool_addr: pool,
},
));
} else if let SanctumSpl(SplPoolAccounts { pool, .. }) = lst.pool {
calc = KnownLstSolValCalc::SanctumSpl(SanctumSplLstSolValCalc::from_keys(
SplLstSolValCalcInitKeys {
lst_mint: lst.mint,
stake_pool_addr: pool,
},
));
} else if let Spl(SplPoolAccounts { pool, .. }) = lst.pool {
calc = KnownLstSolValCalc::Spl(SplLstSolValCalc::from_keys(SplLstSolValCalcInitKeys {
lst_mint: lst.mint,
stake_pool_addr: pool,
}));
} else if let sanctum_lst_list::PoolInfo::Marinade = lst.pool {
calc = MarinadeLstSolValCalc::default().into();
} else if let sanctum_lst_list::PoolInfo::Lido = lst.pool {
calc = LidoLstSolValCalc::default().into();
} else if let sanctum_lst_list::PoolInfo::ReservePool = lst.pool {
calc = WsolLstSolValCalc::default().into();
} else if let SPool(_) = lst.pool {
if lst.mint != *INF_MINT {
return Err(anyhow!("InvalidSpool"));
}
return spool_quote(client).await;
} else {
return Err(anyhow!("SanctumLstNotSpl"));
}
calc_from_calculator(client, calc).await
}
pub async fn spool_quote(client: &RpcClient) -> Result<Decimal> {
let lst_state_list_pubkey = s_controller_lib::program::LST_STATE_LIST_ID;
let pid = s_controller_lib::program::ID;
let lst_state_list_account = client.get_account(&lst_state_list_pubkey).await?;
let SanctumLstList { sanctum_lst_list } = SanctumLstList::load();
let mut spool: SPoolJup =
SPoolJup::from_lst_state_list_account(pid, lst_state_list_account, &sanctum_lst_list)?;
let keys = spool.get_accounts_to_update();
let data_map = get_accounts(&client, keys).await?;
spool.update_full(&data_map)?;
let keys = spool.get_accounts_to_update();
let data_map = get_accounts(&client, keys).await?;
spool.update_full(&data_map)?;
let quote = spool.quote(&jupiter_amm_interface::QuoteParams {
amount: 10_u64.pow(9),
input_mint: *INF_MINT,
output_mint: *SOL_MINT,
swap_mode: SwapMode::ExactIn,
})?;
let out_amount = Decimal::new(quote.out_amount.try_into().unwrap(), 9);
Ok(out_amount)
}
pub fn to_lst<'a>(lsts: &'a Vec<SanctumLst>, key: &'a Pubkey) -> Option<&'a SanctumLst> {
lsts.into_iter().find(|x| x.mint == *key)
}
#[async_recursion]
pub async fn sanctum_task(
ctx: &TaskRunnerContext,
task: &SanctumLstPriceTask,
) -> TaskResult<TaskOutput> {
let lst_mint = Pubkey::from_str(&task.lst_mint.clone().unwrap_or_default())
.map_err(|_| anyhow!("SanctumPriceTask:InvalidMint"))?;
let SanctumLstList {
sanctum_lst_list: lsts,
} = SanctumLstList::load();
let client = ctx
.anchor_client
.program(Pubkey::default())
.map_err(|_| anyhow!("Failed to get program"))?
.async_rpc();
let price = calc_from_lst(&client, to_lst(&lsts, &lst_mint)).await?;
Ok(TaskOutput::Num(price))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment