diff --git a/bin/cli/src/save_snapshot.rs b/bin/cli/src/save_snapshot.rs index c318c99c3..50124b875 100644 --- a/bin/cli/src/save_snapshot.rs +++ b/bin/cli/src/save_snapshot.rs @@ -31,14 +31,9 @@ pub async fn save_snapshot( let oracles_and_vaults = group_context .tokens .values() - .map(|value| value.mint_info.oracle) - .chain(group_context.perp_markets.values().map(|p| p.market.oracle)) - .chain( - group_context - .tokens - .values() - .flat_map(|value| value.mint_info.vaults), - ) + .map(|value| value.oracle) + .chain(group_context.perp_markets.values().map(|p| p.oracle)) + .chain(group_context.tokens.values().flat_map(|value| value.vaults)) .unique() .filter(|pk| *pk != Pubkey::default()) .collect::>(); @@ -46,7 +41,7 @@ pub async fn save_snapshot( let serum_programs = group_context .serum3_markets .values() - .map(|s3| s3.market.serum_program) + .map(|s3| s3.serum_program) .unique() .collect_vec(); diff --git a/bin/cli/src/test_oracles.rs b/bin/cli/src/test_oracles.rs index 7f8be54c0..7a7402112 100644 --- a/bin/cli/src/test_oracles.rs +++ b/bin/cli/src/test_oracles.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use itertools::Itertools; use mango_v4::accounts_zerocopy::KeyedAccount; use mango_v4_client::{Client, MangoGroupContext}; @@ -11,11 +13,23 @@ pub async fn run(client: &Client, group: Pubkey) -> anyhow::Result<()> { let oracles = context .tokens .values() - .map(|t| t.mint_info.oracle) - .chain(context.perp_markets.values().map(|p| p.market.oracle)) + .map(|t| t.oracle) + .chain(context.perp_markets.values().map(|p| p.oracle)) .unique() .collect_vec(); + let banks: HashMap<_, _> = mango_v4_client::gpa::fetch_banks(&rpc_async, mango_v4::id(), group) + .await? + .iter() + .map(|(_, b)| (b.oracle, *b)) + .collect(); + let perp_markets: HashMap<_, _> = + mango_v4_client::gpa::fetch_perp_markets(&rpc_async, mango_v4::id(), group) + .await? + .iter() + .map(|(_, p)| (p.oracle, *p)) + .collect(); + let mut interval = tokio::time::interval(std::time::Duration::from_secs(5)); loop { interval.tick().await; @@ -41,25 +55,19 @@ pub async fn run(client: &Client, group: Pubkey) -> anyhow::Result<()> { account: account_opt.unwrap(), }; - let tc_opt = context - .tokens - .values() - .find(|t| t.mint_info.oracle == *pubkey); - let pc_opt = context - .perp_markets - .values() - .find(|p| p.market.oracle == *pubkey); + let bank_opt = banks.get(pubkey); + let perp_opt = perp_markets.get(pubkey); let mut price = None; - if let Some(tc) = tc_opt { - match tc.bank.oracle_price(&keyed_account, Some(slot)) { + if let Some(bank) = bank_opt { + match bank.oracle_price(&keyed_account, Some(slot)) { Ok(p) => price = Some(p), Err(e) => { error!("could not read bank oracle {}: {e:?}", keyed_account.key); } } } - if let Some(pc) = pc_opt { - match pc.market.oracle_price(&keyed_account, Some(slot)) { + if let Some(perp) = perp_opt { + match perp.oracle_price(&keyed_account, Some(slot)) { Ok(p) => price = Some(p), Err(e) => { error!("could not read perp oracle {}: {e:?}", keyed_account.key); diff --git a/bin/keeper/src/crank.rs b/bin/keeper/src/crank.rs index c708bb895..8d7fbb073 100644 --- a/bin/keeper/src/crank.rs +++ b/bin/keeper/src/crank.rs @@ -5,7 +5,8 @@ use itertools::Itertools; use anchor_lang::{__private::bytemuck::cast_ref, solana_program}; use futures::Future; -use mango_v4::state::{EventQueue, EventType, FillEvent, OutEvent, PerpMarket, TokenIndex}; +use mango_v4::state::{EventQueue, EventType, FillEvent, OutEvent, TokenIndex}; +use mango_v4_client::PerpMarketContext; use prometheus::{register_histogram, Encoder, Histogram, IntCounter, Registry}; use solana_sdk::{ instruction::{AccountMeta, Instruction}, @@ -79,7 +80,7 @@ pub async fn runner( interval_update_banks: u64, interval_consume_events: u64, interval_update_funding: u64, - interval_check_new_listings_and_abort: u64, + interval_check_for_changes_and_abort: u64, ) -> Result<(), anyhow::Error> { let handles1 = mango_client .context @@ -106,12 +107,12 @@ pub async fn runner( .values() .filter(|perp| // MNGO-PERP-OLD - perp.market.perp_market_index != 1) + perp.perp_market_index != 1) .map(|perp| { loop_consume_events( mango_client.clone(), perp.address, - perp.market, + perp, interval_consume_events, ) }) @@ -123,12 +124,12 @@ pub async fn runner( .values() .filter(|perp| // MNGO-PERP-OLD - perp.market.perp_market_index != 1) + perp.perp_market_index != 1) .map(|perp| { loop_update_funding( mango_client.clone(), perp.address, - perp.market, + perp, interval_update_funding, ) }) @@ -138,9 +139,9 @@ pub async fn runner( futures::future::join_all(handles1), futures::future::join_all(handles2), futures::future::join_all(handles3), - loop_check_new_listings_and_abort( + MangoClient::loop_check_for_context_changes_and_abort( mango_client.clone(), - interval_check_new_listings_and_abort + Duration::from_secs(interval_check_for_changes_and_abort), ), serve_metrics(), debugging_handle, @@ -149,27 +150,6 @@ pub async fn runner( Ok(()) } -pub async fn loop_check_new_listings_and_abort(mango_client: Arc, interval: u64) { - let mut interval = time::interval(Duration::from_secs(interval)); - loop { - if mango_client - .context - .new_tokens_listed(&mango_client.client.rpc_async()) - .await - .unwrap() - || mango_client - .context - .new_perp_markets_listed(&mango_client.client.rpc_async()) - .await - .unwrap() - { - std::process::abort(); - } - - interval.tick().await; - } -} - pub async fn loop_update_index_and_rate( mango_client: Arc, token_indices: Vec, @@ -191,14 +171,14 @@ pub async fn loop_update_index_and_rate( let mut instructions = vec![]; for token_index in token_indices_clone.iter() { let token = client.context.token(*token_index); - let banks_for_a_token = token.mint_info.banks(); - let oracle = token.mint_info.oracle; + let banks_for_a_token = token.banks(); + let oracle = token.oracle; let mut ix = Instruction { program_id: mango_v4::id(), accounts: anchor_lang::ToAccountMetas::to_account_metas( &mango_v4::accounts::TokenUpdateIndexAndRate { - group: token.mint_info.group, + group: token.group, mint_info: token.mint_info_address, oracle, instructions: solana_program::sysvar::instructions::id(), @@ -264,7 +244,7 @@ pub async fn loop_update_index_and_rate( pub async fn loop_consume_events( mango_client: Arc, pk: Pubkey, - perp_market: PerpMarket, + perp_market: &PerpMarketContext, interval: u64, ) { let mut interval = time::interval(Duration::from_secs(interval)); @@ -362,7 +342,7 @@ pub async fn loop_consume_events( METRIC_CONSUME_EVENTS_FAILURE.inc(); info!( "metricName=ConsumeEventsV4Failure market={} durationMs={} consumed={} error={}", - perp_market.name(), + perp_market.name, confirmation_time, num_of_events, e.to_string() @@ -372,9 +352,7 @@ pub async fn loop_consume_events( METRIC_CONSUME_EVENTS_SUCCESS.inc(); info!( "metricName=ConsumeEventsV4Success market={} durationMs={} consumed={}", - perp_market.name(), - confirmation_time, - num_of_events, + perp_market.name, confirmation_time, num_of_events, ); info!("{:?}", sig_result); } @@ -384,7 +362,7 @@ pub async fn loop_consume_events( pub async fn loop_update_funding( mango_client: Arc, pk: Pubkey, - perp_market: PerpMarket, + perp_market: &PerpMarketContext, interval: u64, ) { let mut interval = time::interval(Duration::from_secs(interval)); @@ -417,7 +395,7 @@ pub async fn loop_update_funding( METRIC_UPDATE_FUNDING_FAILURE.inc(); error!( "metricName=UpdateFundingV4Error market={} durationMs={} error={}", - perp_market.name(), + perp_market.name, confirmation_time, e.to_string() ); @@ -426,8 +404,7 @@ pub async fn loop_update_funding( METRIC_UPDATE_FUNDING_SUCCESS.inc(); info!( "metricName=UpdateFundingV4Success market={} durationMs={}", - perp_market.name(), - confirmation_time, + perp_market.name, confirmation_time, ); info!("{:?}", sig_result); } diff --git a/bin/keeper/src/taker.rs b/bin/keeper/src/taker.rs index 17cd9ebc2..f1eb3eb27 100644 --- a/bin/keeper/src/taker.rs +++ b/bin/keeper/src/taker.rs @@ -25,7 +25,7 @@ pub async fn runner( let mut price_arcs = HashMap::new(); for s3_market in mango_client.context.serum3_markets.values() { - let base_token_index = s3_market.market.base_token_index; + let base_token_index = s3_market.base_token_index; let price = mango_client .bank_oracle_price(base_token_index) .await @@ -47,11 +47,8 @@ pub async fn runner( .map(|s3_market| { loop_blocking_orders( mango_client.clone(), - s3_market.market.name().to_string(), - price_arcs - .get(&s3_market.market.base_token_index) - .unwrap() - .clone(), + s3_market.name.clone(), + price_arcs.get(&s3_market.base_token_index).unwrap().clone(), ) }) .collect::>(); @@ -70,7 +67,7 @@ async fn ensure_oo(mango_client: &Arc) -> Result<(), anyhow::Error> for (market_index, serum3_market) in mango_client.context.serum3_markets.iter() { if account.serum3_orders(*market_index).is_err() { mango_client - .serum3_create_open_orders(serum3_market.market.name()) + .serum3_create_open_orders(&serum3_market.name) .await?; } } diff --git a/bin/liquidator/src/liquidate.rs b/bin/liquidator/src/liquidate.rs index e551c39a6..c95b64b8d 100644 --- a/bin/liquidator/src/liquidate.rs +++ b/bin/liquidator/src/liquidate.rs @@ -178,7 +178,6 @@ impl<'a> LiquidateHelper<'a> { let max_perp_unsettled_leverage = I80F48::from_num(0.95); let perp_unsettled_cost = I80F48::ONE - perp - .market .init_overall_asset_weight .min(max_perp_unsettled_leverage); let max_pnl_transfer = allowed_usdc_borrow / perp_unsettled_cost; @@ -334,7 +333,7 @@ impl<'a> LiquidateHelper<'a> { asset_usdc_equivalent.is_positive() && self .allowed_asset_tokens - .contains(&self.client.context.token(*asset_token_index).mint_info.mint) + .contains(&self.client.context.token(*asset_token_index).mint) }) .ok_or_else(|| { anyhow::anyhow!( @@ -350,7 +349,7 @@ impl<'a> LiquidateHelper<'a> { liab_usdc_equivalent.is_negative() && self .allowed_liab_tokens - .contains(&self.client.context.token(*liab_token_index).mint_info.mint) + .contains(&self.client.context.token(*liab_token_index).mint) }) .ok_or_else(|| { anyhow::anyhow!( @@ -414,7 +413,7 @@ impl<'a> LiquidateHelper<'a> { liab_usdc_equivalent.is_negative() && self .allowed_liab_tokens - .contains(&self.client.context.token(*liab_token_index).mint_info.mint) + .contains(&self.client.context.token(*liab_token_index).mint) }) .ok_or_else(|| { anyhow::anyhow!( @@ -569,13 +568,7 @@ pub async fn maybe_liquidate_account( let maint_health = health_cache.health(HealthType::Maint); - let all_token_mints = HashSet::from_iter( - mango_client - .context - .tokens - .values() - .map(|c| c.mint_info.mint), - ); + let all_token_mints = HashSet::from_iter(mango_client.context.tokens.values().map(|c| c.mint)); // try liquidating let maybe_txsig = LiquidateHelper { diff --git a/bin/liquidator/src/main.rs b/bin/liquidator/src/main.rs index 940c9cffd..9841d21ba 100644 --- a/bin/liquidator/src/main.rs +++ b/bin/liquidator/src/main.rs @@ -198,15 +198,15 @@ async fn main() -> anyhow::Result<()> { let mango_oracles = group_context .tokens .values() - .map(|value| value.mint_info.oracle) - .chain(group_context.perp_markets.values().map(|p| p.market.oracle)) + .map(|value| value.oracle) + .chain(group_context.perp_markets.values().map(|p| p.oracle)) .unique() .collect::>(); let serum_programs = group_context .serum3_markets .values() - .map(|s3| s3.market.serum_program) + .map(|s3| s3.serum_program) .unique() .collect_vec(); @@ -533,6 +533,12 @@ async fn main() -> anyhow::Result<()> { } }); + let check_changes_for_abort_job = + tokio::spawn(MangoClient::loop_check_for_context_changes_and_abort( + mango_client.clone(), + Duration::from_secs(300), + )); + if cli.telemetry == BoolArg::True { tokio::spawn(telemetry::report_regularly( mango_client, @@ -546,6 +552,7 @@ async fn main() -> anyhow::Result<()> { rebalance_job, liquidation_job, token_swap_info_job, + check_changes_for_abort_job, ] .into_iter() .collect(); diff --git a/bin/liquidator/src/rebalance.rs b/bin/liquidator/src/rebalance.rs index f1b37ec81..08b4a361d 100644 --- a/bin/liquidator/src/rebalance.rs +++ b/bin/liquidator/src/rebalance.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use mango_v4::accounts_zerocopy::KeyedAccountSharedData; use mango_v4::state::{ - Bank, BookSide, MangoAccountValue, PerpPosition, PlaceOrderType, Side, TokenIndex, + Bank, BookSide, MangoAccountValue, PerpMarket, PerpPosition, PlaceOrderType, Side, TokenIndex, QUOTE_TOKEN_INDEX, }; use mango_v4_client::{ @@ -34,7 +34,7 @@ fn token_bank( token: &TokenContext, account_fetcher: &chain_data::AccountFetcher, ) -> anyhow::Result { - account_fetcher.fetch::(&token.mint_info.first_bank()) + account_fetcher.fetch::(&token.first_bank()) } pub struct Rebalancer { @@ -121,8 +121,8 @@ impl Rebalancer { .get("SOL") // TODO: better use mint .unwrap(), ); - let quote_mint = quote_token.mint_info.mint; - let sol_mint = sol_token.mint_info.mint; + let quote_mint = quote_token.mint; + let sol_mint = sol_token.mint; let jupiter_version = self.config.jupiter_version; let full_route_job = self.jupiter_quote( @@ -143,7 +143,7 @@ impl Rebalancer { // For the SOL -> output route we need to adjust the in amount by the SOL price let sol_price = self .account_fetcher - .fetch_bank_price(&sol_token.mint_info.first_bank())?; + .fetch_bank_price(&sol_token.first_bank())?; let in_amount_sol = (I80F48::from(in_amount_quote) / sol_price) .ceil() .to_num::(); @@ -199,8 +199,8 @@ impl Rebalancer { .get("SOL") // TODO: better use mint .unwrap(), ); - let quote_mint = quote_token.mint_info.mint; - let sol_mint = sol_token.mint_info.mint; + let quote_mint = quote_token.mint; + let sol_mint = sol_token.mint; let jupiter_version = self.config.jupiter_version; let full_route_job = @@ -305,10 +305,8 @@ impl Rebalancer { { continue; } - let token_mint = token.mint_info.mint; - let token_price = self - .account_fetcher - .fetch_bank_price(&token.mint_info.first_bank())?; + let token_mint = token.mint; + let token_price = self.account_fetcher.fetch_bank_price(&token.first_bank())?; // It's not always possible to bring the native balance to 0 through swaps: // Consider a price <1. You need to sell a bunch of tokens to get 1 USDC native and @@ -419,7 +417,7 @@ impl Rebalancer { #[instrument( skip_all, fields( - perp_market_name = perp.market.name(), + perp_market_name = perp.name, base_lots = perp_position.base_position_lots(), effective_lots = perp_position.effective_base_position_lots(), quote_native = %perp_position.quote_position_native() @@ -438,28 +436,28 @@ impl Rebalancer { let base_lots = perp_position.base_position_lots(); let effective_lots = perp_position.effective_base_position_lots(); let quote_native = perp_position.quote_position_native(); + let perp_market: PerpMarket = self.account_fetcher.fetch(&perp.address)?; if effective_lots != 0 { // send an ioc order to reduce the base position - let oracle_account_data = self.account_fetcher.fetch_raw(&perp.market.oracle)?; - let oracle_account = - KeyedAccountSharedData::new(perp.market.oracle, oracle_account_data); - let oracle_price = perp.market.oracle_price(&oracle_account, None)?; - let oracle_price_lots = perp.market.native_price_to_lot(oracle_price); + let oracle_account_data = self.account_fetcher.fetch_raw(&perp.oracle)?; + let oracle_account = KeyedAccountSharedData::new(perp.oracle, oracle_account_data); + let oracle_price = perp_market.oracle_price(&oracle_account, None)?; + let oracle_price_lots = perp_market.native_price_to_lot(oracle_price); let (side, order_price, oo_lots) = if effective_lots > 0 { ( Side::Ask, - oracle_price * (I80F48::ONE - perp.market.base_liquidation_fee), + oracle_price * (I80F48::ONE - perp_market.base_liquidation_fee), perp_position.asks_base_lots, ) } else { ( Side::Bid, - oracle_price * (I80F48::ONE + perp.market.base_liquidation_fee), + oracle_price * (I80F48::ONE + perp_market.base_liquidation_fee), perp_position.bids_base_lots, ) }; - let price_lots = perp.market.native_price_to_lot(order_price); + let price_lots = perp_market.native_price_to_lot(order_price); let max_base_lots = effective_lots.abs() - oo_lots; if max_base_lots <= 0 { warn!(?side, oo_lots, "cannot place reduce-only order",); @@ -470,8 +468,8 @@ impl Rebalancer { // even match anything. That way we don't need to pay the tx fee and // ioc penalty fee unnecessarily. let opposite_side_key = match side.invert_side() { - Side::Bid => perp.market.bids, - Side::Ask => perp.market.asks, + Side::Bid => perp.bids, + Side::Ask => perp.asks, }; let bookside = Box::new(self.account_fetcher.fetch::(&opposite_side_key)?); if bookside.quantity_at_price(price_lots, now_ts, oracle_price_lots) <= 0 { diff --git a/bin/liquidator/src/token_swap_info.rs b/bin/liquidator/src/token_swap_info.rs index 71e41f462..33ed288aa 100644 --- a/bin/liquidator/src/token_swap_info.rs +++ b/bin/liquidator/src/token_swap_info.rs @@ -94,8 +94,8 @@ impl TokenSwapInfoUpdater { return Ok(()); } - let token_mint = self.mango_client.context.mint_info(token_index).mint; - let quote_mint = self.mango_client.context.mint_info(quote_index).mint; + let token_mint = self.mango_client.context.token(token_index).mint; + let quote_mint = self.mango_client.context.token(quote_index).mint; // these prices are in USD, which doesn't exist on chain let token_price = self diff --git a/bin/liquidator/src/trigger_tcs.rs b/bin/liquidator/src/trigger_tcs.rs index 96e3267d9..b94431155 100644 --- a/bin/liquidator/src/trigger_tcs.rs +++ b/bin/liquidator/src/trigger_tcs.rs @@ -364,7 +364,7 @@ impl Context { &self, token_index: TokenIndex, ) -> anyhow::Result<(Bank, I80F48, Pubkey)> { - let info = self.mango_client.context.mint_info(token_index); + let info = self.mango_client.context.token(token_index); let (bank, price) = self .account_fetcher .fetch_bank_and_price(&info.first_bank())?; diff --git a/bin/liquidator/src/util.rs b/bin/liquidator/src/util.rs index fbd31ba0f..70da942b7 100644 --- a/bin/liquidator/src/util.rs +++ b/bin/liquidator/src/util.rs @@ -60,10 +60,8 @@ pub fn max_swap_source_with_limits( mango_v4_client::health_cache::new_sync(&client.context, account_fetcher, &account) .expect("always ok"); - let source_bank: Bank = - account_fetcher.fetch(&client.context.mint_info(source).first_bank())?; - let target_bank: Bank = - account_fetcher.fetch(&client.context.mint_info(target).first_bank())?; + let source_bank: Bank = account_fetcher.fetch(&client.context.token(source).first_bank())?; + let target_bank: Bank = account_fetcher.fetch(&client.context.token(target).first_bank())?; let source_price = health_cache.token_info(source).unwrap().prices.oracle; @@ -104,10 +102,8 @@ pub fn max_swap_source_ignoring_limits( mango_v4_client::health_cache::new_sync(&client.context, account_fetcher, &account) .expect("always ok"); - let source_bank: Bank = - account_fetcher.fetch(&client.context.mint_info(source).first_bank())?; - let target_bank: Bank = - account_fetcher.fetch(&client.context.mint_info(target).first_bank())?; + let source_bank: Bank = account_fetcher.fetch(&client.context.token(source).first_bank())?; + let target_bank: Bank = account_fetcher.fetch(&client.context.token(target).first_bank())?; let source_price = health_cache.token_info(source).unwrap().prices.oracle; diff --git a/bin/service-mango-crank/src/main.rs b/bin/service-mango-crank/src/main.rs index c07397418..1a36dc6e2 100644 --- a/bin/service-mango-crank/src/main.rs +++ b/bin/service-mango-crank/src/main.rs @@ -83,14 +83,14 @@ async fn main() -> anyhow::Result<()> { let perp_queue_pks: Vec<_> = group_context .perp_markets .values() - .map(|context| (context.address, context.market.event_queue)) + .map(|context| (context.address, context.event_queue)) .collect(); // fetch all serum/openbook markets to find their event queues let serum_market_pks: Vec<_> = group_context .serum3_markets .values() - .map(|context| context.market.serum_market_external) + .map(|context| context.serum_market_external) .collect(); let serum_market_ais = client diff --git a/bin/service-mango-fills/src/main.rs b/bin/service-mango-fills/src/main.rs index 8b7f9fbd5..9350ce4c0 100644 --- a/bin/service-mango-fills/src/main.rs +++ b/bin/service-mango-fills/src/main.rs @@ -384,23 +384,22 @@ async fn main() -> anyhow::Result<()> { .perp_markets .values() .map(|context| { - let quote_decimals = match group_context.tokens.get(&context.market.settle_token_index) - { + let quote_decimals = match group_context.tokens.get(&context.settle_token_index) { Some(token) => token.decimals, None => panic!("token not found for market"), // todo: default to 6 for usdc? }; ( context.address, MarketConfig { - name: context.market.name().to_owned(), - bids: context.market.bids, - asks: context.market.asks, - event_queue: context.market.event_queue, - oracle: context.market.oracle, - base_decimals: context.market.base_decimals, + name: context.name.clone(), + bids: context.bids, + asks: context.asks, + event_queue: context.event_queue, + oracle: context.oracle, + base_decimals: context.base_decimals, quote_decimals, - base_lot_size: context.market.base_lot_size, - quote_lot_size: context.market.quote_lot_size, + base_lot_size: context.base_lot_size, + quote_lot_size: context.quote_lot_size, }, ) }) @@ -410,18 +409,18 @@ async fn main() -> anyhow::Result<()> { .serum3_markets .values() .map(|context| { - let base_decimals = match group_context.tokens.get(&context.market.base_token_index) { + let base_decimals = match group_context.tokens.get(&context.base_token_index) { Some(token) => token.decimals, None => panic!("token not found for market"), // todo: default? }; - let quote_decimals = match group_context.tokens.get(&context.market.quote_token_index) { + let quote_decimals = match group_context.tokens.get(&context.quote_token_index) { Some(token) => token.decimals, None => panic!("token not found for market"), // todo: default to 6 for usdc? }; ( - context.market.serum_market_external, + context.serum_market_external, MarketConfig { - name: context.market.name().to_owned(), + name: context.name.clone(), bids: context.bids, asks: context.asks, event_queue: context.event_q, @@ -438,7 +437,7 @@ async fn main() -> anyhow::Result<()> { let perp_queue_pks: Vec<(Pubkey, Pubkey)> = group_context .perp_markets .values() - .map(|context| (context.address, context.market.event_queue)) + .map(|context| (context.address, context.event_queue)) .collect(); let _a: Vec<(String, String)> = group_context @@ -446,20 +445,15 @@ async fn main() -> anyhow::Result<()> { .values() .map(|context| { ( - context.market.serum_market_external.to_string(), - context.market.name().to_owned(), + context.serum_market_external.to_string(), + context.name.clone(), ) }) .collect(); let b: Vec<(String, String)> = group_context .perp_markets .values() - .map(|context| { - ( - context.address.to_string(), - context.market.name().to_owned(), - ) - }) + .map(|context| (context.address.to_string(), context.name.clone())) .collect(); let market_pubkey_strings: HashMap = [b].concat().into_iter().collect(); diff --git a/bin/service-mango-orderbook/src/main.rs b/bin/service-mango-orderbook/src/main.rs index 18b830b21..4e01f32a8 100644 --- a/bin/service-mango-orderbook/src/main.rs +++ b/bin/service-mango-orderbook/src/main.rs @@ -368,23 +368,22 @@ async fn main() -> anyhow::Result<()> { .perp_markets .values() .map(|context| { - let quote_decimals = match group_context.tokens.get(&context.market.settle_token_index) - { + let quote_decimals = match group_context.tokens.get(&context.settle_token_index) { Some(token) => token.decimals, None => panic!("token not found for market"), // todo: default to 6 for usdc? }; ( context.address, MarketConfig { - name: context.market.name().to_owned(), - bids: context.market.bids, - asks: context.market.asks, - event_queue: context.market.event_queue, - oracle: context.market.oracle, - base_decimals: context.market.base_decimals, + name: context.name.clone(), + bids: context.bids, + asks: context.asks, + event_queue: context.event_queue, + oracle: context.oracle, + base_decimals: context.base_decimals, quote_decimals, - base_lot_size: context.market.base_lot_size, - quote_lot_size: context.market.quote_lot_size, + base_lot_size: context.base_lot_size, + quote_lot_size: context.quote_lot_size, }, ) }) @@ -394,18 +393,18 @@ async fn main() -> anyhow::Result<()> { .serum3_markets .values() .map(|context| { - let base_decimals = match group_context.tokens.get(&context.market.base_token_index) { + let base_decimals = match group_context.tokens.get(&context.base_token_index) { Some(token) => token.decimals, None => panic!("token not found for market"), // todo: default? }; - let quote_decimals = match group_context.tokens.get(&context.market.quote_token_index) { + let quote_decimals = match group_context.tokens.get(&context.quote_token_index) { Some(token) => token.decimals, None => panic!("token not found for market"), // todo: default to 6 for usdc? }; ( - context.market.serum_market_external, + context.serum_market_external, MarketConfig { - name: context.market.name().to_owned(), + name: context.name.clone(), bids: context.bids, asks: context.asks, event_queue: context.event_q, diff --git a/bin/service-mango-pnl/src/main.rs b/bin/service-mango-pnl/src/main.rs index 148c23bc5..2b3d69b56 100644 --- a/bin/service-mango-pnl/src/main.rs +++ b/bin/service-mango-pnl/src/main.rs @@ -65,7 +65,6 @@ async fn compute_pnl( .perp_markets .get(&pp.market_index) .unwrap() - .market .settle_token_index; let perp_settle_health = health_cache.perp_max_settle(settle_token_index).unwrap(); let settleable_pnl = if pnl > 0 { diff --git a/bin/settler/src/main.rs b/bin/settler/src/main.rs index 853261704..ae357518b 100644 --- a/bin/settler/src/main.rs +++ b/bin/settler/src/main.rs @@ -125,15 +125,15 @@ async fn main() -> anyhow::Result<()> { let mango_oracles = group_context .tokens .values() - .map(|value| value.mint_info.oracle) - .chain(group_context.perp_markets.values().map(|p| p.market.oracle)) + .map(|value| value.oracle) + .chain(group_context.perp_markets.values().map(|p| p.oracle)) .unique() .collect::>(); let serum_programs = group_context .serum3_markets .values() - .map(|s3| s3.market.serum_program) + .map(|s3| s3.serum_program) .unique() .collect_vec(); @@ -337,10 +337,21 @@ async fn main() -> anyhow::Result<()> { } }); + let check_changes_for_abort_job = + tokio::spawn(MangoClient::loop_check_for_context_changes_and_abort( + mango_client.clone(), + Duration::from_secs(300), + )); + use futures::StreamExt; - let mut jobs: futures::stream::FuturesUnordered<_> = vec![data_job, settle_job, tcs_start_job] - .into_iter() - .collect(); + let mut jobs: futures::stream::FuturesUnordered<_> = vec![ + data_job, + settle_job, + tcs_start_job, + check_changes_for_abort_job, + ] + .into_iter() + .collect(); jobs.next().await; error!("a critical job aborted, exiting"); diff --git a/bin/settler/src/settle.rs b/bin/settler/src/settle.rs index 43a06f250..4bfb1ac72 100644 --- a/bin/settler/src/settle.rs +++ b/bin/settler/src/settle.rs @@ -42,7 +42,7 @@ fn perp_markets_and_prices( let settle_token = mango_client.context.token(perp_market.settle_token_index); let settle_token_price = - account_fetcher.fetch_bank_price(&settle_token.mint_info.first_bank())?; + account_fetcher.fetch_bank_price(&settle_token.first_bank())?; Ok(( *market_index, diff --git a/bin/settler/src/tcs_start.rs b/bin/settler/src/tcs_start.rs index c682b912a..6c98eb012 100644 --- a/bin/settler/src/tcs_start.rs +++ b/bin/settler/src/tcs_start.rs @@ -112,7 +112,7 @@ impl State { // Clear newly created token positions, so the liqor account is mostly empty for token_index in startable_chunk.iter().map(|(_, _, ti)| *ti).unique() { - let mint = mango_client.context.token(token_index).mint_info.mint; + let mint = mango_client.context.token(token_index).mint; instructions.append(mango_client.token_withdraw_instructions( &liqor_account, mint, @@ -166,12 +166,7 @@ impl State { } fn oracle_for_token(&self, token_index: TokenIndex) -> anyhow::Result { - let bank_pk = self - .mango_client - .context - .token(token_index) - .mint_info - .first_bank(); + let bank_pk = self.mango_client.context.token(token_index).first_bank(); self.account_fetcher.fetch_bank_price(&bank_pk) } @@ -201,7 +196,6 @@ impl State { .mango_client .context .token(tcs.sell_token_index) - .mint_info .first_bank(); let mut sell_bank: Bank = self.account_fetcher.fetch(&sell_bank_pk)?; let sell_pos = account.token_position(tcs.sell_token_index)?; diff --git a/lib/client/src/client.rs b/lib/client/src/client.rs index 131c5c2d6..9db49c828 100644 --- a/lib/client/src/client.rs +++ b/lib/client/src/client.rs @@ -18,7 +18,7 @@ use itertools::Itertools; use mango_v4::accounts_ix::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side}; use mango_v4::accounts_zerocopy::KeyedAccountSharedData; use mango_v4::state::{ - Bank, Group, MangoAccountValue, PerpMarketIndex, PlaceOrderType, SelfTradeBehavior, + Bank, Group, MangoAccountValue, PerpMarket, PerpMarketIndex, PlaceOrderType, SelfTradeBehavior, Serum3MarketIndex, Side, TokenIndex, INSURANCE_TOKEN_INDEX, }; @@ -295,7 +295,7 @@ impl MangoClient { } pub async fn first_bank(&self, token_index: TokenIndex) -> anyhow::Result { - let bank_address = self.context.mint_info(token_index).first_bank(); + let bank_address = self.context.token(token_index).first_bank(); account_fetcher_fetch_anchor_account(&*self.account_fetcher, &bank_address).await } @@ -338,7 +338,6 @@ impl MangoClient { ) -> anyhow::Result { let token = self.context.token_by_mint(&mint)?; let token_index = token.token_index; - let mint_info = token.mint_info; let (health_check_metas, health_cu) = self .derive_health_check_remaining_account_metas(vec![token_index], vec![], vec![]) @@ -353,13 +352,10 @@ impl MangoClient { group: self.group(), account: self.mango_account_address, owner: self.owner(), - bank: mint_info.first_bank(), - vault: mint_info.first_vault(), - oracle: mint_info.oracle, - token_account: get_associated_token_address( - &self.owner(), - &mint_info.mint, - ), + bank: token.first_bank(), + vault: token.first_vault(), + oracle: token.oracle, + token_account: get_associated_token_address(&self.owner(), &token.mint), token_authority: self.owner(), token_program: Token::id(), }, @@ -390,7 +386,6 @@ impl MangoClient { ) -> anyhow::Result { let token = self.context.token_by_mint(&mint)?; let token_index = token.token_index; - let mint_info = token.mint_info; let (health_check_metas, health_cu) = self.context.derive_health_check_remaining_account_metas( @@ -416,12 +411,12 @@ impl MangoClient { group: self.group(), account: self.mango_account_address, owner: self.owner(), - bank: mint_info.first_bank(), - vault: mint_info.first_vault(), - oracle: mint_info.oracle, + bank: token.first_bank(), + vault: token.first_vault(), + oracle: token.oracle, token_account: get_associated_token_address( &self.owner(), - &mint_info.mint, + &token.mint, ), token_program: Token::id(), }, @@ -454,7 +449,7 @@ impl MangoClient { pub async fn bank_oracle_price(&self, token_index: TokenIndex) -> anyhow::Result { let bank = self.first_bank(token_index).await?; - let mint_info = self.context.mint_info(token_index); + let mint_info = self.context.token(token_index); let oracle = self .account_fetcher .fetch_raw_account(&mint_info.oracle) @@ -471,12 +466,11 @@ impl MangoClient { perp_market_index: PerpMarketIndex, ) -> anyhow::Result { let perp = self.context.perp(perp_market_index); - let oracle = self - .account_fetcher - .fetch_raw_account(&perp.market.oracle) - .await?; - let price = perp.market.oracle_price( - &KeyedAccountSharedData::new(perp.market.oracle, oracle.into()), + let perp_market: PerpMarket = + account_fetcher_fetch_anchor_account(&*self.account_fetcher, &perp.address).await?; + let oracle = self.account_fetcher.fetch_raw_account(&perp.oracle).await?; + let price = perp_market.oracle_price( + &KeyedAccountSharedData::new(perp.oracle, oracle.into()), None, )?; Ok(price) @@ -510,8 +504,8 @@ impl MangoClient { group: self.group(), account: account_pubkey, serum_market: s3.address, - serum_program: s3.market.serum_program, - serum_market_external: s3.market.serum_market_external, + serum_program: s3.serum_program, + serum_market_external: s3.serum_market_external, open_orders, owner: self.owner(), payer: self.owner(), @@ -558,9 +552,9 @@ impl MangoClient { .context .derive_health_check_remaining_account_metas(account, vec![], vec![], vec![])?; - let payer_mint_info = match side { - Serum3Side::Bid => quote.mint_info, - Serum3Side::Ask => base.mint_info, + let payer_token = match side { + Serum3Side::Bid => "e, + Serum3Side::Ask => &base, }; let ixs = PreparedInstructions::from_single( @@ -572,12 +566,12 @@ impl MangoClient { group: self.group(), account: self.mango_account_address, open_orders, - payer_bank: payer_mint_info.first_bank(), - payer_vault: payer_mint_info.first_vault(), - payer_oracle: payer_mint_info.oracle, + payer_bank: payer_token.first_bank(), + payer_vault: payer_token.first_vault(), + payer_oracle: payer_token.oracle, serum_market: s3.address, - serum_program: s3.market.serum_program, - serum_market_external: s3.market.serum_market_external, + serum_program: s3.serum_program, + serum_market_external: s3.serum_market_external, market_bids: s3.bids, market_asks: s3.asks, market_event_queue: s3.event_q, @@ -660,13 +654,13 @@ impl MangoClient { group: self.group(), account: self.mango_account_address, open_orders, - quote_bank: quote.mint_info.first_bank(), - quote_vault: quote.mint_info.first_vault(), - base_bank: base.mint_info.first_bank(), - base_vault: base.mint_info.first_vault(), + quote_bank: quote.first_bank(), + quote_vault: quote.first_vault(), + base_bank: base.first_bank(), + base_vault: base.first_vault(), serum_market: s3.address, - serum_program: s3.market.serum_program, - serum_market_external: s3.market.serum_market_external, + serum_program: s3.serum_program, + serum_market_external: s3.serum_market_external, market_base_vault: s3.coin_vault, market_quote_vault: s3.pc_vault, market_vault_signer: s3.vault_signer, @@ -674,8 +668,8 @@ impl MangoClient { token_program: Token::id(), }, v2: mango_v4::accounts::Serum3SettleFundsV2Extra { - quote_oracle: quote.mint_info.oracle, - base_oracle: base.mint_info.oracle, + quote_oracle: quote.oracle, + base_oracle: base.oracle, }, }, None, @@ -708,8 +702,8 @@ impl MangoClient { market_asks: s3.asks, market_event_queue: s3.event_q, serum_market: s3.address, - serum_program: s3.market.serum_program, - serum_market_external: s3.market.serum_market_external, + serum_program: s3.serum_program, + serum_market_external: s3.serum_market_external, owner: self.owner(), }, None, @@ -782,18 +776,18 @@ impl MangoClient { account: *liqee.0, open_orders: *open_orders, serum_market: s3.address, - serum_program: s3.market.serum_program, - serum_market_external: s3.market.serum_market_external, + serum_program: s3.serum_program, + serum_market_external: s3.serum_market_external, market_bids: s3.bids, market_asks: s3.asks, market_event_queue: s3.event_q, market_base_vault: s3.coin_vault, market_quote_vault: s3.pc_vault, market_vault_signer: s3.vault_signer, - quote_bank: quote.mint_info.first_bank(), - quote_vault: quote.mint_info.first_vault(), - base_bank: base.mint_info.first_bank(), - base_vault: base.mint_info.first_vault(), + quote_bank: quote.first_bank(), + quote_vault: quote.first_vault(), + base_bank: base.first_bank(), + base_vault: base.first_vault(), token_program: Token::id(), }, None, @@ -832,8 +826,8 @@ impl MangoClient { group: self.group(), account: self.mango_account_address, serum_market: s3.address, - serum_program: s3.market.serum_program, - serum_market_external: s3.market.serum_market_external, + serum_program: s3.serum_program, + serum_market_external: s3.serum_market_external, open_orders, market_bids: s3.bids, market_asks: s3.asks, @@ -890,10 +884,10 @@ impl MangoClient { account: self.mango_account_address, owner: self.owner(), perp_market: perp.address, - bids: perp.market.bids, - asks: perp.market.asks, - event_queue: perp.market.event_queue, - oracle: perp.market.oracle, + bids: perp.bids, + asks: perp.asks, + event_queue: perp.event_queue, + oracle: perp.oracle, }, None, ); @@ -972,8 +966,8 @@ impl MangoClient { account: self.mango_account_address, owner: self.owner(), perp_market: perp.address, - bids: perp.market.bids, - asks: perp.market.asks, + bids: perp.bids, + asks: perp.asks, }, None, ) @@ -1030,7 +1024,7 @@ impl MangoClient { account_b: (&Pubkey, &MangoAccountValue), ) -> anyhow::Result { let perp = self.context.perp(market_index); - let settlement_token = self.context.token(perp.market.settle_token_index); + let settlement_token = self.context.token(perp.settle_token_index); let (health_remaining_ams, health_cu) = self .context @@ -1054,9 +1048,9 @@ impl MangoClient { perp_market: perp.address, account_a: *account_a.0, account_b: *account_b.0, - oracle: perp.market.oracle, - settle_bank: settlement_token.mint_info.first_bank(), - settle_oracle: settlement_token.mint_info.oracle, + oracle: perp.oracle, + settle_bank: settlement_token.first_bank(), + settle_oracle: settlement_token.oracle, }, None, ); @@ -1103,8 +1097,8 @@ impl MangoClient { group: self.group(), account: *liqee.0, perp_market: perp.address, - bids: perp.market.bids, - asks: perp.market.asks, + bids: perp.bids, + asks: perp.asks, }, None, ); @@ -1130,7 +1124,7 @@ impl MangoClient { max_pnl_transfer: u64, ) -> anyhow::Result { let perp = self.context.perp(market_index); - let settle_token_info = self.context.token(perp.market.settle_token_index); + let settle_token_info = self.context.token(perp.settle_token_index); let (health_remaining_ams, health_cu) = self .derive_liquidation_health_check_remaining_account_metas(liqee.1, &[], &[]) @@ -1144,13 +1138,13 @@ impl MangoClient { &mango_v4::accounts::PerpLiqBaseOrPositivePnl { group: self.group(), perp_market: perp.address, - oracle: perp.market.oracle, + oracle: perp.oracle, liqor: self.mango_account_address, liqor_owner: self.owner(), liqee: *liqee.0, - settle_bank: settle_token_info.mint_info.first_bank(), - settle_vault: settle_token_info.mint_info.first_vault(), - settle_oracle: settle_token_info.mint_info.oracle, + settle_bank: settle_token_info.first_bank(), + settle_vault: settle_token_info.first_vault(), + settle_oracle: settle_token_info.oracle, }, None, ); @@ -1183,7 +1177,7 @@ impl MangoClient { .await?; let perp = self.context.perp(market_index); - let settle_token_info = self.context.token(perp.market.settle_token_index); + let settle_token_info = self.context.token(perp.settle_token_index); let insurance_token_info = self.context.token(INSURANCE_TOKEN_INDEX); let (health_remaining_ams, health_cu) = self @@ -1202,17 +1196,17 @@ impl MangoClient { &mango_v4::accounts::PerpLiqNegativePnlOrBankruptcyV2 { group: self.group(), perp_market: perp.address, - oracle: perp.market.oracle, + oracle: perp.oracle, liqor: self.mango_account_address, liqor_owner: self.owner(), liqee: *liqee.0, - settle_bank: settle_token_info.mint_info.first_bank(), - settle_vault: settle_token_info.mint_info.first_vault(), - settle_oracle: settle_token_info.mint_info.oracle, + settle_bank: settle_token_info.first_bank(), + settle_vault: settle_token_info.first_vault(), + settle_oracle: settle_token_info.oracle, insurance_vault: group.insurance_vault, - insurance_bank: insurance_token_info.mint_info.first_bank(), - insurance_bank_vault: insurance_token_info.mint_info.first_vault(), - insurance_oracle: insurance_token_info.mint_info.oracle, + insurance_bank: insurance_token_info.first_bank(), + insurance_bank_vault: insurance_token_info.first_vault(), + insurance_oracle: insurance_token_info.oracle, token_program: Token::id(), }, None, @@ -1289,7 +1283,6 @@ impl MangoClient { let liab_info = self.context.token(liab_token_index); let bank_remaining_ams = liab_info - .mint_info .banks() .iter() .map(|bank_pubkey| util::to_writable_account_meta(*bank_pubkey)) @@ -1320,7 +1313,7 @@ impl MangoClient { liqor: self.mango_account_address, liqor_owner: self.owner(), liab_mint_info: liab_info.mint_info_address, - quote_vault: quote_info.mint_info.first_vault(), + quote_vault: quote_info.first_vault(), insurance_vault: group.insurance_vault, token_program: Token::id(), }, @@ -1664,6 +1657,30 @@ impl MangoClient { .simulate(&self.client) .await } + + pub async fn loop_check_for_context_changes_and_abort( + mango_client: Arc, + interval: Duration, + ) { + let mut delay = tokio::time::interval(interval); + let rpc_async = mango_client.client.rpc_async(); + loop { + delay.tick().await; + + let new_context = + match MangoGroupContext::new_from_rpc(&rpc_async, mango_client.group()).await { + Ok(v) => v, + Err(e) => { + tracing::warn!("could not fetch context to check for changes: {e:?}"); + continue; + } + }; + + if mango_client.context.changed_significantly(&new_context) { + std::process::abort(); + } + } + } } #[derive(Debug, thiserror::Error)] diff --git a/lib/client/src/context.rs b/lib/client/src/context.rs index 310c07fc2..e0633cab6 100644 --- a/lib/client/src/context.rs +++ b/lib/client/src/context.rs @@ -2,11 +2,10 @@ use std::collections::HashMap; use anchor_client::ClientError; -use anchor_lang::__private::bytemuck::{self, Zeroable}; +use anchor_lang::__private::bytemuck; use mango_v4::state::{ - Bank, Group, MangoAccountValue, MintInfo, PerpMarket, PerpMarketIndex, Serum3Market, - Serum3MarketIndex, TokenIndex, + Group, MangoAccountValue, PerpMarketIndex, Serum3MarketIndex, TokenIndex, MAX_BANKS, }; use fixed::types::I80F48; @@ -20,26 +19,51 @@ use solana_sdk::account::Account; use solana_sdk::instruction::AccountMeta; use solana_sdk::pubkey::Pubkey; -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub struct TokenContext { + pub group: Pubkey, pub token_index: TokenIndex, pub name: String, - pub mint_info: MintInfo, + pub mint: Pubkey, + pub oracle: Pubkey, + pub banks: [Pubkey; MAX_BANKS], + pub vaults: [Pubkey; MAX_BANKS], + pub fallback_oracle: Pubkey, pub mint_info_address: Pubkey, pub decimals: u8, - /// Bank snapshot is never updated, only use static parts! - pub bank: Bank, } impl TokenContext { pub fn native_to_ui(&self, native: I80F48) -> f64 { (native / I80F48::from(10u64.pow(self.decimals.into()))).to_num() } + + pub fn first_bank(&self) -> Pubkey { + self.banks[0] + } + + pub fn first_vault(&self) -> Pubkey { + self.vaults[0] + } + + pub fn banks(&self) -> &[Pubkey] { + let n_banks = self + .banks + .iter() + .position(|&b| b == Pubkey::default()) + .unwrap_or(MAX_BANKS); + &self.banks[..n_banks] + } } +#[derive(Clone, PartialEq, Eq)] pub struct Serum3MarketContext { pub address: Pubkey, - pub market: Serum3Market, + pub name: String, + pub serum_program: Pubkey, + pub serum_market_external: Pubkey, + pub base_token_index: TokenIndex, + pub quote_token_index: TokenIndex, pub bids: Pubkey, pub asks: Pubkey, pub event_q: Pubkey, @@ -51,10 +75,21 @@ pub struct Serum3MarketContext { pub pc_lot_size: u64, } +#[derive(Clone, PartialEq, Eq)] pub struct PerpMarketContext { + pub group: Pubkey, + pub perp_market_index: PerpMarketIndex, + pub settle_token_index: TokenIndex, pub address: Pubkey, - /// PerpMarket snapshot is never updated, only use static parts! - pub market: PerpMarket, + pub name: String, + pub bids: Pubkey, + pub asks: Pubkey, + pub event_queue: Pubkey, + pub oracle: Pubkey, + pub base_lot_size: i64, + pub quote_lot_size: i64, + pub base_decimals: u8, + pub init_overall_asset_weight: I80F48, } pub struct ComputeEstimates { @@ -128,10 +163,6 @@ impl MangoGroupContext { self.token(token_index).mint_info_address } - pub fn mint_info(&self, token_index: TokenIndex) -> MintInfo { - self.token(token_index).mint_info - } - pub fn perp(&self, perp_market_index: PerpMarketIndex) -> &PerpMarketContext { self.perp_markets.get(&perp_market_index).unwrap() } @@ -149,11 +180,11 @@ impl MangoGroupContext { } pub fn serum3_base_token(&self, market_index: Serum3MarketIndex) -> &TokenContext { - self.token(self.serum3(market_index).market.base_token_index) + self.token(self.serum3(market_index).base_token_index) } pub fn serum3_quote_token(&self, market_index: Serum3MarketIndex) -> &TokenContext { - self.token(self.serum3(market_index).market.quote_token_index) + self.token(self.serum3(market_index).quote_token_index) } pub fn token(&self, token_index: TokenIndex) -> &TokenContext { @@ -163,7 +194,7 @@ impl MangoGroupContext { pub fn token_by_mint(&self, mint: &Pubkey) -> anyhow::Result<&TokenContext> { self.tokens .values() - .find(|tc| tc.mint_info.mint == *mint) + .find(|tc| tc.mint == *mint) .ok_or_else(|| anyhow::anyhow!("no token for mint {}", mint)) } @@ -192,10 +223,14 @@ impl MangoGroupContext { TokenContext { token_index: mi.token_index, name: String::new(), - mint_info: *mi, mint_info_address: *pk, decimals: u8::MAX, - bank: Bank::zeroed(), + banks: mi.banks, + vaults: mi.vaults, + fallback_oracle: Pubkey::default(), // coming in v0.22 + oracle: mi.oracle, + group: mi.group, + mint: mi.mint, }, ) }) @@ -209,7 +244,6 @@ impl MangoGroupContext { let token = tokens.get_mut(&bank.token_index).unwrap(); token.name = bank.name().into(); token.decimals = bank.mint_decimals; - token.bank = bank.clone(); } assert!(tokens.values().all(|t| t.decimals != u8::MAX)); @@ -237,7 +271,11 @@ impl MangoGroupContext { s.market_index, Serum3MarketContext { address: *pk, - market: *s, + base_token_index: s.base_token_index, + quote_token_index: s.quote_token_index, + name: s.name().to_string(), + serum_program: s.serum_program, + serum_market_external: s.serum_market_external, bids: from_serum_style_pubkey(market_external.bids), asks: from_serum_style_pubkey(market_external.asks), event_q: from_serum_style_pubkey(market_external.event_q), @@ -261,7 +299,18 @@ impl MangoGroupContext { pm.perp_market_index, PerpMarketContext { address: *pk, - market: *pm, + group: pm.group, + oracle: pm.oracle, + perp_market_index: pm.perp_market_index, + settle_token_index: pm.settle_token_index, + asks: pm.asks, + bids: pm.bids, + event_queue: pm.event_queue, + base_decimals: pm.base_decimals, + base_lot_size: pm.base_lot_size, + quote_lot_size: pm.quote_lot_size, + init_overall_asset_weight: pm.init_overall_asset_weight, + name: pm.name().to_string(), }, ) }) @@ -274,11 +323,11 @@ impl MangoGroupContext { .collect::>(); let serum3_market_indexes_by_name = serum3_markets .iter() - .map(|(i, s)| (s.market.name().to_string(), *i)) + .map(|(i, s)| (s.name.clone(), *i)) .collect::>(); let perp_market_indexes_by_name = perp_markets .iter() - .map(|(i, p)| (p.market.name().to_string(), *i)) + .map(|(i, p)| (p.name.clone(), *i)) .collect::>(); let group_data = fetch_anchor_account::(rpc, &group).await?; @@ -314,10 +363,7 @@ impl MangoGroupContext { account.ensure_token_position(*affected_token_index)?; } for affected_perp_market_index in affected_perp_markets { - let settle_token_index = self - .perp(affected_perp_market_index) - .market - .settle_token_index; + let settle_token_index = self.perp(affected_perp_market_index).settle_token_index; account.ensure_perp_position(affected_perp_market_index, settle_token_index)?; } @@ -325,12 +371,12 @@ impl MangoGroupContext { let mut banks = vec![]; let mut oracles = vec![]; for position in account.active_token_positions() { - let mint_info = self.mint_info(position.token_index); + let token = self.token(position.token_index); banks.push(( - mint_info.first_bank(), + token.first_bank(), writable_banks.iter().any(|&ti| ti == position.token_index), )); - oracles.push(mint_info.oracle); + oracles.push(token.oracle); } let serum_oos = account.active_serum3_orders().map(|&s| s.open_orders); @@ -339,7 +385,7 @@ impl MangoGroupContext { .map(|&pa| self.perp_market_address(pa.market_index)); let perp_oracles = account .active_perp_positions() - .map(|&pa| self.perp(pa.market_index).market.oracle); + .map(|&pa| self.perp(pa.market_index).oracle); let to_account_meta = |pubkey| AccountMeta { pubkey, @@ -384,10 +430,10 @@ impl MangoGroupContext { .unique(); for token_index in token_indexes { - let mint_info = self.mint_info(token_index); + let token = self.token(token_index); let writable_bank = writable_banks.iter().contains(&token_index); - banks.push((mint_info.first_bank(), writable_bank)); - oracles.push(mint_info.oracle); + banks.push((token.first_bank(), writable_bank)); + oracles.push(token.oracle); } let serum_oos = account2 @@ -405,7 +451,7 @@ impl MangoGroupContext { .map(|&index| self.perp_market_address(index)); let perp_oracles = perp_market_indexes .iter() - .map(|&index| self.perp(index).market.oracle); + .map(|&index| self.perp(index).oracle); let to_account_meta = |pubkey| AccountMeta { pubkey, @@ -453,6 +499,47 @@ impl MangoGroupContext { Ok((accounts, cu)) } + /// Returns true if the on-chain context changed significantly, this currently means: + /// - new listings (token, serum, perp) + /// - oracle pubkey or config changes + /// - other config changes visible through the context + /// This is done because those would affect the pubkeys the websocket streams need to listen to, + /// or change limits, oracle staleness or other relevant configuration. + pub fn changed_significantly(&self, other: &Self) -> bool { + if other.tokens.len() != self.tokens.len() { + return true; + } + for (&ti, old) in self.tokens.iter() { + if old != other.token(ti) { + return true; + } + } + + if other.serum3_markets.len() != self.serum3_markets.len() { + return true; + } + for (&mi, old) in self.serum3_markets.iter() { + if old != other.serum3(mi) { + return true; + } + } + + if other.perp_markets.len() != self.perp_markets.len() { + return true; + } + for (&pi, old) in self.perp_markets.iter() { + if old != other.perp(pi) { + return true; + } + } + + if other.address_lookup_tables != self.address_lookup_tables { + return true; + } + + false + } + pub async fn new_tokens_listed(&self, rpc: &RpcClientAsync) -> anyhow::Result { let mint_infos = fetch_mint_infos(rpc, mango_v4::id(), self.group).await?; Ok(mint_infos.len() > self.tokens.len()) diff --git a/lib/client/src/jupiter/v4.rs b/lib/client/src/jupiter/v4.rs index d5c2b24d7..5c68818bf 100644 --- a/lib/client/src/jupiter/v4.rs +++ b/lib/client/src/jupiter/v4.rs @@ -211,25 +211,19 @@ impl<'a> JupiterV4<'a> { .position(|ix| !is_setup_ix(ix.program_id)) .unwrap(); - let bank_ams = [ - source_token.mint_info.first_bank(), - target_token.mint_info.first_bank(), - ] - .into_iter() - .map(util::to_writable_account_meta) - .collect::>(); + let bank_ams = [source_token.first_bank(), target_token.first_bank()] + .into_iter() + .map(util::to_writable_account_meta) + .collect::>(); - let vault_ams = [ - source_token.mint_info.first_vault(), - target_token.mint_info.first_vault(), - ] - .into_iter() - .map(util::to_writable_account_meta) - .collect::>(); + let vault_ams = [source_token.first_vault(), target_token.first_vault()] + .into_iter() + .map(util::to_writable_account_meta) + .collect::>(); let owner = self.mango_client.owner(); - let token_ams = [source_token.mint_info.mint, target_token.mint_info.mint] + let token_ams = [source_token.mint, target_token.mint] .into_iter() .map(|mint| { util::to_writable_account_meta( @@ -270,7 +264,7 @@ impl<'a> JupiterV4<'a> { spl_associated_token_account::instruction::create_associated_token_account_idempotent( &owner, &owner, - &source_token.mint_info.mint, + &source_token.mint, &Token::id(), ), ); diff --git a/lib/client/src/jupiter/v6.rs b/lib/client/src/jupiter/v6.rs index ce0d32a9d..2d8b93b3c 100644 --- a/lib/client/src/jupiter/v6.rs +++ b/lib/client/src/jupiter/v6.rs @@ -220,25 +220,19 @@ impl<'a> JupiterV6<'a> { let source_token = self.mango_client.context.token_by_mint(&input_mint)?; let target_token = self.mango_client.context.token_by_mint(&output_mint)?; - let bank_ams = [ - source_token.mint_info.first_bank(), - target_token.mint_info.first_bank(), - ] - .into_iter() - .map(util::to_writable_account_meta) - .collect::>(); + let bank_ams = [source_token.first_bank(), target_token.first_bank()] + .into_iter() + .map(util::to_writable_account_meta) + .collect::>(); - let vault_ams = [ - source_token.mint_info.first_vault(), - target_token.mint_info.first_vault(), - ] - .into_iter() - .map(util::to_writable_account_meta) - .collect::>(); + let vault_ams = [source_token.first_vault(), target_token.first_vault()] + .into_iter() + .map(util::to_writable_account_meta) + .collect::>(); let owner = self.mango_client.owner(); - let token_ams = [source_token.mint_info.mint, target_token.mint_info.mint] + let token_ams = [source_token.mint, target_token.mint] .into_iter() .map(|mint| { util::to_writable_account_meta( @@ -303,7 +297,7 @@ impl<'a> JupiterV6<'a> { spl_associated_token_account::instruction::create_associated_token_account_idempotent( &owner, &owner, - &source_token.mint_info.mint, + &source_token.mint, &Token::id(), ), ); diff --git a/lib/client/src/lib.rs b/lib/client/src/lib.rs index a7c48e1db..31f5ae936 100644 --- a/lib/client/src/lib.rs +++ b/lib/client/src/lib.rs @@ -10,7 +10,7 @@ mod chain_data_fetcher; mod client; mod context; pub mod error_tracking; -mod gpa; +pub mod gpa; pub mod health_cache; pub mod jupiter; pub mod perp_pnl;