Make health/oracle functions usable from clients (#63)
* Make health/oracle functions usable from clients By creating a shared trait that is implementable for AccountInfo and AccountSharedData. * Health: fixup tests
This commit is contained in:
parent
a14f37362d
commit
b3568b7f3e
|
@ -0,0 +1,213 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_lang::ZeroCopy;
|
||||
use arrayref::array_ref;
|
||||
use std::cell::RefMut;
|
||||
use std::{cell::Ref, mem};
|
||||
|
||||
/// Functions should prefer to work with AccountReader where possible, to abstract over
|
||||
/// AccountInfo and AccountSharedData. That way the functions become usable in the program
|
||||
/// and in client code.
|
||||
// NOTE: would love to use solana's ReadableAccount, but that's in solana_sdk -- unavailable for programs
|
||||
// TODO: Maybe have KeyedAccountReader that also includes fn key() -> &Pukey?
|
||||
pub trait AccountReader {
|
||||
fn owner(&self) -> &Pubkey;
|
||||
fn data(&self) -> &[u8];
|
||||
}
|
||||
|
||||
/// A Ref to an AccountInfo - makes AccountInfo compatible with AccountReader
|
||||
pub struct AccountInfoRef<'a, 'info: 'a> {
|
||||
pub key: &'info Pubkey,
|
||||
pub owner: &'info Pubkey,
|
||||
pub data: Ref<'a, &'info mut [u8]>,
|
||||
}
|
||||
|
||||
impl<'a, 'info: 'a> AccountInfoRef<'a, 'info> {
|
||||
pub fn borrow(account_info: &'a AccountInfo<'info>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
key: account_info.key,
|
||||
owner: account_info.owner,
|
||||
data: account_info
|
||||
.data
|
||||
.try_borrow()
|
||||
.map_err(|_| ProgramError::AccountBorrowFailed)?,
|
||||
// Why is the following not acceptable?
|
||||
//data: account_info.try_borrow_data()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AccountInfoRefMut<'a, 'info: 'a> {
|
||||
pub key: &'info Pubkey,
|
||||
pub owner: &'info Pubkey,
|
||||
pub data: RefMut<'a, &'info mut [u8]>,
|
||||
}
|
||||
|
||||
impl<'a, 'info: 'a> AccountInfoRefMut<'a, 'info> {
|
||||
pub fn borrow(account_info: &'a AccountInfo<'info>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
key: account_info.key,
|
||||
owner: account_info.owner,
|
||||
data: account_info
|
||||
.data
|
||||
.try_borrow_mut()
|
||||
.map_err(|_| ProgramError::AccountBorrowFailed)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, 'a> AccountReader for AccountInfoRef<'info, 'a> {
|
||||
fn owner(&self) -> &Pubkey {
|
||||
self.owner
|
||||
}
|
||||
|
||||
fn data(&self) -> &[u8] {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, 'a> AccountReader for AccountInfoRefMut<'info, 'a> {
|
||||
fn owner(&self) -> &Pubkey {
|
||||
self.owner
|
||||
}
|
||||
|
||||
fn data(&self) -> &[u8] {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Common traits for loading from account data.
|
||||
//
|
||||
|
||||
pub trait LoadZeroCopy {
|
||||
/// Using AccountLoader forces a AccountInfo.clone() and then binds the loaded
|
||||
/// lifetime to the AccountLoader's lifetime. This function avoids both.
|
||||
/// It checks the account owner and discriminator, then casts the data.
|
||||
fn load<T: ZeroCopy + Owner>(&self) -> Result<&T>;
|
||||
|
||||
/// Same as load(), but doesn't check the discriminator or owner.
|
||||
fn load_fully_unchecked<T: ZeroCopy + Owner>(&self) -> Result<&T>;
|
||||
}
|
||||
|
||||
pub trait LoadMutZeroCopy {
|
||||
/// Same as load(), but mut
|
||||
fn load_mut<T: ZeroCopy + Owner>(&mut self) -> Result<&mut T>;
|
||||
|
||||
/// Same as load_fully_unchecked(), but mut
|
||||
fn load_mut_fully_unchecked<T: ZeroCopy + Owner>(&mut self) -> Result<&mut T>;
|
||||
}
|
||||
|
||||
pub trait LoadZeroCopyRef {
|
||||
/// Using AccountLoader forces a AccountInfo.clone() and then binds the loaded
|
||||
/// lifetime to the AccountLoader's lifetime. This function avoids both.
|
||||
/// It checks the account owner and discriminator, then casts the data.
|
||||
fn load<T: ZeroCopy + Owner>(&self) -> Result<Ref<T>>;
|
||||
|
||||
/// Same as load(), but doesn't check the discriminator or owner.
|
||||
fn load_fully_unchecked<T: ZeroCopy + Owner>(&self) -> Result<Ref<T>>;
|
||||
}
|
||||
|
||||
pub trait LoadMutZeroCopyRef {
|
||||
/// Same as load(), but mut
|
||||
fn load_mut<T: ZeroCopy + Owner>(&self) -> Result<RefMut<T>>;
|
||||
|
||||
/// Same as load_fully_unchecked(), but mut
|
||||
fn load_mut_fully_unchecked<T: ZeroCopy + Owner>(&self) -> Result<RefMut<T>>;
|
||||
}
|
||||
|
||||
impl<A: AccountReader> LoadZeroCopy for A {
|
||||
fn load<T: ZeroCopy + Owner>(&self) -> Result<&T> {
|
||||
if self.owner() != &T::owner() {
|
||||
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
|
||||
}
|
||||
|
||||
let data = self.data();
|
||||
let disc_bytes = array_ref![data, 0, 8];
|
||||
if disc_bytes != &T::discriminator() {
|
||||
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
||||
}
|
||||
|
||||
Ok(bytemuck::from_bytes(&data[8..mem::size_of::<T>() + 8]))
|
||||
}
|
||||
|
||||
fn load_fully_unchecked<T: ZeroCopy + Owner>(&self) -> Result<&T> {
|
||||
Ok(bytemuck::from_bytes(
|
||||
&self.data()[8..mem::size_of::<T>() + 8],
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, 'a> LoadMutZeroCopy for AccountInfoRefMut<'info, 'a> {
|
||||
fn load_mut<T: ZeroCopy + Owner>(&mut self) -> Result<&mut T> {
|
||||
if self.owner != &T::owner() {
|
||||
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
|
||||
}
|
||||
|
||||
let disc_bytes = array_ref![self.data, 0, 8];
|
||||
if disc_bytes != &T::discriminator() {
|
||||
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
||||
}
|
||||
|
||||
Ok(bytemuck::from_bytes_mut(
|
||||
&mut self.data[8..mem::size_of::<T>() + 8],
|
||||
))
|
||||
}
|
||||
|
||||
fn load_mut_fully_unchecked<T: ZeroCopy + Owner>(&mut self) -> Result<&mut T> {
|
||||
Ok(bytemuck::from_bytes_mut(
|
||||
&mut self.data[8..mem::size_of::<T>() + 8],
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info> LoadZeroCopyRef for AccountInfo<'info> {
|
||||
fn load<T: ZeroCopy + Owner>(&self) -> Result<Ref<T>> {
|
||||
if self.owner != &T::owner() {
|
||||
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
|
||||
}
|
||||
|
||||
let data = self.try_borrow_data()?;
|
||||
|
||||
let disc_bytes = array_ref![data, 0, 8];
|
||||
if disc_bytes != &T::discriminator() {
|
||||
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
||||
}
|
||||
|
||||
Ok(Ref::map(data, |data| {
|
||||
bytemuck::from_bytes(&data[8..mem::size_of::<T>() + 8])
|
||||
}))
|
||||
}
|
||||
|
||||
fn load_fully_unchecked<T: ZeroCopy + Owner>(&self) -> Result<Ref<T>> {
|
||||
let data = self.try_borrow_data()?;
|
||||
Ok(Ref::map(data, |data| {
|
||||
bytemuck::from_bytes(&data[8..mem::size_of::<T>() + 8])
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info> LoadMutZeroCopyRef for AccountInfo<'info> {
|
||||
fn load_mut<T: ZeroCopy + Owner>(&self) -> Result<RefMut<T>> {
|
||||
if self.owner != &T::owner() {
|
||||
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
|
||||
}
|
||||
|
||||
let data = self.try_borrow_mut_data()?;
|
||||
|
||||
let disc_bytes = array_ref![data, 0, 8];
|
||||
if disc_bytes != &T::discriminator() {
|
||||
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
||||
}
|
||||
|
||||
Ok(RefMut::map(data, |data| {
|
||||
bytemuck::from_bytes_mut(&mut data[8..mem::size_of::<T>() + 8])
|
||||
}))
|
||||
}
|
||||
|
||||
fn load_mut_fully_unchecked<T: ZeroCopy + Owner>(&self) -> Result<RefMut<T>> {
|
||||
let data = self.try_borrow_mut_data()?;
|
||||
Ok(RefMut::map(data, |data| {
|
||||
bytemuck::from_bytes_mut(&mut data[8..mem::size_of::<T>() + 8])
|
||||
}))
|
||||
}
|
||||
}
|
|
@ -3,8 +3,8 @@ use fixed::types::I80F48;
|
|||
use std::cmp::min;
|
||||
|
||||
use crate::error::*;
|
||||
use crate::state::ScanningAccountRetriever;
|
||||
use crate::state::*;
|
||||
use crate::state::{oracle_price, ScanningAccountRetriever};
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
#[derive(Accounts)]
|
||||
|
@ -35,7 +35,7 @@ pub fn liq_token_with_token(
|
|||
let group_pk = &ctx.accounts.group.key();
|
||||
|
||||
require!(asset_token_index != liab_token_index, MangoError::SomeError);
|
||||
let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk)?;
|
||||
let mut account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk)?;
|
||||
|
||||
let mut liqor = ctx.accounts.liqor.load_mut()?;
|
||||
require!(liqor.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
|
@ -67,12 +67,8 @@ pub fn liq_token_with_token(
|
|||
//
|
||||
// This must happen _after_ the health computation, since immutable borrows of
|
||||
// the bank are not allowed at the same time.
|
||||
let (mut asset_bank, asset_oracle) =
|
||||
account_retriever.bank_mut_and_oracle(asset_token_index)?;
|
||||
let (mut liab_bank, liab_oracle) =
|
||||
account_retriever.bank_mut_and_oracle(liab_token_index)?;
|
||||
let asset_price = oracle_price(asset_oracle, asset_bank.mint_decimals)?;
|
||||
let liab_price = oracle_price(liab_oracle, liab_bank.mint_decimals)?;
|
||||
let (asset_bank, liab_bank, asset_price, liab_price) =
|
||||
account_retriever.banks_mut_and_oracles(asset_token_index, liab_token_index)?;
|
||||
|
||||
let liqee_assets_native = liqee
|
||||
.tokens
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::accounts_zerocopy::*;
|
||||
use crate::error::MangoError;
|
||||
use crate::state::{compute_health_from_fixed_accounts, Bank, Group, HealthType, MangoAccount};
|
||||
use crate::util::LoadZeroCopy;
|
||||
use crate::{group_seeds, Mango};
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token::{self, Token, TokenAccount};
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use bytemuck::cast_ref;
|
||||
|
||||
use crate::accounts_zerocopy::*;
|
||||
use crate::error::MangoError;
|
||||
use crate::state::EventQueue;
|
||||
use crate::{
|
||||
state::{EventType, FillEvent, Group, MangoAccount, OutEvent, PerpMarket},
|
||||
util::LoadZeroCopy,
|
||||
};
|
||||
use crate::state::{EventType, FillEvent, Group, MangoAccount, OutEvent, PerpMarket};
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct PerpConsumeEvents<'info> {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
use crate::accounts_zerocopy::*;
|
||||
use crate::error::*;
|
||||
use crate::state::{
|
||||
compute_health_from_fixed_accounts, oracle_price, Book, EventQueue, Group, HealthType,
|
||||
|
@ -90,7 +91,7 @@ pub fn perp_place_order(
|
|||
let mut event_queue = ctx.accounts.event_queue.load_mut()?;
|
||||
|
||||
let oracle_price = oracle_price(
|
||||
&ctx.accounts.oracle.to_account_info(),
|
||||
&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?,
|
||||
perp_market.base_token_decimals,
|
||||
)?;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
use crate::accounts_zerocopy::*;
|
||||
use crate::state::{oracle_price, Book, PerpMarket};
|
||||
|
||||
#[derive(Accounts)]
|
||||
|
@ -28,7 +29,7 @@ pub fn perp_update_funding(ctx: Context<PerpUpdateFunding>) -> Result<()> {
|
|||
let book = Book::load_mut(bids, asks, &perp_market)?;
|
||||
|
||||
let oracle_price = oracle_price(
|
||||
&ctx.accounts.oracle.to_account_info(),
|
||||
&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?,
|
||||
perp_market.base_token_decimals,
|
||||
)?;
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ use anchor_lang::prelude::*;
|
|||
|
||||
use instructions::*;
|
||||
|
||||
pub mod accounts_zerocopy;
|
||||
pub mod address_lookup_table;
|
||||
pub mod error;
|
||||
pub mod instructions;
|
||||
|
|
|
@ -6,18 +6,24 @@ use std::cmp::min;
|
|||
use std::convert::identity;
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::accounts_zerocopy::*;
|
||||
use crate::error::*;
|
||||
use crate::state::*;
|
||||
|
||||
/// Serum padding is "serum" + data + "padding"
|
||||
fn strip_dex_padding<'a>(acc: &'a AccountInfo) -> Result<Ref<'a, [u8]>> {
|
||||
fn strip_dex_padding<'a>(data: &[u8]) -> Result<&[u8]> {
|
||||
require!(data.len() >= 12, MangoError::SomeError);
|
||||
Ok(&data[5..data.len() - 7])
|
||||
}
|
||||
|
||||
fn strip_dex_padding_ref<'a>(acc: &'a AccountInfo) -> Result<Ref<'a, [u8]>> {
|
||||
require!(acc.data_len() >= 12, MangoError::SomeError);
|
||||
Ok(Ref::map(acc.try_borrow_data()?, |data| {
|
||||
&data[5..data.len() - 7]
|
||||
}))
|
||||
}
|
||||
|
||||
fn strip_dex_padding_mut<'a>(acc: &'a AccountInfo) -> Result<RefMut<'a, [u8]>> {
|
||||
fn strip_dex_padding_ref_mut<'a>(acc: &'a AccountInfo) -> Result<RefMut<'a, [u8]>> {
|
||||
require!(acc.data_len() >= 12, MangoError::SomeError);
|
||||
Ok(RefMut::map(acc.try_borrow_mut_data()?, |data| {
|
||||
let len = data.len();
|
||||
|
@ -49,7 +55,7 @@ pub fn load_market_state<'a>(
|
|||
) -> Result<Ref<'a, serum_dex::state::MarketState>> {
|
||||
require!(market_account.owner == program_id, MangoError::SomeError);
|
||||
let state: Ref<serum_dex::state::MarketState> =
|
||||
Ref::map(strip_dex_padding(market_account)?, |data| {
|
||||
Ref::map(strip_dex_padding_ref(market_account)?, |data| {
|
||||
bytemuck::from_bytes(data)
|
||||
});
|
||||
state
|
||||
|
@ -75,7 +81,7 @@ pub fn load_bids_mut<'a>(
|
|||
bids.key.to_aligned_bytes() == identity(sm.bids),
|
||||
MangoError::SomeError
|
||||
);
|
||||
let orig_data = strip_dex_padding_mut(bids)?;
|
||||
let orig_data = strip_dex_padding_ref_mut(bids)?;
|
||||
let (header, buf) = strip_data_header_mut::<OrderBookStateHeader, u8>(orig_data)?;
|
||||
require!(
|
||||
header.account_flags
|
||||
|
@ -94,7 +100,7 @@ pub fn load_asks_mut<'a>(
|
|||
asks.key.to_aligned_bytes() == identity(sm.asks),
|
||||
MangoError::SomeError
|
||||
);
|
||||
let orig_data = strip_dex_padding_mut(asks)?;
|
||||
let orig_data = strip_dex_padding_ref_mut(asks)?;
|
||||
let (header, buf) = strip_data_header_mut::<OrderBookStateHeader, u8>(orig_data)?;
|
||||
require!(
|
||||
header.account_flags
|
||||
|
@ -105,8 +111,14 @@ pub fn load_asks_mut<'a>(
|
|||
Ok(RefMut::map(buf, serum_dex::critbit::Slab::new))
|
||||
}
|
||||
|
||||
pub fn load_open_orders<'a>(acc: &'a AccountInfo) -> Result<Ref<'a, serum_dex::state::OpenOrders>> {
|
||||
Ok(Ref::map(strip_dex_padding(acc)?, bytemuck::from_bytes))
|
||||
pub fn load_open_orders_ref<'a>(
|
||||
acc: &'a AccountInfo,
|
||||
) -> Result<Ref<'a, serum_dex::state::OpenOrders>> {
|
||||
Ok(Ref::map(strip_dex_padding_ref(acc)?, bytemuck::from_bytes))
|
||||
}
|
||||
|
||||
pub fn load_open_orders<'a>(acc: &impl AccountReader) -> Result<&serum_dex::state::OpenOrders> {
|
||||
Ok(bytemuck::from_bytes(strip_dex_padding(acc.data())?))
|
||||
}
|
||||
|
||||
pub fn pubkey_from_u64_array(d: [u64; 4]) -> Pubkey {
|
||||
|
@ -318,7 +330,7 @@ impl<'a> CancelOrder<'a> {
|
|||
// find all cancels by scanning open_orders/bids/asks
|
||||
let mut cancels = vec![];
|
||||
{
|
||||
let open_orders = load_open_orders(&self.open_orders)?;
|
||||
let open_orders = load_open_orders_ref(&self.open_orders)?;
|
||||
let market = load_market_state(&self.market, self.program.key)?;
|
||||
let bids = load_bids_mut(&market, &self.bids)?;
|
||||
let asks = load_asks_mut(&market, &self.asks)?;
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use fixed::types::I80F48;
|
||||
use serum_dex::state::OpenOrders;
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::accounts_zerocopy::*;
|
||||
use crate::error::MangoError;
|
||||
use crate::serum3_cpi;
|
||||
use crate::state::{oracle_price, Bank, MangoAccount, PerpMarket, PerpMarketIndex, TokenIndex};
|
||||
use crate::util::checked_math as cm;
|
||||
use crate::util::LoadZeroCopy;
|
||||
|
||||
/// This trait abstracts how to find accounts needed for the health computation.
|
||||
///
|
||||
|
@ -20,22 +19,22 @@ use crate::util::LoadZeroCopy;
|
|||
/// It needs more compute, but works when a union of bank/oracle/market accounts
|
||||
/// are passed because health needs to be computed for different baskets in
|
||||
/// one instruction (such as for liquidation instructions).
|
||||
pub trait AccountRetriever<'a, 'b> {
|
||||
pub trait AccountRetriever {
|
||||
fn bank_and_oracle(
|
||||
&self,
|
||||
group: &Pubkey,
|
||||
account_index: usize,
|
||||
token_index: TokenIndex,
|
||||
) -> Result<(Ref<'a, Bank>, &'a AccountInfo<'b>)>;
|
||||
) -> Result<(&Bank, I80F48)>;
|
||||
|
||||
fn serum_oo(&self, account_index: usize, key: &Pubkey) -> Result<Ref<'a, OpenOrders>>;
|
||||
fn serum_oo(&self, account_index: usize, key: &Pubkey) -> Result<&OpenOrders>;
|
||||
|
||||
fn perp_market(
|
||||
&self,
|
||||
group: &Pubkey,
|
||||
account_index: usize,
|
||||
perp_market_index: PerpMarketIndex,
|
||||
) -> Result<Ref<'a, PerpMarket>>;
|
||||
) -> Result<&PerpMarket>;
|
||||
}
|
||||
|
||||
/// Assumes the account infos needed for the health computation follow a strict order.
|
||||
|
@ -44,26 +43,26 @@ pub trait AccountRetriever<'a, 'b> {
|
|||
/// 2. n_banks oracle accounts, one for each bank in the same order
|
||||
/// 3. PerpMarket accounts, in the order of account.perps.iter_active_accounts()
|
||||
/// 4. serum3 OpenOrders accounts, in the order of account.serum3.iter_active()
|
||||
pub struct FixedOrderAccountRetriever<'a, 'b> {
|
||||
ais: &'a [AccountInfo<'b>],
|
||||
pub struct FixedOrderAccountRetriever<'a, 'info> {
|
||||
ais: Vec<AccountInfoRef<'a, 'info>>,
|
||||
n_banks: usize,
|
||||
begin_perp: usize,
|
||||
begin_serum3: usize,
|
||||
}
|
||||
|
||||
impl<'a, 'b> AccountRetriever<'a, 'b> for FixedOrderAccountRetriever<'a, 'b> {
|
||||
impl<'a, 'info> AccountRetriever for FixedOrderAccountRetriever<'a, 'info> {
|
||||
fn bank_and_oracle(
|
||||
&self,
|
||||
group: &Pubkey,
|
||||
account_index: usize,
|
||||
token_index: TokenIndex,
|
||||
) -> Result<(Ref<'a, Bank>, &'a AccountInfo<'b>)> {
|
||||
) -> Result<(&Bank, I80F48)> {
|
||||
let bank = self.ais[account_index].load::<Bank>()?;
|
||||
require!(&bank.group == group, MangoError::SomeError);
|
||||
require!(bank.token_index == token_index, MangoError::SomeError);
|
||||
let oracle = &self.ais[cm!(self.n_banks + account_index)];
|
||||
require!(&bank.oracle == oracle.key, MangoError::SomeError);
|
||||
Ok((bank, oracle))
|
||||
Ok((bank, oracle_price(oracle, bank.mint_decimals)?))
|
||||
}
|
||||
|
||||
fn perp_market(
|
||||
|
@ -71,7 +70,7 @@ impl<'a, 'b> AccountRetriever<'a, 'b> for FixedOrderAccountRetriever<'a, 'b> {
|
|||
group: &Pubkey,
|
||||
account_index: usize,
|
||||
perp_market_index: PerpMarketIndex,
|
||||
) -> Result<Ref<'a, PerpMarket>> {
|
||||
) -> Result<&PerpMarket> {
|
||||
let ai = &self.ais[cm!(self.begin_perp + account_index)];
|
||||
let market = ai.load::<PerpMarket>()?;
|
||||
require!(&market.group == group, MangoError::SomeError);
|
||||
|
@ -82,7 +81,7 @@ impl<'a, 'b> AccountRetriever<'a, 'b> for FixedOrderAccountRetriever<'a, 'b> {
|
|||
Ok(market)
|
||||
}
|
||||
|
||||
fn serum_oo(&self, account_index: usize, key: &Pubkey) -> Result<Ref<'a, OpenOrders>> {
|
||||
fn serum_oo(&self, account_index: usize, key: &Pubkey) -> Result<&OpenOrders> {
|
||||
let ai = &self.ais[cm!(self.begin_serum3 + account_index)];
|
||||
require!(key == ai.key, MangoError::SomeError);
|
||||
serum3_cpi::load_open_orders(ai)
|
||||
|
@ -96,14 +95,14 @@ impl<'a, 'b> AccountRetriever<'a, 'b> for FixedOrderAccountRetriever<'a, 'b> {
|
|||
/// - an unknown number of serum3 OpenOrders accounts
|
||||
/// and retrieves accounts needed for the health computation by doing a linear
|
||||
/// scan for each request.
|
||||
pub struct ScanningAccountRetriever<'a, 'b> {
|
||||
ais: &'a [AccountInfo<'b>],
|
||||
pub struct ScanningAccountRetriever<'a, 'info> {
|
||||
ais: Vec<AccountInfoRefMut<'a, 'info>>,
|
||||
token_index_map: HashMap<TokenIndex, usize>,
|
||||
perp_index_map: HashMap<PerpMarketIndex, usize>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> ScanningAccountRetriever<'a, 'b> {
|
||||
pub fn new(ais: &'a [AccountInfo<'b>], group: &Pubkey) -> Result<Self> {
|
||||
impl<'a, 'info> ScanningAccountRetriever<'a, 'info> {
|
||||
pub fn new(ais: &'a [AccountInfo<'info>], group: &Pubkey) -> Result<Self> {
|
||||
let mut token_index_map = HashMap::with_capacity(ais.len() / 2);
|
||||
for (i, ai) in ais.iter().enumerate() {
|
||||
match ai.load::<Bank>() {
|
||||
|
@ -145,7 +144,10 @@ impl<'a, 'b> ScanningAccountRetriever<'a, 'b> {
|
|||
}
|
||||
|
||||
Ok(Self {
|
||||
ais,
|
||||
ais: ais
|
||||
.into_iter()
|
||||
.map(|ai| AccountInfoRefMut::borrow(ai))
|
||||
.collect::<Result<Vec<_>>>()?,
|
||||
token_index_map,
|
||||
perp_index_map,
|
||||
})
|
||||
|
@ -175,30 +177,54 @@ impl<'a, 'b> ScanningAccountRetriever<'a, 'b> {
|
|||
.ok_or_else(|| error!(MangoError::SomeError))?)
|
||||
}
|
||||
|
||||
pub fn bank_mut_and_oracle(
|
||||
&self,
|
||||
token_index: TokenIndex,
|
||||
) -> Result<(RefMut<'a, Bank>, &'a AccountInfo<'b>)> {
|
||||
let index = self.bank_index(token_index)?;
|
||||
let bank = self.ais[index].load_mut_fully_unchecked::<Bank>()?;
|
||||
let oracle = &self.ais[cm!(self.n_banks() + index)];
|
||||
require!(&bank.oracle == oracle.key, MangoError::SomeError);
|
||||
Ok((bank, oracle))
|
||||
pub fn banks_mut_and_oracles(
|
||||
&mut self,
|
||||
token_index1: TokenIndex,
|
||||
token_index2: TokenIndex,
|
||||
) -> Result<(&mut Bank, &mut Bank, I80F48, I80F48)> {
|
||||
let index1 = self.bank_index(token_index1)?;
|
||||
let index2 = self.bank_index(token_index2)?;
|
||||
let (first, second, swap) = if index1 < index2 {
|
||||
(index1, index2, false)
|
||||
} else {
|
||||
(index2, index1, true)
|
||||
};
|
||||
let n_banks = self.n_banks();
|
||||
|
||||
// split_at_mut after the first bank and after the second bank
|
||||
let (first_bank_part, second_part) = self.ais.split_at_mut(first + 1);
|
||||
let (second_bank_part, oracles_part) = second_part.split_at_mut(second - first);
|
||||
|
||||
let bank1 = first_bank_part[first].load_mut_fully_unchecked::<Bank>()?;
|
||||
let bank2 = second_bank_part[second - (first + 1)].load_mut_fully_unchecked::<Bank>()?;
|
||||
let oracle1 = &oracles_part[cm!(n_banks + first - (second + 1))];
|
||||
let oracle2 = &oracles_part[cm!(n_banks + second - (second + 1))];
|
||||
require!(&bank1.oracle == oracle1.key, MangoError::SomeError);
|
||||
require!(&bank2.oracle == oracle2.key, MangoError::SomeError);
|
||||
let mint_decimals1 = bank1.mint_decimals;
|
||||
let mint_decimals2 = bank2.mint_decimals;
|
||||
let price1 = oracle_price(oracle1, mint_decimals1)?;
|
||||
let price2 = oracle_price(oracle2, mint_decimals2)?;
|
||||
if swap {
|
||||
Ok((bank2, bank1, price2, price1))
|
||||
} else {
|
||||
Ok((bank1, bank2, price1, price2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> AccountRetriever<'a, 'b> for ScanningAccountRetriever<'a, 'b> {
|
||||
impl<'a, 'info> AccountRetriever for ScanningAccountRetriever<'a, 'info> {
|
||||
fn bank_and_oracle(
|
||||
&self,
|
||||
_group: &Pubkey,
|
||||
_account_index: usize,
|
||||
token_index: TokenIndex,
|
||||
) -> Result<(Ref<'a, Bank>, &'a AccountInfo<'b>)> {
|
||||
) -> Result<(&Bank, I80F48)> {
|
||||
let index = self.bank_index(token_index)?;
|
||||
let bank = self.ais[index].load_fully_unchecked::<Bank>()?;
|
||||
let oracle = &self.ais[cm!(self.n_banks() + index)];
|
||||
require!(&bank.oracle == oracle.key, MangoError::SomeError);
|
||||
Ok((bank, oracle))
|
||||
Ok((bank, oracle_price(oracle, bank.mint_decimals)?))
|
||||
}
|
||||
|
||||
fn perp_market(
|
||||
|
@ -206,12 +232,12 @@ impl<'a, 'b> AccountRetriever<'a, 'b> for ScanningAccountRetriever<'a, 'b> {
|
|||
_group: &Pubkey,
|
||||
_account_index: usize,
|
||||
perp_market_index: PerpMarketIndex,
|
||||
) -> Result<Ref<'a, PerpMarket>> {
|
||||
) -> Result<&PerpMarket> {
|
||||
let index = self.perp_market_index(perp_market_index)?;
|
||||
self.ais[index].load_fully_unchecked::<PerpMarket>()
|
||||
}
|
||||
|
||||
fn serum_oo(&self, _account_index: usize, key: &Pubkey) -> Result<Ref<'a, OpenOrders>> {
|
||||
fn serum_oo(&self, _account_index: usize, key: &Pubkey) -> Result<&OpenOrders> {
|
||||
let oo = self.ais[self.begin_serum3()..]
|
||||
.iter()
|
||||
.find(|ai| ai.key == key)
|
||||
|
@ -250,7 +276,10 @@ pub fn compute_health_from_fixed_accounts(
|
|||
require!(ais.len() == expected_ais, MangoError::SomeError);
|
||||
|
||||
let retriever = FixedOrderAccountRetriever {
|
||||
ais,
|
||||
ais: ais
|
||||
.into_iter()
|
||||
.map(|ai| AccountInfoRef::borrow(ai))
|
||||
.collect::<Result<Vec<_>>>()?,
|
||||
n_banks: active_token_len,
|
||||
begin_perp: cm!(active_token_len * 2),
|
||||
begin_serum3: cm!(active_token_len * 2 + active_perp_len),
|
||||
|
@ -259,10 +288,10 @@ pub fn compute_health_from_fixed_accounts(
|
|||
}
|
||||
|
||||
/// Compute health with an arbitrary AccountRetriever
|
||||
pub fn compute_health<'a, 'b: 'a>(
|
||||
pub fn compute_health(
|
||||
account: &MangoAccount,
|
||||
health_type: HealthType,
|
||||
retriever: &impl AccountRetriever<'a, 'b>,
|
||||
retriever: &impl AccountRetriever,
|
||||
) -> Result<I80F48> {
|
||||
compute_health_detail(account, retriever, health_type, true)?.health(health_type)
|
||||
}
|
||||
|
@ -274,9 +303,9 @@ pub fn compute_health<'a, 'b: 'a>(
|
|||
///
|
||||
/// However, this only works if the serum3 open orders accounts have been
|
||||
/// fully settled (like via serum3_liq_force_cancel_orders).
|
||||
pub fn health_cache_for_liqee<'a, 'b: 'a>(
|
||||
pub fn health_cache_for_liqee(
|
||||
account: &MangoAccount,
|
||||
retriever: &impl AccountRetriever<'a, 'b>,
|
||||
retriever: &impl AccountRetriever,
|
||||
) -> Result<HealthCache> {
|
||||
compute_health_detail(account, retriever, HealthType::Init, false)
|
||||
}
|
||||
|
@ -417,18 +446,17 @@ fn pair_health(
|
|||
///
|
||||
/// The reason is that the health type used can affect the way funds reserved for
|
||||
/// orders get distributed to the token balances.
|
||||
fn compute_health_detail<'a, 'b: 'a>(
|
||||
fn compute_health_detail(
|
||||
account: &MangoAccount,
|
||||
retriever: &impl AccountRetriever<'a, 'b>,
|
||||
retriever: &impl AccountRetriever,
|
||||
health_type: HealthType,
|
||||
allow_serum3: bool,
|
||||
) -> Result<HealthCache> {
|
||||
// token contribution from token accounts
|
||||
let mut token_infos = vec![];
|
||||
for (i, position) in account.tokens.iter_active().enumerate() {
|
||||
let (bank, oracle_ai) =
|
||||
let (bank, oracle_price) =
|
||||
retriever.bank_and_oracle(&account.group, i, position.token_index)?;
|
||||
let oracle_price = oracle_price(oracle_ai, bank.mint_decimals)?;
|
||||
|
||||
// converts the token value to the basis token value for health computations
|
||||
// TODO: health basis token == USDC?
|
||||
|
@ -822,21 +850,29 @@ mod tests {
|
|||
oo1.as_account_info(),
|
||||
];
|
||||
|
||||
let retriever = ScanningAccountRetriever::new(&ais, &group).unwrap();
|
||||
let mut retriever = ScanningAccountRetriever::new(&ais, &group).unwrap();
|
||||
|
||||
assert_eq!(retriever.n_banks(), 2);
|
||||
assert_eq!(retriever.begin_serum3(), 5);
|
||||
assert_eq!(retriever.perp_index_map.len(), 1);
|
||||
|
||||
let (b1, o1) = retriever.bank_mut_and_oracle(1).unwrap();
|
||||
{
|
||||
let (b1, b2, o1, o2) = retriever.banks_mut_and_oracles(1, 4).unwrap();
|
||||
assert_eq!(b1.token_index, 1);
|
||||
assert_eq!(o1.key, ais[2].key);
|
||||
|
||||
let (b2, o2) = retriever.bank_mut_and_oracle(4).unwrap();
|
||||
assert_eq!(o1, I80F48::ONE);
|
||||
assert_eq!(b2.token_index, 4);
|
||||
assert_eq!(o2.key, ais[3].key);
|
||||
assert_eq!(o2, 5 * I80F48::ONE);
|
||||
}
|
||||
|
||||
retriever.bank_mut_and_oracle(2).unwrap_err();
|
||||
{
|
||||
let (b1, b2, o1, o2) = retriever.banks_mut_and_oracles(4, 1).unwrap();
|
||||
assert_eq!(b1.token_index, 4);
|
||||
assert_eq!(o1, 5 * I80F48::ONE);
|
||||
assert_eq!(b2.token_index, 1);
|
||||
assert_eq!(o2, I80F48::ONE);
|
||||
}
|
||||
|
||||
retriever.banks_mut_and_oracles(4, 2).unwrap_err();
|
||||
|
||||
let oo = retriever.serum_oo(0, &oo1key).unwrap();
|
||||
assert_eq!(identity(oo.native_pc_total), 20);
|
||||
|
|
|
@ -5,9 +5,9 @@ use anchor_lang::Discriminator;
|
|||
use fixed::types::I80F48;
|
||||
use static_assertions::const_assert_eq;
|
||||
|
||||
use crate::accounts_zerocopy::*;
|
||||
use crate::checked_math as cm;
|
||||
use crate::error::MangoError;
|
||||
use crate::util::LoadZeroCopy;
|
||||
|
||||
pub const QUOTE_DECIMALS: i32 = 6;
|
||||
|
||||
|
@ -38,8 +38,8 @@ pub fn determine_oracle_type(data: &[u8]) -> Result<OracleType> {
|
|||
Err(MangoError::UnknownOracleType.into())
|
||||
}
|
||||
|
||||
pub fn oracle_price(acc_info: &AccountInfo, base_token_decimals: u8) -> Result<I80F48> {
|
||||
let data = &acc_info.try_borrow_data()?;
|
||||
pub fn oracle_price(acc_info: &impl AccountReader, base_token_decimals: u8) -> Result<I80F48> {
|
||||
let data = &acc_info.data();
|
||||
let oracle_type = determine_oracle_type(data)?;
|
||||
|
||||
Ok(match oracle_type {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::cell::RefMut;
|
||||
|
||||
use crate::accounts_zerocopy::*;
|
||||
use crate::{
|
||||
error::MangoError,
|
||||
state::{
|
||||
|
@ -7,7 +8,6 @@ use crate::{
|
|||
EventQueue, MangoAccount, MangoAccountPerps, PerpMarket, FREE_ORDER_SLOT,
|
||||
MAX_PERP_OPEN_ORDERS,
|
||||
},
|
||||
util::LoadZeroCopy,
|
||||
};
|
||||
use anchor_lang::prelude::*;
|
||||
use bytemuck::cast;
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
use crate::error::MangoError;
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_lang::ZeroCopy;
|
||||
use arrayref::array_ref;
|
||||
use std::cell::RefMut;
|
||||
use std::{cell::Ref, mem};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! zip {
|
||||
|
@ -24,72 +20,6 @@ macro_rules! checked_math {
|
|||
}
|
||||
pub(crate) use checked_math;
|
||||
|
||||
pub trait LoadZeroCopy {
|
||||
/// Using AccountLoader forces a AccountInfo.clone() and then binds the loaded
|
||||
/// lifetime to the AccountLoader's lifetime. This function avoids both.
|
||||
/// It checks the account owner and discriminator, then casts the data.
|
||||
fn load<T: ZeroCopy + Owner>(&self) -> Result<Ref<T>>;
|
||||
|
||||
/// Same as load(), but mut
|
||||
fn load_mut<T: ZeroCopy + Owner>(&self) -> Result<RefMut<T>>;
|
||||
|
||||
/// Same as load(), but doesn't check the discriminator or owner.
|
||||
fn load_fully_unchecked<T: ZeroCopy + Owner>(&self) -> Result<Ref<T>>;
|
||||
|
||||
/// Same as load_fully_unchecked(), but mut
|
||||
fn load_mut_fully_unchecked<T: ZeroCopy + Owner>(&self) -> Result<RefMut<T>>;
|
||||
}
|
||||
|
||||
impl<'info> LoadZeroCopy for AccountInfo<'info> {
|
||||
fn load_mut<T: ZeroCopy + Owner>(&self) -> Result<RefMut<T>> {
|
||||
if self.owner != &T::owner() {
|
||||
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
|
||||
}
|
||||
|
||||
let data = self.try_borrow_mut_data()?;
|
||||
|
||||
let disc_bytes = array_ref![data, 0, 8];
|
||||
if disc_bytes != &T::discriminator() {
|
||||
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
||||
}
|
||||
|
||||
Ok(RefMut::map(data, |data| {
|
||||
bytemuck::from_bytes_mut(&mut data[8..mem::size_of::<T>() + 8])
|
||||
}))
|
||||
}
|
||||
|
||||
fn load<T: ZeroCopy + Owner>(&self) -> Result<Ref<T>> {
|
||||
if self.owner != &T::owner() {
|
||||
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
|
||||
}
|
||||
|
||||
let data = self.try_borrow_data()?;
|
||||
|
||||
let disc_bytes = array_ref![data, 0, 8];
|
||||
if disc_bytes != &T::discriminator() {
|
||||
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
||||
}
|
||||
|
||||
Ok(Ref::map(data, |data| {
|
||||
bytemuck::from_bytes(&data[8..mem::size_of::<T>() + 8])
|
||||
}))
|
||||
}
|
||||
|
||||
fn load_mut_fully_unchecked<T: ZeroCopy + Owner>(&self) -> Result<RefMut<T>> {
|
||||
let data = self.try_borrow_mut_data()?;
|
||||
Ok(RefMut::map(data, |data| {
|
||||
bytemuck::from_bytes_mut(&mut data[8..mem::size_of::<T>() + 8])
|
||||
}))
|
||||
}
|
||||
|
||||
fn load_fully_unchecked<T: ZeroCopy + Owner>(&self) -> Result<Ref<T>> {
|
||||
let data = self.try_borrow_data()?;
|
||||
Ok(Ref::map(data, |data| {
|
||||
bytemuck::from_bytes(&data[8..mem::size_of::<T>() + 8])
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill16_from_str(name: String) -> Result<[u8; 16]> {
|
||||
let name_bytes = name.as_bytes();
|
||||
require!(name_bytes.len() < 16, MangoError::SomeError);
|
||||
|
|
Loading…
Reference in New Issue