bins: Fix restarting on new or changed listings (#802)

- Don't just restart on new listings, but also on significant changes to
  old listings such as oracle changes.
- Cover the liquidator and settler in addition to the keeper.

(cherry picked from commit ce16d79b13)
This commit is contained in:
Christian Kamm 2023-12-05 13:22:24 +01:00
parent 5cfbb8386d
commit 9ba0004760
22 changed files with 379 additions and 319 deletions

View File

@ -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::<Vec<Pubkey>>();
@ -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();

View File

@ -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);

View File

@ -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<MangoClient>, 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<MangoClient>,
token_indices: Vec<TokenIndex>,
@ -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<MangoClient>,
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<MangoClient>,
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);
}

View File

@ -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::<Vec<_>>();
@ -70,7 +67,7 @@ async fn ensure_oo(mango_client: &Arc<MangoClient>) -> 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?;
}
}

View File

@ -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 {

View File

@ -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::<Vec<Pubkey>>();
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();

View File

@ -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<Bank> {
account_fetcher.fetch::<Bank>(&token.mint_info.first_bank())
account_fetcher.fetch::<Bank>(&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::<u64>();
@ -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::<BookSide>(&opposite_side_key)?);
if bookside.quantity_at_price(price_lots, now_ts, oracle_price_lots) <= 0 {

View File

@ -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

View File

@ -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())?;

View File

@ -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;

View File

@ -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

View File

@ -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<String, String> = [b].concat().into_iter().collect();

View File

@ -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,

View File

@ -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 {

View File

@ -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::<Vec<Pubkey>>();
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");

View File

@ -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,

View File

@ -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<I80F48> {
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)?;

View File

@ -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<Bank> {
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<Signature> {
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<PreparedInstructions> {
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<I80F48> {
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<I80F48> {
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 => &quote,
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<PreparedInstructions> {
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<PreparedInstructions> {
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<MangoClient>,
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)]

View File

@ -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::<HashMap<_, _>>();
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::<HashMap<_, _>>();
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::<HashMap<_, _>>();
let group_data = fetch_anchor_account::<Group>(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<bool> {
let mint_infos = fetch_mint_infos(rpc, mango_v4::id(), self.group).await?;
Ok(mint_infos.len() > self.tokens.len())

View File

@ -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::<Vec<_>>();
let bank_ams = [source_token.first_bank(), target_token.first_bank()]
.into_iter()
.map(util::to_writable_account_meta)
.collect::<Vec<_>>();
let vault_ams = [
source_token.mint_info.first_vault(),
target_token.mint_info.first_vault(),
]
.into_iter()
.map(util::to_writable_account_meta)
.collect::<Vec<_>>();
let vault_ams = [source_token.first_vault(), target_token.first_vault()]
.into_iter()
.map(util::to_writable_account_meta)
.collect::<Vec<_>>();
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(),
),
);

View File

@ -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::<Vec<_>>();
let bank_ams = [source_token.first_bank(), target_token.first_bank()]
.into_iter()
.map(util::to_writable_account_meta)
.collect::<Vec<_>>();
let vault_ams = [
source_token.mint_info.first_vault(),
target_token.mint_info.first_vault(),
]
.into_iter()
.map(util::to_writable_account_meta)
.collect::<Vec<_>>();
let vault_ams = [source_token.first_vault(), target_token.first_vault()]
.into_iter()
.map(util::to_writable_account_meta)
.collect::<Vec<_>>();
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(),
),
);

View File

@ -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;