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:
Christian Kamm 2022-06-08 13:43:12 +02:00 committed by GitHub
parent a14f37362d
commit b3568b7f3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 334 additions and 146 deletions

View File

@ -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])
}))
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
assert_eq!(b1.token_index, 1);
assert_eq!(o1.key, ais[2].key);
{
let (b1, b2, o1, o2) = retriever.banks_mut_and_oracles(1, 4).unwrap();
assert_eq!(b1.token_index, 1);
assert_eq!(o1, I80F48::ONE);
assert_eq!(b2.token_index, 4);
assert_eq!(o2, 5 * I80F48::ONE);
}
let (b2, o2) = retriever.bank_mut_and_oracle(4).unwrap();
assert_eq!(b2.token_index, 4);
assert_eq!(o2.key, ais[3].key);
{
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.bank_mut_and_oracle(2).unwrap_err();
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);

View File

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

View File

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

View File

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