Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use fallback oracles in Rust client #838

Merged
merged 25 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
081dd66
rename usd_opt to usdc_opt in OracleAccountInfos
Lou-Kamades Jan 4, 2024
17c0db2
use fallbacks when fetching bank+ price in AccountFetcher struct
Lou-Kamades Jan 4, 2024
ddaf0cc
feat: add derive_fallback_oracle_keys to MangoGroupContext
Lou-Kamades Jan 4, 2024
1ff4164
test: properly assert failure CU in test_health_compute_tokens_fallba…
Lou-Kamades Jan 8, 2024
38cba5e
provide fallback oracle accounts in the rust client
Lou-Kamades Jan 8, 2024
8906c5f
liquidator: update for fallback oracles
Lou-Kamades Jan 8, 2024
9f25064
set fallback config in mango services
Lou-Kamades Jan 8, 2024
0a4723f
support fallback oracles in rust settler + keeper
Lou-Kamades Jan 8, 2024
f174fd4
fix send error related to fetching fallbacks dynamically in tcs_start
Lou-Kamades Jan 8, 2024
9cae701
drop derive_fallback_oracle_keys_sync
ckamm Jan 11, 2024
712f38b
add fetch_multiple_accounts to AccountFetcher trait
Lou-Kamades Jan 11, 2024
644ece0
revert client::new() api
Lou-Kamades Jan 11, 2024
40a00bf
deriving oracle keys uses account_fetcher
Lou-Kamades Jan 11, 2024
f682b96
use client helpers for deriving health_check account_metas
Lou-Kamades Jan 11, 2024
a0d8fb2
add health_cache helper to mango client
Lou-Kamades Jan 11, 2024
7756368
add get_slot to account_fetcher
Lou-Kamades Jan 11, 2024
b70d3cb
lint
Lou-Kamades Jan 11, 2024
8b4a024
Merge branch 'dev' into lou/fallback-oracles-client
Lou-Kamades Jan 18, 2024
c1e872c
cached account fetcher only fetches uncached accounts
Lou-Kamades Jan 18, 2024
deb3796
ensure keeper client does not use cached oracles for staleness checks
Lou-Kamades Jan 18, 2024
231b009
address minor review comments
Lou-Kamades Jan 18, 2024
e771fb8
create unique job keys for CachedAccountFetcher.fetch_multiple_accounts
Lou-Kamades Jan 20, 2024
af35d78
Merge branch 'dev' into lou/fallback-oracles-client
Lou-Kamades Jan 20, 2024
46be2eb
fmt
Lou-Kamades Jan 20, 2024
d655a09
improve hashing in CachedAccountFetcher.fetch_multiple_accounts
Lou-Kamades Jan 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions bin/liquidator/src/liquidate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::time::Duration;
use itertools::Itertools;
use mango_v4::health::{HealthCache, HealthType};
use mango_v4::state::{MangoAccountValue, PerpMarketIndex, Side, TokenIndex, QUOTE_TOKEN_INDEX};
use mango_v4_client::{chain_data, health_cache, MangoClient};
use mango_v4_client::{chain_data, MangoClient};
use solana_sdk::signature::Signature;

use futures::{stream, StreamExt, TryStreamExt};
Expand Down Expand Up @@ -155,10 +155,11 @@ impl<'a> LiquidateHelper<'a> {
.await
.context("getting liquidator account")?;
liqor.ensure_perp_position(*perp_market_index, QUOTE_TOKEN_INDEX)?;
let mut health_cache =
health_cache::new(&self.client.context, self.account_fetcher, &liqor)
.await
.context("health cache")?;
let mut health_cache = self
.client
.health_cache(&liqor, self.account_fetcher)
.await
.expect("always ok");
let quote_bank = self
.client
.first_bank(QUOTE_TOKEN_INDEX)
Expand Down Expand Up @@ -589,7 +590,8 @@ pub async fn maybe_liquidate_account(
let liqor_min_health_ratio = I80F48::from_num(config.min_health_ratio);

let account = account_fetcher.fetch_mango_account(pubkey)?;
let health_cache = health_cache::new(&mango_client.context, account_fetcher, &account)
let health_cache = mango_client
.health_cache(&account, account_fetcher)
.await
.context("creating health cache 1")?;
let maint_health = health_cache.health(HealthType::Maint);
Expand All @@ -607,7 +609,8 @@ pub async fn maybe_liquidate_account(
// This is -- unfortunately -- needed because the websocket streams seem to not
// be great at providing timely updates to the account data.
let account = account_fetcher.fetch_fresh_mango_account(pubkey).await?;
let health_cache = health_cache::new(&mango_client.context, account_fetcher, &account)
let health_cache = mango_client
.health_cache(&account, account_fetcher)
.await
.context("creating health cache 2")?;
if !health_cache.is_liquidatable() {
Expand Down
1 change: 1 addition & 0 deletions bin/liquidator/src/rebalance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,7 @@ impl Rebalancer {
};
let counters = perp_pnl::fetch_top(
&self.mango_client.context,
&self.mango_client.client.fallback_oracle_config,
self.account_fetcher.as_ref(),
perp_position.market_index,
direction,
Expand Down
10 changes: 7 additions & 3 deletions bin/liquidator/src/trigger_tcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use mango_v4::{
i80f48::ClampToInt,
state::{Bank, MangoAccountValue, TokenConditionalSwap, TokenIndex},
};
use mango_v4_client::{chain_data, health_cache, jupiter, MangoClient, TransactionBuilder};
use mango_v4_client::{chain_data, jupiter, MangoClient, TransactionBuilder};

use anyhow::Context as AnyhowContext;
use solana_sdk::{signature::Signature, signer::Signer};
Expand Down Expand Up @@ -666,7 +666,9 @@ impl Context {
tcs_id: u64,
) -> anyhow::Result<Option<PreparedExecution>> {
let fetcher = self.account_fetcher.as_ref();
let health_cache = health_cache::new(&self.mango_client.context, fetcher, liqee_old)
let health_cache = self
.mango_client
.health_cache(liqee_old, fetcher)
.await
.context("creating health cache 1")?;
if health_cache.is_liquidatable() {
Expand All @@ -685,7 +687,9 @@ impl Context {
return Ok(None);
}

let health_cache = health_cache::new(&self.mango_client.context, fetcher, &liqee)
let health_cache = self
.mango_client
.health_cache(&liqee, fetcher)
.await
.context("creating health cache 2")?;
if health_cache.is_liquidatable() {
Expand Down
11 changes: 9 additions & 2 deletions bin/service-mango-pnl/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ use fixed::types::I80F48;
use mango_feeds_connector::metrics::*;
use mango_v4::state::{MangoAccount, MangoAccountValue, PerpMarketIndex};
use mango_v4_client::{
chain_data, health_cache, AccountFetcher, Client, MangoGroupContext, TransactionBuilderConfig,
chain_data, health_cache, AccountFetcher, Client, FallbackOracleConfig, MangoGroupContext,
TransactionBuilderConfig,
};
use solana_sdk::commitment_config::CommitmentConfig;
use solana_sdk::{account::ReadableAccount, signature::Keypair};
Expand Down Expand Up @@ -52,7 +53,13 @@ async fn compute_pnl(
account_fetcher: Arc<impl AccountFetcher>,
account: &MangoAccountValue,
) -> anyhow::Result<Vec<(PerpMarketIndex, I80F48)>> {
let health_cache = health_cache::new(&context, account_fetcher.as_ref(), account).await?;
let health_cache = health_cache::new(
&context,
&FallbackOracleConfig::Dynamic,
account_fetcher.as_ref(),
account,
)
.await?;

let pnls = account
.active_perp_positions()
Expand Down
19 changes: 11 additions & 8 deletions bin/settler/src/settle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ use mango_v4::accounts_zerocopy::KeyedAccountSharedData;
use mango_v4::health::HealthType;
use mango_v4::state::{OracleAccountInfos, PerpMarket, PerpMarketIndex};
use mango_v4_client::{
chain_data, health_cache, prettify_solana_client_error, MangoClient, PreparedInstructions,
TransactionBuilder,
chain_data, prettify_solana_client_error, MangoClient, PreparedInstructions, TransactionBuilder,
};
use solana_sdk::address_lookup_table_account::AddressLookupTableAccount;
use solana_sdk::commitment_config::CommitmentConfig;
Expand Down Expand Up @@ -114,7 +113,8 @@ impl SettlementState {
continue;
}

let health_cache = health_cache::new(&mango_client.context, account_fetcher, &account)
let health_cache = mango_client
.health_cache(&account, account_fetcher)
.await
.context("creating health cache")?;
let liq_end_health = health_cache.health(HealthType::LiquidationEnd);
Expand Down Expand Up @@ -311,11 +311,14 @@ impl<'a> SettleBatchProcessor<'a> {
) -> anyhow::Result<Option<Signature>> {
let a_value = self.account_fetcher.fetch_mango_account(&account_a)?;
let b_value = self.account_fetcher.fetch_mango_account(&account_b)?;
let new_ixs = self.mango_client.perp_settle_pnl_instruction(
self.perp_market_index,
(&account_a, &a_value),
(&account_b, &b_value),
)?;
let new_ixs = self
.mango_client
.perp_settle_pnl_instruction(
self.perp_market_index,
(&account_a, &a_value),
(&account_b, &b_value),
)
.await?;
let previous = self.instructions.clone();
self.instructions.append(new_ixs.clone());

Expand Down
18 changes: 11 additions & 7 deletions bin/settler/src/tcs_start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,18 @@ 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 new_token_pos_indices = startable_chunk
.iter()
.map(|(_, _, ti)| *ti)
.unique()
.collect_vec();
for token_index in new_token_pos_indices {
let mint = mango_client.context.token(token_index).mint;
instructions.append(mango_client.token_withdraw_instructions(
&liqor_account,
mint,
u64::MAX,
false,
)?);
let ix = mango_client
.token_withdraw_instructions(&liqor_account, mint, u64::MAX, false)
.await?;

instructions.append(ix)
}

let txsig = match mango_client
Expand Down
31 changes: 31 additions & 0 deletions lib/client/src/account_fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use solana_sdk::pubkey::Pubkey;

use mango_v4::state::MangoAccountValue;

use crate::gpa;

#[async_trait::async_trait]
pub trait AccountFetcher: Sync + Send {
async fn fetch_raw_account(&self, address: &Pubkey) -> anyhow::Result<AccountSharedData>;
Expand All @@ -29,6 +31,13 @@ pub trait AccountFetcher: Sync + Send {
program: &Pubkey,
discriminator: [u8; 8],
) -> anyhow::Result<Vec<(Pubkey, AccountSharedData)>>;

async fn fetch_multiple_accounts(
&self,
keys: &[Pubkey],
) -> anyhow::Result<Vec<(Pubkey, AccountSharedData)>>;

async fn get_slot(&self) -> anyhow::Result<u64>;
}

// Can't be in the trait, since then it would no longer be object-safe...
Expand Down Expand Up @@ -100,6 +109,17 @@ impl AccountFetcher for RpcAccountFetcher {
.map(|(pk, acc)| (pk, acc.into()))
.collect::<Vec<_>>())
}

async fn fetch_multiple_accounts(
&self,
keys: &[Pubkey],
) -> anyhow::Result<Vec<(Pubkey, AccountSharedData)>> {
gpa::fetch_multiple_accounts(&self.rpc, keys).await
}

async fn get_slot(&self) -> anyhow::Result<u64> {
Ok(self.rpc.get_slot().await?)
}
}

struct CoalescedAsyncJob<Key, Output> {
Expand Down Expand Up @@ -261,4 +281,15 @@ impl<T: AccountFetcher + 'static> AccountFetcher for CachedAccountFetcher<T> {
)),
}
}

async fn fetch_multiple_accounts(
&self,
keys: &[Pubkey],
) -> anyhow::Result<Vec<(Pubkey, AccountSharedData)>> {
self.fetcher.fetch_multiple_accounts(keys).await
ckamm marked this conversation as resolved.
Show resolved Hide resolved
}

async fn get_slot(&self) -> anyhow::Result<u64> {
self.fetcher.get_slot().await
}
}
54 changes: 50 additions & 4 deletions lib/client/src/chain_data_fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use anchor_lang::Discriminator;

use fixed::types::I80F48;
use mango_v4::accounts_zerocopy::{KeyedAccountSharedData, LoadZeroCopy};
use mango_v4::state::{Bank, MangoAccount, MangoAccountValue, OracleAccountInfos};
use mango_v4::state::{
pyth_mainnet_sol_oracle, pyth_mainnet_usdc_oracle, Bank, MangoAccount, MangoAccountValue,
OracleAccountInfos,
};

use anyhow::Context;

Expand Down Expand Up @@ -64,12 +67,34 @@ impl AccountFetcher {

pub fn fetch_bank_and_price(&self, bank: &Pubkey) -> anyhow::Result<(Bank, I80F48)> {
let bank: Bank = self.fetch(bank)?;
let oracle = self.fetch_raw(&bank.oracle)?;
let oracle_acc = &KeyedAccountSharedData::new(bank.oracle, oracle.into());
let price = bank.oracle_price(&OracleAccountInfos::from_reader(oracle_acc), None)?;
let oracle_data = self.fetch_raw(&bank.oracle)?;
let oracle = &KeyedAccountSharedData::new(bank.oracle, oracle_data.into());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like oracle could also be fetched with the new helper?


let fallback_opt = self.fetch_keyed_account_data(bank.fallback_oracle)?;
let sol_opt = self.fetch_keyed_account_data(pyth_mainnet_sol_oracle::ID)?;
let usdc_opt = self.fetch_keyed_account_data(pyth_mainnet_usdc_oracle::ID)?;

let oracle_acc_infos = OracleAccountInfos {
oracle,
fallback_opt: fallback_opt.as_ref(),
usdc_opt: usdc_opt.as_ref(),
sol_opt: sol_opt.as_ref(),
};
let price = bank.oracle_price(&oracle_acc_infos, None)?;
Ok((bank, price))
}

#[inline(always)]
fn fetch_keyed_account_data(
&self,
key: Pubkey,
) -> anyhow::Result<Option<KeyedAccountSharedData>> {
Ok(self
.fetch_raw(&key)
.ok()
.map(|data| KeyedAccountSharedData::new(key, data)))
}

pub fn fetch_bank_price(&self, bank: &Pubkey) -> anyhow::Result<I80F48> {
self.fetch_bank_and_price(bank).map(|(_, p)| p)
}
Expand Down Expand Up @@ -217,4 +242,25 @@ impl crate::AccountFetcher for AccountFetcher {
})
.collect::<Vec<_>>())
}

async fn fetch_multiple_accounts(
&self,
keys: &[Pubkey],
) -> anyhow::Result<Vec<(Pubkey, AccountSharedData)>> {
let chain_data = self.chain_data.read().unwrap();
Ok(chain_data
.iter_accounts()
Lou-Kamades marked this conversation as resolved.
Show resolved Hide resolved
.filter_map(|(pk, data)| {
if !keys.contains(pk) {
return None;
}
Some((*pk, data.account.clone()))
})
.collect::<Vec<_>>())
}

async fn get_slot(&self) -> anyhow::Result<u64> {
let chain_data = self.chain_data.read().unwrap();
Ok(chain_data.newest_processed_slot())
}
}
Loading
Loading