diff --git a/client/src/account_fetcher.rs b/client/src/account_fetcher.rs index 88bec46b6..7061cda79 100644 --- a/client/src/account_fetcher.rs +++ b/client/src/account_fetcher.rs @@ -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; + fn fetch_raw_account(&self, address: &Pubkey) -> anyhow::Result; + fn fetch_program_accounts( + &self, + program: &Pubkey, + discriminator: [u8; 8], + ) -> anyhow::Result>; } // Can't be in the trait, since then it would no longer be object-safe... pub fn account_fetcher_fetch_anchor_account( fetcher: &dyn AccountFetcher, - address: Pubkey, + address: &Pubkey, ) -> anyhow::Result { 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( // 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 { 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 { + fn fetch_raw_account(&self, address: &Pubkey) -> anyhow::Result { 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> { + 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::>()) + } +} + +struct AccountCache { + accounts: HashMap, + keys_for_program_and_discriminator: HashMap<(Pubkey, [u8; 8]), Vec>, +} + +impl AccountCache { + fn clear(&mut self) { + self.accounts.clear(); + self.keys_for_program_and_discriminator.clear(); } } pub struct CachedAccountFetcher { fetcher: T, - cache: Mutex>, + cache: Mutex, } impl CachedAccountFetcher { 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 CachedAccountFetcher { } impl AccountFetcher for CachedAccountFetcher { - fn fetch_raw_account(&self, address: Pubkey) -> anyhow::Result { + fn fetch_raw_account(&self, address: &Pubkey) -> anyhow::Result { 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> { + 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::>()); + } + 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) + } } diff --git a/client/src/chain_data.rs b/client/src/chain_data.rs index 1241bd274..e875ca659 100644 --- a/client/src/chain_data.rs +++ b/client/src/chain_data.rs @@ -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 { + 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() } diff --git a/client/src/chain_data_fetcher.rs b/client/src/chain_data_fetcher.rs index 26f7f8221..caabb322e 100644 --- a/client/src/chain_data_fetcher.rs +++ b/client/src/chain_data_fetcher.rs @@ -138,7 +138,31 @@ impl AccountFetcher { } impl crate::AccountFetcher for AccountFetcher { - fn fetch_raw_account(&self, address: Pubkey) -> anyhow::Result { - self.fetch_raw(&address).map(|a| a.into()) + fn fetch_raw_account( + &self, + address: &Pubkey, + ) -> anyhow::Result { + self.fetch_raw(&address) + } + + fn fetch_program_accounts( + &self, + program: &Pubkey, + discriminator: [u8; 8], + ) -> anyhow::Result> { + 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::>()) } } diff --git a/client/src/client.rs b/client/src/client.rs index 0170f7bed..910c7df44 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -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 { 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 { - 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 { 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 { 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::()], ); @@ -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 { + 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 { - 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 { - 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::( &*self.account_fetcher, - self.context.group, + &self.context.group, )?; self.program() diff --git a/client/src/health_cache.rs b/client/src/health_cache.rs new file mode 100644 index 000000000..03b24e58b --- /dev/null +++ b/client/src/health_cache.rs @@ -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 { + 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::>>()?; + + 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") +} diff --git a/client/src/lib.rs b/client/src/lib.rs index 5dcedaef8..eef1b25aa 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -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; diff --git a/client/src/perp_pnl.rs b/client/src/perp_pnl.rs new file mode 100644 index 000000000..d69063ecc --- /dev/null +++ b/client/src/perp_pnl.rs @@ -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> { + let perp_market = + account_fetcher_fetch_anchor_account::(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::>(); + + // 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()) +} diff --git a/liquidator/src/account_shared_data.rs b/liquidator/src/account_shared_data.rs deleted file mode 100644 index c6e7f534a..000000000 --- a/liquidator/src/account_shared_data.rs +++ /dev/null @@ -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 - } -} diff --git a/liquidator/src/liquidate.rs b/liquidator/src/liquidate.rs index 1572d2614..02a9f01d5 100644 --- a/liquidator/src/liquidate.rs +++ b/liquidator/src/liquidate.rs @@ -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 { - 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::>>()?; - - 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::(&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, diff --git a/liquidator/src/main.rs b/liquidator/src/main.rs index faf84ce1c..77120fd90 100644 --- a/liquidator/src/main.rs +++ b/liquidator/src/main.rs @@ -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; diff --git a/liquidator/src/rebalance.rs b/liquidator/src/rebalance.rs index 9cdcbb76d..3a969139e 100644 --- a/liquidator/src/rebalance.rs +++ b/liquidator/src/rebalance.rs @@ -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 { - 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(), diff --git a/programs/mango-v4/src/accounts_zerocopy.rs b/programs/mango-v4/src/accounts_zerocopy.rs index e1fb765f4..6ec30ae2d 100644 --- a/programs/mango-v4/src/accounts_zerocopy.rs +++ b/programs/mango-v4/src/accounts_zerocopy.rs @@ -110,6 +110,63 @@ impl 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. //