client: fetch top settle pnl counterparties

This commit is contained in:
Christian Kamm 2022-09-23 11:59:18 +02:00
parent 9cbc352197
commit 11daf4d0eb
12 changed files with 385 additions and 105 deletions

View File

@ -7,22 +7,27 @@ use anchor_client::ClientError;
use anchor_lang::AccountDeserialize;
use solana_client::rpc_client::RpcClient;
use solana_sdk::account::Account;
use solana_sdk::account::{AccountSharedData, ReadableAccount};
use solana_sdk::pubkey::Pubkey;
use mango_v4::state::MangoAccountValue;
pub trait AccountFetcher: Sync + Send {
fn fetch_raw_account(&self, address: Pubkey) -> anyhow::Result<Account>;
fn fetch_raw_account(&self, address: &Pubkey) -> anyhow::Result<AccountSharedData>;
fn fetch_program_accounts(
&self,
program: &Pubkey,
discriminator: [u8; 8],
) -> anyhow::Result<Vec<(Pubkey, AccountSharedData)>>;
}
// Can't be in the trait, since then it would no longer be object-safe...
pub fn account_fetcher_fetch_anchor_account<T: AccountDeserialize>(
fetcher: &dyn AccountFetcher,
address: Pubkey,
address: &Pubkey,
) -> anyhow::Result<T> {
let account = fetcher.fetch_raw_account(address)?;
let mut data: &[u8] = &account.data;
let mut data: &[u8] = &account.data();
T::try_deserialize(&mut data)
.with_context(|| format!("deserializing anchor account {}", address))
}
@ -30,10 +35,10 @@ pub fn account_fetcher_fetch_anchor_account<T: AccountDeserialize>(
// Can't be in the trait, since then it would no longer be object-safe...
pub fn account_fetcher_fetch_mango_account(
fetcher: &dyn AccountFetcher,
address: Pubkey,
address: &Pubkey,
) -> anyhow::Result<MangoAccountValue> {
let account = fetcher.fetch_raw_account(address)?;
let data: &[u8] = &account.data;
let data: &[u8] = &account.data();
MangoAccountValue::from_bytes(&data[8..])
.with_context(|| format!("deserializing mango account {}", address))
}
@ -43,26 +48,73 @@ pub struct RpcAccountFetcher {
}
impl AccountFetcher for RpcAccountFetcher {
fn fetch_raw_account(&self, address: Pubkey) -> anyhow::Result<Account> {
fn fetch_raw_account(&self, address: &Pubkey) -> anyhow::Result<AccountSharedData> {
self.rpc
.get_account_with_commitment(&address, self.rpc.commitment())
.with_context(|| format!("fetch account {}", address))?
.get_account_with_commitment(address, self.rpc.commitment())
.with_context(|| format!("fetch account {}", *address))?
.value
.ok_or(ClientError::AccountNotFound)
.with_context(|| format!("fetch account {}", address))
.with_context(|| format!("fetch account {}", *address))
.map(Into::into)
}
fn fetch_program_accounts(
&self,
program: &Pubkey,
discriminator: [u8; 8],
) -> anyhow::Result<Vec<(Pubkey, AccountSharedData)>> {
use solana_account_decoder::UiAccountEncoding;
use solana_client::rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig};
use solana_client::rpc_filter::{
Memcmp, MemcmpEncodedBytes, MemcmpEncoding, RpcFilterType,
};
let config = RpcProgramAccountsConfig {
filters: Some(vec![RpcFilterType::Memcmp(Memcmp {
offset: 0,
bytes: MemcmpEncodedBytes::Bytes(discriminator.to_vec()),
encoding: Some(MemcmpEncoding::Binary),
})]),
account_config: RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
commitment: Some(self.rpc.commitment()),
..RpcAccountInfoConfig::default()
},
with_context: Some(true),
};
let accs = self.rpc.get_program_accounts_with_config(program, config)?;
// convert Account -> AccountSharedData
Ok(accs
.into_iter()
.map(|(pk, acc)| (pk, acc.into()))
.collect::<Vec<_>>())
}
}
struct AccountCache {
accounts: HashMap<Pubkey, AccountSharedData>,
keys_for_program_and_discriminator: HashMap<(Pubkey, [u8; 8]), Vec<Pubkey>>,
}
impl AccountCache {
fn clear(&mut self) {
self.accounts.clear();
self.keys_for_program_and_discriminator.clear();
}
}
pub struct CachedAccountFetcher<T: AccountFetcher> {
fetcher: T,
cache: Mutex<HashMap<Pubkey, Account>>,
cache: Mutex<AccountCache>,
}
impl<T: AccountFetcher> CachedAccountFetcher<T> {
pub fn new(fetcher: T) -> Self {
Self {
fetcher,
cache: Mutex::new(HashMap::new()),
cache: Mutex::new(AccountCache {
accounts: HashMap::new(),
keys_for_program_and_discriminator: HashMap::new(),
}),
}
}
@ -73,13 +125,38 @@ impl<T: AccountFetcher> CachedAccountFetcher<T> {
}
impl<T: AccountFetcher> AccountFetcher for CachedAccountFetcher<T> {
fn fetch_raw_account(&self, address: Pubkey) -> anyhow::Result<Account> {
fn fetch_raw_account(&self, address: &Pubkey) -> anyhow::Result<AccountSharedData> {
let mut cache = self.cache.lock().unwrap();
if let Some(account) = cache.get(&address) {
if let Some(account) = cache.accounts.get(address) {
return Ok(account.clone());
}
let account = self.fetcher.fetch_raw_account(address)?;
cache.insert(address, account.clone());
cache.accounts.insert(*address, account.clone());
Ok(account)
}
fn fetch_program_accounts(
&self,
program: &Pubkey,
discriminator: [u8; 8],
) -> anyhow::Result<Vec<(Pubkey, AccountSharedData)>> {
let cache_key = (*program, discriminator);
let mut cache = self.cache.lock().unwrap();
if let Some(accounts) = cache.keys_for_program_and_discriminator.get(&cache_key) {
return Ok(accounts
.iter()
.map(|pk| (*pk, cache.accounts.get(&pk).unwrap().clone()))
.collect::<Vec<_>>());
}
let accounts = self
.fetcher
.fetch_program_accounts(program, discriminator)?;
cache
.keys_for_program_and_discriminator
.insert(cache_key, accounts.iter().map(|(pk, _)| *pk).collect());
for (pk, acc) in accounts.iter() {
cache.accounts.insert(*pk, acc.clone());
}
Ok(accounts)
}
}

View File

@ -224,6 +224,16 @@ impl ChainData {
.ok_or_else(|| anyhow::anyhow!("account {} has no live data", pubkey))
}
pub fn iter_accounts<'a>(&'a self) -> impl Iterator<Item = (&'a Pubkey, &'a AccountAndSlot)> {
self.accounts.iter().filter_map(|(pk, writes)| {
writes
.iter()
.rev()
.find(|w| self.is_account_write_live(w))
.map(|latest_write| (pk, latest_write))
})
}
pub fn slots_count(&self) -> usize {
self.slots.len()
}

View File

@ -138,7 +138,31 @@ impl AccountFetcher {
}
impl crate::AccountFetcher for AccountFetcher {
fn fetch_raw_account(&self, address: Pubkey) -> anyhow::Result<solana_sdk::account::Account> {
self.fetch_raw(&address).map(|a| a.into())
fn fetch_raw_account(
&self,
address: &Pubkey,
) -> anyhow::Result<solana_sdk::account::AccountSharedData> {
self.fetch_raw(&address)
}
fn fetch_program_accounts(
&self,
program: &Pubkey,
discriminator: [u8; 8],
) -> anyhow::Result<Vec<(Pubkey, AccountSharedData)>> {
let chain_data = self.chain_data.read().unwrap();
Ok(chain_data
.iter_accounts()
.filter_map(|(pk, data)| {
if data.account.owner() != program {
return None;
}
let acc_data = data.account.data();
if acc_data.len() < 8 || acc_data[..8] != discriminator {
return None;
}
Some((*pk, data.account.clone()))
})
.collect::<Vec<_>>())
}
}

View File

@ -24,12 +24,13 @@ use solana_client::rpc_client::RpcClient;
use solana_sdk::signer::keypair;
use crate::account_fetcher::*;
use crate::context::{MangoGroupContext, PerpMarketContext, Serum3MarketContext, TokenContext};
use crate::context::{MangoGroupContext, Serum3MarketContext, TokenContext};
use crate::gpa::fetch_mango_accounts;
use crate::jupiter;
use crate::util::MyClone;
use anyhow::Context;
use solana_sdk::account::ReadableAccount;
use solana_sdk::instruction::{AccountMeta, Instruction};
use solana_sdk::signature::{Keypair, Signature};
use solana_sdk::sysvar;
@ -230,7 +231,7 @@ impl MangoClient {
) -> anyhow::Result<Self> {
let rpc = client.rpc();
let account_fetcher = Arc::new(CachedAccountFetcher::new(RpcAccountFetcher { rpc }));
let mango_account = account_fetcher_fetch_mango_account(&*account_fetcher, account)?;
let mango_account = account_fetcher_fetch_mango_account(&*account_fetcher, &account)?;
let group = mango_account.fixed.group;
if mango_account.fixed.owner != owner.pubkey() {
anyhow::bail!(
@ -290,12 +291,12 @@ impl MangoClient {
}
pub fn mango_account(&self) -> anyhow::Result<MangoAccountValue> {
account_fetcher_fetch_mango_account(&*self.account_fetcher, self.mango_account_address)
account_fetcher_fetch_mango_account(&*self.account_fetcher, &self.mango_account_address)
}
pub fn first_bank(&self, token_index: TokenIndex) -> anyhow::Result<Bank> {
let bank_address = self.context.mint_info(token_index).first_bank();
account_fetcher_fetch_anchor_account(&*self.account_fetcher, bank_address)
account_fetcher_fetch_anchor_account(&*self.account_fetcher, &bank_address)
}
pub fn derive_health_check_remaining_account_metas(
@ -461,8 +462,8 @@ impl MangoClient {
) -> Result<pyth_sdk_solana::Price, anyhow::Error> {
let token_index = *self.context.token_indexes_by_name.get(token_name).unwrap();
let mint_info = self.context.mint_info(token_index);
let oracle_account = self.account_fetcher.fetch_raw_account(mint_info.oracle)?;
Ok(pyth_sdk_solana::load_price(&oracle_account.data).unwrap())
let oracle_account = self.account_fetcher.fetch_raw_account(&mint_info.oracle)?;
Ok(pyth_sdk_solana::load_price(&oracle_account.data()).unwrap())
}
//
@ -719,8 +720,8 @@ impl MangoClient {
.unwrap();
let account = self.mango_account()?;
let open_orders = account.serum3_orders(market_index).unwrap().open_orders;
let open_orders_bytes = self.account_fetcher.fetch_raw_account(open_orders)?.data;
let open_orders_acc = self.account_fetcher.fetch_raw_account(&open_orders)?;
let open_orders_bytes = open_orders_acc.data();
let open_orders_data: &serum_dex::state::OpenOrders = bytemuck::from_bytes(
&open_orders_bytes[5..5 + std::mem::size_of::<serum_dex::state::OpenOrders>()],
);
@ -838,11 +839,45 @@ impl MangoClient {
//
// Perps
//
fn perp_data_by_market_index(
pub fn perp_settle_pnl(
&self,
market_index: PerpMarketIndex,
) -> Result<&PerpMarketContext, ClientError> {
Ok(self.context.perp_markets.get(&market_index).unwrap())
account_a: &Pubkey,
account_b: (&Pubkey, &MangoAccountValue),
) -> anyhow::Result<Signature> {
let perp = self.context.perp(market_index);
let settlement_token = self.context.token(0);
let health_remaining_ams = self
.context
.derive_health_check_remaining_account_metas(account_b.1, vec![], false)
.unwrap();
self.program()
.request()
.instruction(Instruction {
program_id: mango_v4::id(),
accounts: {
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::PerpSettlePnl {
group: self.group(),
perp_market: perp.address,
account_a: *account_a,
account_b: *account_b.0,
oracle: perp.market.oracle,
quote_bank: settlement_token.mint_info.first_bank(),
},
None,
);
ams.extend(health_remaining_ams.into_iter());
ams
},
data: anchor_lang::InstructionData::data(&mango_v4::instruction::PerpSettlePnl {
max_settle_amount: u64::MAX,
}),
})
.send()
.map_err(prettify_client_error)
}
pub fn perp_liq_force_cancel_orders(
@ -850,7 +885,7 @@ impl MangoClient {
liqee: (&Pubkey, &MangoAccountValue),
market_index: PerpMarketIndex,
) -> anyhow::Result<Signature> {
let perp = self.perp_data_by_market_index(market_index)?;
let perp = self.context.perp(market_index);
let health_remaining_ams = self
.context
@ -890,7 +925,7 @@ impl MangoClient {
market_index: PerpMarketIndex,
max_base_transfer: i64,
) -> anyhow::Result<Signature> {
let perp = self.perp_data_by_market_index(market_index)?;
let perp = self.context.perp(market_index);
let health_remaining_ams = self
.context
@ -1002,7 +1037,7 @@ impl MangoClient {
let group = account_fetcher_fetch_anchor_account::<Group>(
&*self.account_fetcher,
self.context.group,
&self.context.group,
)?;
self.program()

View File

@ -0,0 +1,33 @@
use crate::{AccountFetcher, MangoGroupContext};
use anyhow::Context;
use mango_v4::accounts_zerocopy::KeyedAccountSharedData;
use mango_v4::state::{FixedOrderAccountRetriever, HealthCache, MangoAccountValue};
pub fn new(
context: &MangoGroupContext,
account_fetcher: &impl AccountFetcher,
account: &MangoAccountValue,
) -> anyhow::Result<HealthCache> {
let active_token_len = account.active_token_positions().count();
let active_perp_len = account.active_perp_positions().count();
let metas = context.derive_health_check_remaining_account_metas(account, vec![], false)?;
let accounts = metas
.iter()
.map(|meta| {
Ok(KeyedAccountSharedData::new(
meta.pubkey,
account_fetcher.fetch_raw_account(&meta.pubkey)?,
))
})
.collect::<anyhow::Result<Vec<_>>>()?;
let retriever = FixedOrderAccountRetriever {
ais: accounts,
n_banks: active_token_len,
n_perps: active_perp_len,
begin_perp: active_token_len * 2,
begin_serum3: active_token_len * 2 + active_perp_len,
};
mango_v4::state::new_health_cache(&account.borrow(), &retriever).context("make health cache")
}

View File

@ -9,5 +9,7 @@ mod chain_data_fetcher;
mod client;
mod context;
mod gpa;
pub mod health_cache;
mod jupiter;
pub mod perp_pnl;
mod util;

103
client/src/perp_pnl.rs Normal file
View File

@ -0,0 +1,103 @@
use anchor_lang::prelude::*;
use anchor_lang::Discriminator;
use fixed::types::I80F48;
use solana_sdk::account::ReadableAccount;
use crate::*;
use mango_v4::accounts_zerocopy::KeyedAccountSharedData;
use mango_v4::state::*;
#[derive(Debug, PartialEq)]
pub enum Direction {
MaxPositive,
MaxNegative,
}
/// Returns up to `count` accounts with highest abs pnl (by `direction`) in descending order.
pub fn fetch_top(
context: &crate::context::MangoGroupContext,
account_fetcher: &impl AccountFetcher,
perp_market_index: PerpMarketIndex,
perp_market_address: &Pubkey,
direction: Direction,
count: usize,
) -> anyhow::Result<Vec<(Pubkey, MangoAccountValue, I80F48)>> {
let perp_market =
account_fetcher_fetch_anchor_account::<PerpMarket>(account_fetcher, perp_market_address)?;
let oracle_acc = account_fetcher.fetch_raw_account(&perp_market.oracle)?;
let oracle_price =
perp_market.oracle_price(&KeyedAccountSharedData::new(perp_market.oracle, oracle_acc))?;
let accounts =
account_fetcher.fetch_program_accounts(&mango_v4::id(), MangoAccount::discriminator())?;
let mut accounts_pnl = accounts
.iter()
.filter_map(|(pk, acc)| {
let data = acc.data();
let mango_acc = MangoAccountValue::from_bytes(&data[8..]);
if mango_acc.is_err() {
return None;
}
let mango_acc = mango_acc.unwrap();
let perp_pos = mango_acc.perp_position(perp_market_index);
if perp_pos.is_err() {
return None;
}
let perp_pos = perp_pos.unwrap();
let pnl = perp_pos.base_position_native(&perp_market) * oracle_price
+ perp_pos.quote_position_native();
if pnl >= 0 && direction == Direction::MaxNegative
|| pnl <= 0 && direction == Direction::MaxPositive
{
return None;
}
Some((*pk, mango_acc, pnl))
})
.collect::<Vec<_>>();
// Sort the top accounts to the front
match direction {
Direction::MaxPositive => {
accounts_pnl.sort_by(|a, b| b.2.cmp(&a.2));
}
Direction::MaxNegative => {
accounts_pnl.sort_by(|a, b| a.2.cmp(&b.2));
}
}
// Negative pnl needs to be limited by spot_health.
// We're doing it in a second step, because it's pretty expensive and we don't
// want to run this for all accounts.
if direction == Direction::MaxNegative {
let mut stable = 0;
for i in 0..accounts_pnl.len() {
let (_, acc, pnl) = &accounts_pnl[i];
let next_pnl = if i + 1 < accounts_pnl.len() {
accounts_pnl[i + 1].2
} else {
I80F48::ZERO
};
let spot_health = crate::health_cache::new(context, account_fetcher, &acc)?
.spot_health(HealthType::Maint);
let settleable_pnl = if spot_health > 0 && !acc.being_liquidated() {
(*pnl).max(-spot_health)
} else {
I80F48::ZERO
};
accounts_pnl[i].2 = settleable_pnl;
// if the ordering was unchanged `count` times we know we have the top `count` accounts
if settleable_pnl <= next_pnl {
stable += 1;
if stable >= count {
break;
}
}
}
accounts_pnl.sort_by(|a, b| a.2.cmp(&b.2));
}
// return highest abs pnl accounts
Ok(accounts_pnl[0..count].to_vec())
}

View File

@ -1,30 +0,0 @@
use mango_v4::accounts_zerocopy::{AccountReader, KeyedAccountReader};
use solana_sdk::{account::AccountSharedData, pubkey::Pubkey};
#[derive(Clone)]
pub struct KeyedAccountSharedData {
pub key: Pubkey,
pub data: AccountSharedData,
}
impl KeyedAccountSharedData {
pub fn new(key: Pubkey, data: AccountSharedData) -> Self {
Self { key, data }
}
}
impl AccountReader for KeyedAccountSharedData {
fn owner(&self) -> &Pubkey {
AccountReader::owner(&self.data)
}
fn data(&self) -> &[u8] {
AccountReader::data(&self.data)
}
}
impl KeyedAccountReader for KeyedAccountSharedData {
fn key(&self) -> &Pubkey {
&self.key
}
}

View File

@ -1,11 +1,9 @@
use std::time::Duration;
use crate::account_shared_data::KeyedAccountSharedData;
use client::{chain_data, AccountFetcher, MangoClient, MangoClientError, MangoGroupContext};
use client::{chain_data, health_cache, AccountFetcher, MangoClient, MangoClientError};
use mango_v4::accounts_zerocopy::KeyedAccountSharedData;
use mango_v4::state::{
new_health_cache, Bank, FixedOrderAccountRetriever, HealthCache, HealthType, MangoAccountValue,
PerpMarketIndex, Serum3Orders, Side, TokenIndex, QUOTE_TOKEN_INDEX,
Bank, HealthType, PerpMarketIndex, Serum3Orders, Side, TokenIndex, QUOTE_TOKEN_INDEX,
};
use itertools::Itertools;
@ -17,35 +15,6 @@ pub struct Config {
pub refresh_timeout: Duration,
}
pub fn new_health_cache_(
context: &MangoGroupContext,
account_fetcher: &chain_data::AccountFetcher,
account: &MangoAccountValue,
) -> anyhow::Result<HealthCache> {
let active_token_len = account.active_token_positions().count();
let active_perp_len = account.active_perp_positions().count();
let metas = context.derive_health_check_remaining_account_metas(account, vec![], false)?;
let accounts = metas
.iter()
.map(|meta| {
Ok(KeyedAccountSharedData::new(
meta.pubkey,
account_fetcher.fetch_raw(&meta.pubkey)?,
))
})
.collect::<anyhow::Result<Vec<_>>>()?;
let retriever = FixedOrderAccountRetriever {
ais: accounts,
n_banks: active_token_len,
n_perps: active_perp_len,
begin_perp: active_token_len * 2,
begin_serum3: active_token_len * 2 + active_perp_len,
};
new_health_cache(&account.borrow(), &retriever).context("make health cache")
}
pub fn jupiter_market_can_buy(
mango_client: &MangoClient,
token: TokenIndex,
@ -112,7 +81,7 @@ pub fn maybe_liquidate_account(
let account = account_fetcher.fetch_mango_account(pubkey)?;
let health_cache =
new_health_cache_(&mango_client.context, account_fetcher, &account).expect("always ok");
health_cache::new(&mango_client.context, account_fetcher, &account).expect("always ok");
let maint_health = health_cache.health(HealthType::Maint);
if !health_cache.is_liquidatable() {
return Ok(false);
@ -130,7 +99,7 @@ pub fn maybe_liquidate_account(
// be great at providing timely updates to the account data.
let account = account_fetcher.fetch_fresh_mango_account(pubkey)?;
let health_cache =
new_health_cache_(&mango_client.context, account_fetcher, &account).expect("always ok");
health_cache::new(&mango_client.context, account_fetcher, &account).expect("always ok");
if !health_cache.is_liquidatable() {
return Ok(false);
}
@ -145,7 +114,7 @@ pub fn maybe_liquidate_account(
.map(|token_position| {
let token = mango_client.context.token(token_position.token_index);
let bank = account_fetcher.fetch::<Bank>(&token.mint_info.first_bank())?;
let oracle = account_fetcher.fetch_raw_account(token.mint_info.oracle)?;
let oracle = account_fetcher.fetch_raw_account(&token.mint_info.oracle)?;
let price = bank.oracle_price(&KeyedAccountSharedData::new(
token.mint_info.oracle,
oracle.into(),
@ -163,7 +132,7 @@ pub fn maybe_liquidate_account(
let serum_force_cancels = account
.active_serum3_orders()
.map(|orders| {
let open_orders_account = account_fetcher.fetch_raw_account(orders.open_orders)?;
let open_orders_account = account_fetcher.fetch_raw_account(&orders.open_orders)?;
let open_orders = mango_v4::serum3_cpi::load_open_orders(&open_orders_account)?;
let can_force_cancel = open_orders.native_coin_total > 0
|| open_orders.native_pc_total > 0
@ -190,7 +159,7 @@ pub fn maybe_liquidate_account(
return Ok(None);
}
let perp = mango_client.context.perp(pp.market_index);
let oracle = account_fetcher.fetch_raw_account(perp.market.oracle)?;
let oracle = account_fetcher.fetch_raw_account(&perp.market.oracle)?;
let price = perp.market.oracle_price(&KeyedAccountSharedData::new(
perp.market.oracle,
oracle.into(),
@ -218,7 +187,7 @@ pub fn maybe_liquidate_account(
liqor.ensure_token_position(target)?;
let health_cache =
new_health_cache_(&mango_client.context, account_fetcher, &liqor).expect("always ok");
health_cache::new(&mango_client.context, account_fetcher, &liqor).expect("always ok");
let source_price = health_cache.token_info(source).unwrap().oracle_price;
let target_price = health_cache.token_info(target).unwrap().oracle_price;
@ -278,7 +247,7 @@ pub fn maybe_liquidate_account(
.fetch_fresh_mango_account(&mango_client.mango_account_address)
.context("getting liquidator account")?;
liqor.ensure_perp_position(*perp_market_index, QUOTE_TOKEN_INDEX)?;
let health_cache = new_health_cache_(&mango_client.context, account_fetcher, &liqor)
let health_cache = health_cache::new(&mango_client.context, account_fetcher, &liqor)
.expect("always ok");
health_cache.max_perp_for_health_ratio(
*perp_market_index,

View File

@ -13,7 +13,6 @@ use solana_sdk::commitment_config::CommitmentConfig;
use solana_sdk::pubkey::Pubkey;
use std::collections::HashSet;
pub mod account_shared_data;
pub mod liquidate;
pub mod metrics;
pub mod rebalance;

View File

@ -1,6 +1,7 @@
use crate::{account_shared_data::KeyedAccountSharedData, AnyhowWrap};
use crate::AnyhowWrap;
use client::{chain_data, AccountFetcher, MangoClient, TokenContext};
use mango_v4::accounts_zerocopy::KeyedAccountSharedData;
use mango_v4::state::{Bank, TokenIndex, TokenPosition, QUOTE_TOKEN_INDEX};
use {fixed::types::I80F48, solana_sdk::pubkey::Pubkey};
@ -43,7 +44,7 @@ impl TokenState {
bank: &Bank,
account_fetcher: &chain_data::AccountFetcher,
) -> anyhow::Result<I80F48> {
let oracle = account_fetcher.fetch_raw_account(token.mint_info.oracle)?;
let oracle = account_fetcher.fetch_raw_account(&token.mint_info.oracle)?;
bank.oracle_price(&KeyedAccountSharedData::new(
token.mint_info.oracle,
oracle.into(),

View File

@ -110,6 +110,63 @@ impl<T: solana_sdk::account::ReadableAccount> AccountReader for T {
}
}
#[cfg(feature = "solana-sdk")]
#[derive(Clone)]
pub struct KeyedAccount {
pub key: Pubkey,
pub account: solana_sdk::account::Account,
}
#[cfg(feature = "solana-sdk")]
impl AccountReader for KeyedAccount {
fn owner(&self) -> &Pubkey {
self.account.owner()
}
fn data(&self) -> &[u8] {
self.account.data()
}
}
#[cfg(feature = "solana-sdk")]
impl KeyedAccountReader for KeyedAccount {
fn key(&self) -> &Pubkey {
&self.key
}
}
#[cfg(feature = "solana-sdk")]
#[derive(Clone)]
pub struct KeyedAccountSharedData {
pub key: Pubkey,
pub data: solana_sdk::account::AccountSharedData,
}
#[cfg(feature = "solana-sdk")]
impl KeyedAccountSharedData {
pub fn new(key: Pubkey, data: solana_sdk::account::AccountSharedData) -> Self {
Self { key, data }
}
}
#[cfg(feature = "solana-sdk")]
impl AccountReader for KeyedAccountSharedData {
fn owner(&self) -> &Pubkey {
AccountReader::owner(&self.data)
}
fn data(&self) -> &[u8] {
AccountReader::data(&self.data)
}
}
#[cfg(feature = "solana-sdk")]
impl KeyedAccountReader for KeyedAccountSharedData {
fn key(&self) -> &Pubkey {
&self.key
}
}
//
// Common traits for loading from account data.
//