Merge branch 'main' into mc/clippy
This commit is contained in:
commit
5919a5a1df
|
@ -30,6 +30,8 @@ pub struct CreatePerpMarket<'info> {
|
||||||
#[account(zero)]
|
#[account(zero)]
|
||||||
pub asks: AccountLoader<'info, BookSide>,
|
pub asks: AccountLoader<'info, BookSide>,
|
||||||
|
|
||||||
|
pub event_queue: UncheckedAccount<'info>,
|
||||||
|
|
||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
pub payer: Signer<'info>,
|
pub payer: Signer<'info>,
|
||||||
|
|
||||||
|
@ -50,6 +52,7 @@ pub fn create_perp_market(
|
||||||
oracle: ctx.accounts.oracle.key(),
|
oracle: ctx.accounts.oracle.key(),
|
||||||
bids: ctx.accounts.bids.key(),
|
bids: ctx.accounts.bids.key(),
|
||||||
asks: ctx.accounts.asks.key(),
|
asks: ctx.accounts.asks.key(),
|
||||||
|
event_queue: ctx.accounts.event_queue.key(),
|
||||||
quote_lot_size,
|
quote_lot_size,
|
||||||
base_lot_size,
|
base_lot_size,
|
||||||
seq_num: 0,
|
seq_num: 0,
|
||||||
|
@ -65,5 +68,7 @@ pub fn create_perp_market(
|
||||||
let mut asks = ctx.accounts.asks.load_init()?;
|
let mut asks = ctx.accounts.asks.load_init()?;
|
||||||
asks.book_side_type = BookSideType::Asks;
|
asks.book_side_type = BookSideType::Asks;
|
||||||
|
|
||||||
|
// TODO: discriminator on event queue
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
|
||||||
// TODO: This will be used to disable is_bankrupt or being_liquidated
|
// TODO: This will be used to disable is_bankrupt or being_liquidated
|
||||||
// when health recovers sufficiently
|
// when health recovers sufficiently
|
||||||
//
|
//
|
||||||
let health = compute_health(&account, ctx.remaining_accounts)?;
|
let health = compute_health_from_fixed_accounts(&account, ctx.remaining_accounts)?;
|
||||||
msg!("health: {}", health);
|
msg!("health: {}", health);
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
use anchor_lang::prelude::*;
|
||||||
|
|
||||||
|
use crate::state::*;
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct LiqTokenWithToken<'info> {
|
||||||
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
has_one = group,
|
||||||
|
constraint = liqor.load()?.owner == liqor_owner.key(),
|
||||||
|
)]
|
||||||
|
pub liqor: AccountLoader<'info, MangoAccount>,
|
||||||
|
pub liqor_owner: Signer<'info>,
|
||||||
|
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
has_one = group,
|
||||||
|
)]
|
||||||
|
pub liqee: AccountLoader<'info, MangoAccount>,
|
||||||
|
|
||||||
|
// TODO: these banks are also passed in remainingAccounts
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
has_one = group,
|
||||||
|
constraint = asset_bank.load()?.token_index != liab_bank.load()?.token_index,
|
||||||
|
)]
|
||||||
|
pub asset_bank: AccountLoader<'info, Bank>,
|
||||||
|
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
has_one = group,
|
||||||
|
)]
|
||||||
|
pub liab_bank: AccountLoader<'info, Bank>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn liq_token_with_token(ctx: Context<LiqTokenWithToken>) -> Result<()> {
|
||||||
|
//
|
||||||
|
// Health computation
|
||||||
|
//
|
||||||
|
let liqee = ctx.accounts.liqee.load()?;
|
||||||
|
let health = compute_health_by_scanning_accounts(&liqee, ctx.remaining_accounts)?;
|
||||||
|
msg!("health: {}", health);
|
||||||
|
|
||||||
|
// TODO: everything
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::error::MangoError;
|
use crate::error::MangoError;
|
||||||
use crate::state::{compute_health, Bank, Group, MangoAccount};
|
use crate::state::{compute_health_from_fixed_accounts, Bank, Group, MangoAccount};
|
||||||
use crate::{group_seeds, Mango};
|
use crate::{group_seeds, Mango};
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
use anchor_spl::token::TokenAccount;
|
use anchor_spl::token::TokenAccount;
|
||||||
|
@ -94,7 +94,7 @@ pub fn margin_trade<'key, 'accounts, 'remaining, 'info>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// compute pre cpi health
|
// compute pre cpi health
|
||||||
let pre_cpi_health = compute_health(&account, health_ais)?;
|
let pre_cpi_health = compute_health_from_fixed_accounts(&account, health_ais)?;
|
||||||
require!(pre_cpi_health > 0, MangoError::HealthMustBePositive);
|
require!(pre_cpi_health > 0, MangoError::HealthMustBePositive);
|
||||||
msg!("pre_cpi_health {:?}", pre_cpi_health);
|
msg!("pre_cpi_health {:?}", pre_cpi_health);
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ pub fn margin_trade<'key, 'accounts, 'remaining, 'info>(
|
||||||
// compute post cpi health
|
// compute post cpi health
|
||||||
// todo: this is not working, the health is computed on old bank state and not taking into account
|
// todo: this is not working, the health is computed on old bank state and not taking into account
|
||||||
// withdraws done in adjust_for_post_cpi_token_amounts
|
// withdraws done in adjust_for_post_cpi_token_amounts
|
||||||
let post_cpi_health = compute_health(&account, health_ais)?;
|
let post_cpi_health = compute_health_from_fixed_accounts(&account, health_ais)?;
|
||||||
require!(post_cpi_health > 0, MangoError::HealthMustBePositive);
|
require!(post_cpi_health > 0, MangoError::HealthMustBePositive);
|
||||||
msg!("post_cpi_health {:?}", post_cpi_health);
|
msg!("post_cpi_health {:?}", post_cpi_health);
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ pub use create_group::*;
|
||||||
pub use create_perp_market::*;
|
pub use create_perp_market::*;
|
||||||
pub use create_stub_oracle::*;
|
pub use create_stub_oracle::*;
|
||||||
pub use deposit::*;
|
pub use deposit::*;
|
||||||
|
pub use liq_token_with_token::*;
|
||||||
pub use place_perp_order::*;
|
pub use place_perp_order::*;
|
||||||
pub use register_token::*;
|
pub use register_token::*;
|
||||||
pub use serum3_cancel_order::*;
|
pub use serum3_cancel_order::*;
|
||||||
|
@ -20,6 +21,7 @@ mod create_group;
|
||||||
mod create_perp_market;
|
mod create_perp_market;
|
||||||
mod create_stub_oracle;
|
mod create_stub_oracle;
|
||||||
mod deposit;
|
mod deposit;
|
||||||
|
mod liq_token_with_token;
|
||||||
mod margin_trade;
|
mod margin_trade;
|
||||||
mod place_perp_order;
|
mod place_perp_order;
|
||||||
mod register_token;
|
mod register_token;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
|
||||||
use crate::state::{Book, BookSide, Group, MangoAccount, OrderType, PerpMarket};
|
use crate::state::{Book, BookSide, EventQueue, Group, MangoAccount, OrderType, PerpMarket};
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct PlacePerpOrder<'info> {
|
pub struct PlacePerpOrder<'info> {
|
||||||
|
@ -25,6 +25,7 @@ pub struct PlacePerpOrder<'info> {
|
||||||
pub asks: AccountLoader<'info, BookSide>,
|
pub asks: AccountLoader<'info, BookSide>,
|
||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
pub bids: AccountLoader<'info, BookSide>,
|
pub bids: AccountLoader<'info, BookSide>,
|
||||||
|
pub event_queue: UncheckedAccount<'info>,
|
||||||
pub oracle: UncheckedAccount<'info>,
|
pub oracle: UncheckedAccount<'info>,
|
||||||
|
|
||||||
pub owner: Signer<'info>,
|
pub owner: Signer<'info>,
|
||||||
|
@ -50,6 +51,10 @@ pub fn place_perp_order(
|
||||||
let asks = &ctx.accounts.asks.to_account_info();
|
let asks = &ctx.accounts.asks.to_account_info();
|
||||||
let mut book = Book::load_checked(bids, asks, &perp_market)?;
|
let mut book = Book::load_checked(bids, asks, &perp_market)?;
|
||||||
|
|
||||||
|
let event_queue_ai = &ctx.accounts.event_queue.to_account_info();
|
||||||
|
let mut event_queue =
|
||||||
|
EventQueue::load_mut_checked(event_queue_ai, ctx.program_id, &perp_market)?;
|
||||||
|
|
||||||
// let oracle_price = oracle_price(&ctx.accounts.oracle.to_account_info())?;
|
// let oracle_price = oracle_price(&ctx.accounts.oracle.to_account_info())?;
|
||||||
|
|
||||||
let now_ts = Clock::get()?.unix_timestamp as u64;
|
let now_ts = Clock::get()?.unix_timestamp as u64;
|
||||||
|
@ -70,6 +75,7 @@ pub fn place_perp_order(
|
||||||
// TODO reduce_only based on event queue
|
// TODO reduce_only based on event queue
|
||||||
|
|
||||||
book.new_bid(
|
book.new_bid(
|
||||||
|
&mut event_queue,
|
||||||
&mut perp_market,
|
&mut perp_market,
|
||||||
// oracle_price,
|
// oracle_price,
|
||||||
// &mut account,
|
// &mut account,
|
||||||
|
|
|
@ -101,7 +101,7 @@ pub fn serum3_liq_force_cancel_orders(
|
||||||
// TODO: do the correct health / being_liquidated check
|
// TODO: do the correct health / being_liquidated check
|
||||||
{
|
{
|
||||||
let account = ctx.accounts.account.load()?;
|
let account = ctx.accounts.account.load()?;
|
||||||
let health = compute_health(&account, ctx.remaining_accounts)?;
|
let health = compute_health_from_fixed_accounts(&account, ctx.remaining_accounts)?;
|
||||||
msg!("health: {}", health);
|
msg!("health: {}", health);
|
||||||
require!(health < 0, MangoError::SomeError);
|
require!(health < 0, MangoError::SomeError);
|
||||||
}
|
}
|
||||||
|
|
|
@ -234,7 +234,7 @@ pub fn serum3_place_order(
|
||||||
// Health check
|
// Health check
|
||||||
//
|
//
|
||||||
let account = ctx.accounts.account.load()?;
|
let account = ctx.accounts.account.load()?;
|
||||||
let health = compute_health(&account, ctx.remaining_accounts)?;
|
let health = compute_health_from_fixed_accounts(&account, &ctx.remaining_accounts)?;
|
||||||
msg!("health: {}", health);
|
msg!("health: {}", health);
|
||||||
require!(health >= 0, MangoError::SomeError);
|
require!(health >= 0, MangoError::SomeError);
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,7 @@ pub fn withdraw(ctx: Context<Withdraw>, amount: u64, allow_borrow: bool) -> Resu
|
||||||
//
|
//
|
||||||
// Health check
|
// Health check
|
||||||
//
|
//
|
||||||
let health = compute_health(&account, ctx.remaining_accounts)?;
|
let health = compute_health_from_fixed_accounts(&account, &ctx.remaining_accounts)?;
|
||||||
msg!("health: {}", health);
|
msg!("health: {}", health);
|
||||||
require!(health >= 0, MangoError::SomeError);
|
require!(health >= 0, MangoError::SomeError);
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,152 @@
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
use fixed::types::I80F48;
|
use fixed::types::I80F48;
|
||||||
|
use serum_dex::state::OpenOrders;
|
||||||
use std::cell::Ref;
|
use std::cell::Ref;
|
||||||
|
|
||||||
use crate::error::MangoError;
|
use crate::error::MangoError;
|
||||||
use crate::serum3_cpi;
|
use crate::serum3_cpi;
|
||||||
use crate::state::{oracle_price, Bank, MangoAccount};
|
use crate::state::{oracle_price, Bank, MangoAccount, TokenIndex};
|
||||||
use crate::util;
|
|
||||||
use crate::util::checked_math as cm;
|
use crate::util::checked_math as cm;
|
||||||
use crate::util::LoadZeroCopy;
|
use crate::util::LoadZeroCopy;
|
||||||
|
|
||||||
pub fn compute_health(account: &MangoAccount, ais: &[AccountInfo]) -> Result<I80F48> {
|
/// This trait abstracts how to find accounts needed for the health computation.
|
||||||
|
///
|
||||||
|
/// There are different ways they are retrieved from remainingAccounts, based
|
||||||
|
/// on the instruction:
|
||||||
|
/// - FixedOrderAccountRetriever requires the remainingAccounts to be in a well
|
||||||
|
/// defined order and is the fastest. It's used where possible.
|
||||||
|
/// - ScanningAccountRetriever does a linear scan for each account it needs.
|
||||||
|
/// 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).
|
||||||
|
trait AccountRetriever<'a, 'b> {
|
||||||
|
fn bank_and_oracle(
|
||||||
|
&self,
|
||||||
|
group: &Pubkey,
|
||||||
|
account_index: usize,
|
||||||
|
token_index: TokenIndex,
|
||||||
|
) -> Result<(Ref<'a, Bank>, &'a AccountInfo<'b>)>;
|
||||||
|
|
||||||
|
fn serum_oo(&self, account_index: usize, key: &Pubkey) -> Result<Ref<'a, OpenOrders>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assumes the account infos needed for the health computation follow a strict order.
|
||||||
|
///
|
||||||
|
/// 1. n_banks Bank account, in the order of account.token_account_map.iter_active()
|
||||||
|
/// 2. n_banks oracle accounts, one for each bank in the same order
|
||||||
|
/// 3. serum3 OpenOrders accounts, in the order of account.serum3_account_map.iter_active()
|
||||||
|
struct FixedOrderAccountRetriever<'a, 'b> {
|
||||||
|
ais: &'a [AccountInfo<'b>],
|
||||||
|
n_banks: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> AccountRetriever<'a, 'b> for FixedOrderAccountRetriever<'a, 'b> {
|
||||||
|
fn bank_and_oracle(
|
||||||
|
&self,
|
||||||
|
group: &Pubkey,
|
||||||
|
account_index: usize,
|
||||||
|
token_index: TokenIndex,
|
||||||
|
) -> Result<(Ref<'a, Bank>, &'a AccountInfo<'b>)> {
|
||||||
|
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[self.n_banks + account_index];
|
||||||
|
require!(&bank.oracle == oracle.key, MangoError::SomeError);
|
||||||
|
Ok((bank, oracle))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serum_oo(&self, account_index: usize, key: &Pubkey) -> Result<Ref<'a, OpenOrders>> {
|
||||||
|
let ai = &self.ais[2 * self.n_banks + account_index];
|
||||||
|
require!(key == ai.key, MangoError::SomeError);
|
||||||
|
serum3_cpi::load_open_orders(ai)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a list of account infos containing
|
||||||
|
/// - an unknown number of Banks in any order, followed by
|
||||||
|
/// - the same number of oracles in the same order as the banks, followed by
|
||||||
|
/// - an unknown number of serum3 OpenOrders accounts
|
||||||
|
/// and retrieves accounts needed for the health computation by doing a linear
|
||||||
|
/// scan for each request.
|
||||||
|
struct ScanningAccountRetriever<'a, 'b> {
|
||||||
|
ais: &'a [AccountInfo<'b>],
|
||||||
|
banks: Vec<Ref<'a, Bank>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> ScanningAccountRetriever<'a, 'b> {
|
||||||
|
fn new(ais: &'a [AccountInfo<'b>]) -> Result<Self> {
|
||||||
|
let mut banks = vec![];
|
||||||
|
for ai in ais.iter() {
|
||||||
|
match ai.load::<Bank>() {
|
||||||
|
Ok(bank) => banks.push(bank),
|
||||||
|
Err(Error::AnchorError(error))
|
||||||
|
if error.error_code_number
|
||||||
|
== ErrorCode::AccountDiscriminatorMismatch as u32 =>
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(Self { ais, banks })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn n_banks(&self) -> usize {
|
||||||
|
self.banks.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> AccountRetriever<'a, 'b> for ScanningAccountRetriever<'a, 'b> {
|
||||||
|
fn bank_and_oracle(
|
||||||
|
&self,
|
||||||
|
group: &Pubkey,
|
||||||
|
_account_index: usize,
|
||||||
|
token_index: TokenIndex,
|
||||||
|
) -> Result<(Ref<'a, Bank>, &'a AccountInfo<'b>)> {
|
||||||
|
let (i, bank) = self
|
||||||
|
.banks
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, b)| b.token_index == token_index)
|
||||||
|
.unwrap();
|
||||||
|
require!(&bank.group == group, MangoError::SomeError);
|
||||||
|
let oracle = &self.ais[self.n_banks() + i];
|
||||||
|
require!(&bank.oracle == oracle.key, MangoError::SomeError);
|
||||||
|
Ok((Ref::clone(bank), oracle))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serum_oo(&self, _account_index: usize, key: &Pubkey) -> Result<Ref<'a, OpenOrders>> {
|
||||||
|
let oo = self.ais[2 * self.n_banks()..]
|
||||||
|
.iter()
|
||||||
|
.find(|ai| ai.key == key)
|
||||||
|
.unwrap();
|
||||||
|
serum3_cpi::load_open_orders(oo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compute_health_from_fixed_accounts<'a, 'b>(
|
||||||
|
account: &MangoAccount,
|
||||||
|
ais: &'a [AccountInfo<'b>],
|
||||||
|
) -> Result<I80F48> {
|
||||||
let active_token_len = account.token_account_map.iter_active().count();
|
let active_token_len = account.token_account_map.iter_active().count();
|
||||||
let active_serum_len = account.serum3_account_map.iter_active().count();
|
let active_serum_len = account.serum3_account_map.iter_active().count();
|
||||||
let expected_ais = active_token_len * 2 // banks + oracles
|
let expected_ais = active_token_len * 2 // banks + oracles
|
||||||
+ active_serum_len; // open_orders
|
+ active_serum_len; // open_orders
|
||||||
require!(ais.len() == expected_ais, MangoError::SomeError);
|
require!(ais.len() == expected_ais, MangoError::SomeError);
|
||||||
let banks = &ais[0..active_token_len];
|
|
||||||
let oracles = &ais[active_token_len..active_token_len * 2];
|
|
||||||
let serum_oos = &ais[active_token_len * 2..];
|
|
||||||
|
|
||||||
compute_health_detail(account, banks, oracles, serum_oos)
|
let retriever = FixedOrderAccountRetriever {
|
||||||
|
ais,
|
||||||
|
n_banks: active_token_len,
|
||||||
|
};
|
||||||
|
compute_health_detail(account, retriever)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compute_health_by_scanning_accounts<'a, 'b>(
|
||||||
|
account: &MangoAccount,
|
||||||
|
ais: &'a [AccountInfo<'b>],
|
||||||
|
) -> Result<I80F48> {
|
||||||
|
let retriever = ScanningAccountRetriever::new(ais)?;
|
||||||
|
compute_health_detail(account, retriever)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TokenInfo<'a> {
|
struct TokenInfo<'a> {
|
||||||
|
@ -84,57 +211,34 @@ fn pair_health(
|
||||||
Ok(cm!(health1 + health2))
|
Ok(cm!(health1 + health2))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_health_detail(
|
fn compute_health_detail<'a, 'b: 'a>(
|
||||||
account: &MangoAccount,
|
account: &MangoAccount,
|
||||||
banks: &[AccountInfo],
|
retriever: impl AccountRetriever<'a, 'b>,
|
||||||
oracles: &[AccountInfo],
|
|
||||||
serum_oos: &[AccountInfo],
|
|
||||||
) -> Result<I80F48> {
|
) -> Result<I80F48> {
|
||||||
// collect the bank and oracle data once
|
|
||||||
let mut token_infos = util::zip!(banks.iter(), oracles.iter())
|
|
||||||
.map(|(bank_ai, oracle_ai)| {
|
|
||||||
let bank = bank_ai.load::<Bank>()?;
|
|
||||||
require!(bank.group == account.group, MangoError::SomeError);
|
|
||||||
require!(bank.oracle == oracle_ai.key(), MangoError::UnexpectedOracle);
|
|
||||||
let oracle_price = oracle_price(oracle_ai)?;
|
|
||||||
Ok(TokenInfo {
|
|
||||||
bank,
|
|
||||||
oracle_price,
|
|
||||||
balance: I80F48::ZERO,
|
|
||||||
price_asset_cache: I80F48::ZERO,
|
|
||||||
price_liab_cache: I80F48::ZERO,
|
|
||||||
price_inv_cache: I80F48::ZERO,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>>>()?;
|
|
||||||
|
|
||||||
// token contribution from token accounts
|
// token contribution from token accounts
|
||||||
for (position, token_info) in util::zip!(
|
let mut token_infos = vec![];
|
||||||
account.token_account_map.iter_active(),
|
for (i, position) in account.token_account_map.iter_active().enumerate() {
|
||||||
token_infos.iter_mut()
|
let (bank, oracle_ai) =
|
||||||
) {
|
retriever.bank_and_oracle(&account.group, i, position.token_index)?;
|
||||||
let bank = &token_info.bank;
|
let oracle_price = oracle_price(oracle_ai)?;
|
||||||
// This assumes banks are passed in order
|
|
||||||
require!(
|
|
||||||
bank.token_index == position.token_index,
|
|
||||||
MangoError::SomeError
|
|
||||||
);
|
|
||||||
|
|
||||||
// converts the token value to the basis token value for health computations
|
// converts the token value to the basis token value for health computations
|
||||||
// TODO: health basis token == USDC?
|
// TODO: health basis token == USDC?
|
||||||
let native = position.native(bank);
|
let native = position.native(&bank);
|
||||||
token_info.balance = cm!(token_info.balance + native);
|
|
||||||
|
token_infos.push(TokenInfo {
|
||||||
|
bank,
|
||||||
|
oracle_price,
|
||||||
|
balance: native,
|
||||||
|
price_asset_cache: I80F48::ZERO,
|
||||||
|
price_liab_cache: I80F48::ZERO,
|
||||||
|
price_inv_cache: I80F48::ZERO,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// token contribution from serum accounts
|
// token contribution from serum accounts
|
||||||
for (serum_account, oo_ai) in
|
for (i, serum_account) in account.serum3_account_map.iter_active().enumerate() {
|
||||||
util::zip!(account.serum3_account_map.iter_active(), serum_oos.iter())
|
let oo = retriever.serum_oo(i, &serum_account.open_orders)?;
|
||||||
{
|
|
||||||
// This assumes serum open orders are passed in order
|
|
||||||
require!(
|
|
||||||
&serum_account.open_orders == oo_ai.key,
|
|
||||||
MangoError::SomeError
|
|
||||||
);
|
|
||||||
|
|
||||||
// find the TokenInfos for the market's base and quote tokens
|
// find the TokenInfos for the market's base and quote tokens
|
||||||
let base_index = token_infos
|
let base_index = token_infos
|
||||||
|
@ -153,8 +257,6 @@ fn compute_health_detail(
|
||||||
(&mut r[0], &mut l[quote_index])
|
(&mut r[0], &mut l[quote_index])
|
||||||
};
|
};
|
||||||
|
|
||||||
let oo = serum3_cpi::load_open_orders(oo_ai)?;
|
|
||||||
|
|
||||||
// add the amounts that are freely settleable
|
// add the amounts that are freely settleable
|
||||||
let base_free = I80F48::from_num(oo.native_coin_free);
|
let base_free = I80F48::from_num(oo.native_coin_free);
|
||||||
let quote_free = I80F48::from_num(cm!(oo.native_pc_free + oo.referrer_rebates_accrued));
|
let quote_free = I80F48::from_num(cm!(oo.native_pc_free + oo.referrer_rebates_accrued));
|
||||||
|
|
|
@ -14,6 +14,6 @@ mod health;
|
||||||
mod mango_account;
|
mod mango_account;
|
||||||
mod mint_info;
|
mod mint_info;
|
||||||
mod oracle;
|
mod oracle;
|
||||||
pub mod orderbook;
|
mod orderbook;
|
||||||
mod perp_market;
|
mod perp_market;
|
||||||
mod serum3_market;
|
mod serum3_market;
|
||||||
|
|
|
@ -4,10 +4,11 @@ use crate::{
|
||||||
error::MangoError,
|
error::MangoError,
|
||||||
state::{
|
state::{
|
||||||
orderbook::{bookside::BookSide, nodes::LeafNode},
|
orderbook::{bookside::BookSide, nodes::LeafNode},
|
||||||
PerpMarket,
|
EventQueue, OutEvent, PerpMarket,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
use bytemuck::cast;
|
||||||
use fixed::types::I80F48;
|
use fixed::types::I80F48;
|
||||||
use fixed_macro::types::I80F48;
|
use fixed_macro::types::I80F48;
|
||||||
|
|
||||||
|
@ -340,7 +341,7 @@ impl<'a> Book<'a> {
|
||||||
// mango_group: &MangoGroup,
|
// mango_group: &MangoGroup,
|
||||||
// mango_group_pk: &Pubkey,
|
// mango_group_pk: &Pubkey,
|
||||||
// mango_cache: &MangoCache,
|
// mango_cache: &MangoCache,
|
||||||
// event_queue: &mut EventQueue,
|
event_queue: &mut EventQueue,
|
||||||
market: &mut PerpMarket,
|
market: &mut PerpMarket,
|
||||||
// oracle_price: I80F48,
|
// oracle_price: I80F48,
|
||||||
// mango_account: &mut MangoAccount,
|
// mango_account: &mut MangoAccount,
|
||||||
|
@ -404,16 +405,16 @@ impl<'a> Book<'a> {
|
||||||
// Remove the order from the book unless we've done that enough
|
// Remove the order from the book unless we've done that enough
|
||||||
if number_of_dropped_expired_orders < DROP_EXPIRED_ORDER_LIMIT {
|
if number_of_dropped_expired_orders < DROP_EXPIRED_ORDER_LIMIT {
|
||||||
number_of_dropped_expired_orders += 1;
|
number_of_dropped_expired_orders += 1;
|
||||||
// let event = OutEvent::new(
|
let event = OutEvent::new(
|
||||||
// Side::Ask,
|
Side::Ask,
|
||||||
// best_ask.owner_slot,
|
best_ask.owner_slot,
|
||||||
// now_ts,
|
now_ts,
|
||||||
// event_queue.header.seq_num,
|
event_queue.header.seq_num,
|
||||||
// best_ask.owner,
|
best_ask.owner,
|
||||||
// best_ask.quantity,
|
best_ask.quantity,
|
||||||
// );
|
);
|
||||||
// event_queue.push_back(cast(event)).unwrap();
|
event_queue.push_back(cast(event)).unwrap();
|
||||||
// ask_deletes.push(best_ask.key);
|
ask_deletes.push(best_ask.key);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ pub use metadata::*;
|
||||||
pub use nodes::*;
|
pub use nodes::*;
|
||||||
pub use ob_utils::*;
|
pub use ob_utils::*;
|
||||||
pub use order_type::*;
|
pub use order_type::*;
|
||||||
pub use order_type::*;
|
|
||||||
pub use queue::*;
|
pub use queue::*;
|
||||||
|
|
||||||
pub mod book;
|
pub mod book;
|
||||||
|
|
|
@ -1,437 +1,439 @@
|
||||||
// use std::cell::RefMut;
|
use std::cell::RefMut;
|
||||||
// use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
// use crate::error::MangoError;
|
use crate::error::MangoError;
|
||||||
// use crate::state::PerpMarket;
|
use crate::state::PerpMarket;
|
||||||
// use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
// use fixed::types::I80F48;
|
use fixed::types::I80F48;
|
||||||
// use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
// use solana_program::account_info::AccountInfo;
|
use solana_program::account_info::AccountInfo;
|
||||||
// use solana_program::pubkey::Pubkey;
|
use solana_program::pubkey::Pubkey;
|
||||||
// use solana_program::sysvar::rent::Rent;
|
use solana_program::sysvar::rent::Rent;
|
||||||
// use static_assertions::const_assert_eq;
|
use static_assertions::const_assert_eq;
|
||||||
|
|
||||||
// // use mango_logs::FillLog;
|
// use mango_logs::FillLog;
|
||||||
// use mango_macro::Pod;
|
use mango_macro::Pod;
|
||||||
|
|
||||||
// use super::metadata::MetaData;
|
use super::metadata::MetaData;
|
||||||
// use super::ob_utils::strip_header_mut;
|
use super::ob_utils::strip_header_mut;
|
||||||
// use super::order_type::Side;
|
use super::order_type::Side;
|
||||||
// // use safe_transmute::{self, trivial::TriviallyTransmutable};
|
// use safe_transmute::{self, trivial::TriviallyTransmutable};
|
||||||
|
|
||||||
// // use crate::error::{check_assert, MangoErrorCode, MangoResult, SourceFileId};
|
// use crate::error::{check_assert, MangoErrorCode, MangoResult, SourceFileId};
|
||||||
// // use crate::matching::Side;
|
// use crate::matching::Side;
|
||||||
// // use crate::state::{DataType, MetaData, PerpMarket};
|
// use crate::state::{DataType, MetaData, PerpMarket};
|
||||||
// // use crate::utils::strip_header_mut;
|
// use crate::utils::strip_header_mut;
|
||||||
|
|
||||||
// // Don't want event queue to become single threaded if it's logging liquidations
|
// Don't want event queue to become single threaded if it's logging liquidations
|
||||||
// // Most common scenario will be liqors depositing USDC and withdrawing some other token
|
// Most common scenario will be liqors depositing USDC and withdrawing some other token
|
||||||
// // So tying it to token deposited is not wise
|
// So tying it to token deposited is not wise
|
||||||
// // also can't tie it to token withdrawn because during bull market, liqs will be depositing all base tokens and withdrawing quote
|
// also can't tie it to token withdrawn because during bull market, liqs will be depositing all base tokens and withdrawing quote
|
||||||
// //
|
//
|
||||||
|
|
||||||
// pub trait QueueHeader: bytemuck::Pod {
|
pub const MAX_NUM_EVENTS: usize = 512;
|
||||||
// type Item: bytemuck::Pod + Copy;
|
|
||||||
|
|
||||||
// fn head(&self) -> usize;
|
pub trait QueueHeader: bytemuck::Pod {
|
||||||
// fn set_head(&mut self, value: usize);
|
type Item: bytemuck::Pod + Copy;
|
||||||
// fn count(&self) -> usize;
|
|
||||||
// fn set_count(&mut self, value: usize);
|
|
||||||
|
|
||||||
// fn incr_event_id(&mut self);
|
fn head(&self) -> usize;
|
||||||
// fn decr_event_id(&mut self, n: usize);
|
fn set_head(&mut self, value: usize);
|
||||||
// }
|
fn count(&self) -> usize;
|
||||||
|
fn set_count(&mut self, value: usize);
|
||||||
|
|
||||||
// pub struct Queue<'a, H: QueueHeader> {
|
fn incr_event_id(&mut self);
|
||||||
// pub header: RefMut<'a, H>,
|
fn decr_event_id(&mut self, n: usize);
|
||||||
// pub buf: RefMut<'a, [H::Item]>,
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// impl<'a, H: QueueHeader> Queue<'a, H> {
|
pub struct Queue<'a, H: QueueHeader> {
|
||||||
// pub fn new(header: RefMut<'a, H>, buf: RefMut<'a, [H::Item]>) -> Self {
|
pub header: RefMut<'a, H>,
|
||||||
// Self { header, buf }
|
pub buf: RefMut<'a, [H::Item]>,
|
||||||
// }
|
}
|
||||||
|
|
||||||
// pub fn load_mut(account: &'a AccountInfo) -> Result<Self> {
|
impl<'a, H: QueueHeader> Queue<'a, H> {
|
||||||
// let (header, buf) = strip_header_mut::<H, H::Item>(account)?;
|
pub fn new(header: RefMut<'a, H>, buf: RefMut<'a, [H::Item]>) -> Self {
|
||||||
// Ok(Self { header, buf })
|
Self { header, buf }
|
||||||
// }
|
}
|
||||||
|
|
||||||
// pub fn len(&self) -> usize {
|
pub fn load_mut(account: &'a AccountInfo) -> Result<Self> {
|
||||||
// self.header.count()
|
let (header, buf) = strip_header_mut::<H, H::Item>(account)?;
|
||||||
// }
|
Ok(Self { header, buf })
|
||||||
|
}
|
||||||
|
|
||||||
// pub fn full(&self) -> bool {
|
pub fn len(&self) -> usize {
|
||||||
// self.header.count() == self.buf.len()
|
self.header.count()
|
||||||
// }
|
}
|
||||||
|
|
||||||
// pub fn empty(&self) -> bool {
|
pub fn full(&self) -> bool {
|
||||||
// self.header.count() == 0
|
self.header.count() == self.buf.len()
|
||||||
// }
|
}
|
||||||
|
|
||||||
// pub fn push_back(&mut self, value: H::Item) -> std::result::Result<(), H::Item> {
|
pub fn empty(&self) -> bool {
|
||||||
// if self.full() {
|
self.header.count() == 0
|
||||||
// return Err(value);
|
}
|
||||||
// }
|
|
||||||
// let slot = (self.header.head() + self.header.count()) % self.buf.len();
|
|
||||||
// self.buf[slot] = value;
|
|
||||||
|
|
||||||
// let count = self.header.count();
|
pub fn push_back(&mut self, value: H::Item) -> std::result::Result<(), H::Item> {
|
||||||
// self.header.set_count(count + 1);
|
if self.full() {
|
||||||
|
return Err(value);
|
||||||
|
}
|
||||||
|
let slot = (self.header.head() + self.header.count()) % self.buf.len();
|
||||||
|
self.buf[slot] = value;
|
||||||
|
|
||||||
// self.header.incr_event_id();
|
let count = self.header.count();
|
||||||
// Ok(())
|
self.header.set_count(count + 1);
|
||||||
// }
|
|
||||||
|
|
||||||
// pub fn peek_front(&self) -> Option<&H::Item> {
|
self.header.incr_event_id();
|
||||||
// if self.empty() {
|
Ok(())
|
||||||
// return None;
|
}
|
||||||
// }
|
|
||||||
// Some(&self.buf[self.header.head()])
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub fn peek_front_mut(&mut self) -> Option<&mut H::Item> {
|
pub fn peek_front(&self) -> Option<&H::Item> {
|
||||||
// if self.empty() {
|
if self.empty() {
|
||||||
// return None;
|
return None;
|
||||||
// }
|
}
|
||||||
// Some(&mut self.buf[self.header.head()])
|
Some(&self.buf[self.header.head()])
|
||||||
// }
|
}
|
||||||
|
|
||||||
// pub fn pop_front(&mut self) -> std::result::Result<H::Item, ()> {
|
pub fn peek_front_mut(&mut self) -> Option<&mut H::Item> {
|
||||||
// if self.empty() {
|
if self.empty() {
|
||||||
// return Err(());
|
return None;
|
||||||
// }
|
}
|
||||||
// let value = self.buf[self.header.head()];
|
Some(&mut self.buf[self.header.head()])
|
||||||
|
}
|
||||||
|
|
||||||
// let count = self.header.count();
|
pub fn pop_front(&mut self) -> std::result::Result<H::Item, ()> {
|
||||||
// self.header.set_count(count - 1);
|
if self.empty() {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
let value = self.buf[self.header.head()];
|
||||||
|
|
||||||
// let head = self.header.head();
|
let count = self.header.count();
|
||||||
// self.header.set_head((head + 1) % self.buf.len());
|
self.header.set_count(count - 1);
|
||||||
|
|
||||||
// Ok(value)
|
let head = self.header.head();
|
||||||
// }
|
self.header.set_head((head + 1) % self.buf.len());
|
||||||
|
|
||||||
// pub fn revert_pushes(&mut self, desired_len: usize) -> Result<()> {
|
Ok(value)
|
||||||
// require!(desired_len <= self.header.count(), MangoError::SomeError);
|
}
|
||||||
// let len_diff = self.header.count() - desired_len;
|
|
||||||
// self.header.set_count(desired_len);
|
|
||||||
// self.header.decr_event_id(len_diff);
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub fn iter(&self) -> impl Iterator<Item = &H::Item> {
|
pub fn revert_pushes(&mut self, desired_len: usize) -> Result<()> {
|
||||||
// QueueIterator {
|
require!(desired_len <= self.header.count(), MangoError::SomeError);
|
||||||
// queue: self,
|
let len_diff = self.header.count() - desired_len;
|
||||||
// index: 0,
|
self.header.set_count(desired_len);
|
||||||
// }
|
self.header.decr_event_id(len_diff);
|
||||||
// }
|
Ok(())
|
||||||
// }
|
}
|
||||||
|
|
||||||
// struct QueueIterator<'a, 'b, H: QueueHeader> {
|
pub fn iter(&self) -> impl Iterator<Item = &H::Item> {
|
||||||
// queue: &'b Queue<'a, H>,
|
QueueIterator {
|
||||||
// index: usize,
|
queue: self,
|
||||||
// }
|
index: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// impl<'a, 'b, H: QueueHeader> Iterator for QueueIterator<'a, 'b, H> {
|
struct QueueIterator<'a, 'b, H: QueueHeader> {
|
||||||
// type Item = &'b H::Item;
|
queue: &'b Queue<'a, H>,
|
||||||
// fn next(&mut self) -> Option<Self::Item> {
|
index: usize,
|
||||||
// if self.index == self.queue.len() {
|
}
|
||||||
// None
|
|
||||||
// } else {
|
|
||||||
// let item =
|
|
||||||
// &self.queue.buf[(self.queue.header.head() + self.index) % self.queue.buf.len()];
|
|
||||||
// self.index += 1;
|
|
||||||
// Some(item)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[account(zero_copy)]
|
impl<'a, 'b, H: QueueHeader> Iterator for QueueIterator<'a, 'b, H> {
|
||||||
// pub struct EventQueueHeader {
|
type Item = &'b H::Item;
|
||||||
// pub meta_data: MetaData,
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
// head: usize,
|
if self.index == self.queue.len() {
|
||||||
// count: usize,
|
None
|
||||||
// pub seq_num: usize,
|
} else {
|
||||||
// }
|
let item =
|
||||||
// // unsafe impl TriviallyTransmutable for EventQueueHeader {}
|
&self.queue.buf[(self.queue.header.head() + self.index) % self.queue.buf.len()];
|
||||||
|
self.index += 1;
|
||||||
|
Some(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// impl QueueHeader for EventQueueHeader {
|
#[account(zero_copy)]
|
||||||
// type Item = AnyEvent;
|
pub struct EventQueueHeader {
|
||||||
|
pub meta_data: MetaData,
|
||||||
|
head: usize,
|
||||||
|
count: usize,
|
||||||
|
pub seq_num: usize,
|
||||||
|
}
|
||||||
|
// unsafe impl TriviallyTransmutable for EventQueueHeader {}
|
||||||
|
|
||||||
// fn head(&self) -> usize {
|
impl QueueHeader for EventQueueHeader {
|
||||||
// self.head
|
type Item = AnyEvent;
|
||||||
// }
|
|
||||||
// fn set_head(&mut self, value: usize) {
|
|
||||||
// self.head = value;
|
|
||||||
// }
|
|
||||||
// fn count(&self) -> usize {
|
|
||||||
// self.count
|
|
||||||
// }
|
|
||||||
// fn set_count(&mut self, value: usize) {
|
|
||||||
// self.count = value;
|
|
||||||
// }
|
|
||||||
// fn incr_event_id(&mut self) {
|
|
||||||
// self.seq_num += 1;
|
|
||||||
// }
|
|
||||||
// fn decr_event_id(&mut self, n: usize) {
|
|
||||||
// self.seq_num -= n;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub type EventQueue<'a> = Queue<'a, EventQueueHeader>;
|
fn head(&self) -> usize {
|
||||||
|
self.head
|
||||||
|
}
|
||||||
|
fn set_head(&mut self, value: usize) {
|
||||||
|
self.head = value;
|
||||||
|
}
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
self.count
|
||||||
|
}
|
||||||
|
fn set_count(&mut self, value: usize) {
|
||||||
|
self.count = value;
|
||||||
|
}
|
||||||
|
fn incr_event_id(&mut self) {
|
||||||
|
self.seq_num += 1;
|
||||||
|
}
|
||||||
|
fn decr_event_id(&mut self, n: usize) {
|
||||||
|
self.seq_num -= n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// impl<'a> EventQueue<'a> {
|
pub type EventQueue<'a> = Queue<'a, EventQueueHeader>;
|
||||||
// pub fn load_mut_checked(
|
|
||||||
// account: &'a AccountInfo,
|
|
||||||
// program_id: &Pubkey,
|
|
||||||
// perp_market: &PerpMarket,
|
|
||||||
// ) -> Result<Self> {
|
|
||||||
// require!(account.owner == program_id, MangoError::SomeError); // MangoErrorCode::InvalidOwner
|
|
||||||
// // require!(
|
|
||||||
// // &perp_market.event_queue == account.key,
|
|
||||||
// // MangoError::SomeError
|
|
||||||
// // ); // MangoErrorCode::InvalidAccount
|
|
||||||
// Self::load_mut(account)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub fn load_and_init(
|
impl<'a> EventQueue<'a> {
|
||||||
// account: &'a AccountInfo,
|
pub fn load_mut_checked(
|
||||||
// program_id: &Pubkey,
|
account: &'a AccountInfo,
|
||||||
// rent: &Rent,
|
program_id: &Pubkey,
|
||||||
// ) -> Result<Self> {
|
perp_market: &PerpMarket,
|
||||||
// // NOTE: check this first so we can borrow account later
|
) -> Result<Self> {
|
||||||
// require!(
|
require!(account.owner == program_id, MangoError::SomeError); // InvalidOwner
|
||||||
// rent.is_exempt(account.lamports(), account.data_len()),
|
require!(
|
||||||
// MangoError::SomeError
|
&perp_market.event_queue == account.key,
|
||||||
// ); //MangoErrorCode::AccountNotRentExempt
|
MangoError::SomeError
|
||||||
|
); //InvalidAccount
|
||||||
|
Self::load_mut(account)
|
||||||
|
}
|
||||||
|
|
||||||
// let mut state = Self::load_mut(account)?;
|
pub fn load_and_init(
|
||||||
// require!(account.owner == program_id, MangoError::SomeError); // MangoErrorCode::InvalidOwner
|
account: &'a AccountInfo,
|
||||||
|
program_id: &Pubkey,
|
||||||
|
rent: &Rent,
|
||||||
|
) -> Result<Self> {
|
||||||
|
// NOTE: check this first so we can borrow account later
|
||||||
|
require!(
|
||||||
|
rent.is_exempt(account.lamports(), account.data_len()),
|
||||||
|
MangoError::SomeError
|
||||||
|
); //MangoErrorCode::AccountNotRentExempt
|
||||||
|
|
||||||
// // require!(
|
let state = Self::load_mut(account)?;
|
||||||
// // !state.header.meta_data.is_initialized,
|
require!(account.owner == program_id, MangoError::SomeError); // MangoErrorCode::InvalidOwner
|
||||||
// // MangoError::SomeError
|
|
||||||
// // );
|
|
||||||
// // state.header.meta_data = MetaData::new(DataType::EventQueue, 0, true);
|
|
||||||
|
|
||||||
// Ok(state)
|
// require!(
|
||||||
// }
|
// !state.header.meta_data.is_initialized,
|
||||||
// }
|
// MangoError::SomeError
|
||||||
|
// );
|
||||||
|
// state.header.meta_data = MetaData::new(DataType::EventQueue, 0, true);
|
||||||
|
|
||||||
// #[derive(Copy, Clone, IntoPrimitive, TryFromPrimitive, Eq, PartialEq)]
|
Ok(state)
|
||||||
// #[repr(u8)]
|
}
|
||||||
// pub enum EventType {
|
}
|
||||||
// Fill,
|
|
||||||
// Out,
|
|
||||||
// Liquidate,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const EVENT_SIZE: usize = 200;
|
#[derive(Copy, Clone, IntoPrimitive, TryFromPrimitive, Eq, PartialEq)]
|
||||||
// #[derive(Copy, Clone, Debug, Pod)]
|
#[repr(u8)]
|
||||||
// #[repr(C)]
|
pub enum EventType {
|
||||||
// pub struct AnyEvent {
|
Fill,
|
||||||
// pub event_type: u8,
|
Out,
|
||||||
// pub padding: [u8; EVENT_SIZE - 1],
|
Liquidate,
|
||||||
// }
|
}
|
||||||
// // unsafe impl TriviallyTransmutable for AnyEvent {}
|
|
||||||
|
|
||||||
// #[derive(Copy, Clone, Debug, Pod)]
|
const EVENT_SIZE: usize = 200;
|
||||||
// #[repr(C)]
|
#[derive(Copy, Clone, Debug, Pod)]
|
||||||
// pub struct FillEvent {
|
#[repr(C)]
|
||||||
// pub event_type: u8,
|
pub struct AnyEvent {
|
||||||
// pub taker_side: Side, // side from the taker's POV
|
pub event_type: u8,
|
||||||
// pub maker_slot: u8,
|
pub padding: [u8; EVENT_SIZE - 1],
|
||||||
// pub maker_out: bool, // true if maker order quantity == 0
|
}
|
||||||
// pub version: u8,
|
// unsafe impl TriviallyTransmutable for AnyEvent {}
|
||||||
// pub market_fees_applied: bool,
|
|
||||||
// pub padding: [u8; 2],
|
|
||||||
// pub timestamp: u64,
|
|
||||||
// pub seq_num: usize, // note: usize same as u64
|
|
||||||
|
|
||||||
// pub maker: Pubkey,
|
#[derive(Copy, Clone, Debug, Pod)]
|
||||||
// pub maker_order_id: i128,
|
#[repr(C)]
|
||||||
// pub maker_client_order_id: u64,
|
pub struct FillEvent {
|
||||||
// pub maker_fee: I80F48,
|
pub event_type: u8,
|
||||||
|
pub taker_side: Side, // side from the taker's POV
|
||||||
|
pub maker_slot: u8,
|
||||||
|
pub maker_out: bool, // true if maker order quantity == 0
|
||||||
|
pub version: u8,
|
||||||
|
pub market_fees_applied: bool,
|
||||||
|
pub padding: [u8; 2],
|
||||||
|
pub timestamp: u64,
|
||||||
|
pub seq_num: usize, // note: usize same as u64
|
||||||
|
|
||||||
// // The best bid/ask at the time the maker order was placed. Used for liquidity incentives
|
pub maker: Pubkey,
|
||||||
// pub best_initial: i64,
|
pub maker_order_id: i128,
|
||||||
|
pub maker_client_order_id: u64,
|
||||||
|
pub maker_fee: I80F48,
|
||||||
|
|
||||||
// // Timestamp of when the maker order was placed; copied over from the LeafNode
|
// The best bid/ask at the time the maker order was placed. Used for liquidity incentives
|
||||||
// pub maker_timestamp: u64,
|
pub best_initial: i64,
|
||||||
|
|
||||||
// pub taker: Pubkey,
|
// Timestamp of when the maker order was placed; copied over from the LeafNode
|
||||||
// pub taker_order_id: i128,
|
pub maker_timestamp: u64,
|
||||||
// pub taker_client_order_id: u64,
|
|
||||||
// pub taker_fee: I80F48,
|
|
||||||
|
|
||||||
// pub price: i64,
|
pub taker: Pubkey,
|
||||||
// pub quantity: i64, // number of quote lots
|
pub taker_order_id: i128,
|
||||||
// }
|
pub taker_client_order_id: u64,
|
||||||
// // unsafe impl TriviallyTransmutable for FillEvent {}
|
pub taker_fee: I80F48,
|
||||||
|
|
||||||
// impl FillEvent {
|
pub price: i64,
|
||||||
// pub fn new(
|
pub quantity: i64, // number of quote lots
|
||||||
// taker_side: Side,
|
}
|
||||||
// maker_slot: u8,
|
// unsafe impl TriviallyTransmutable for FillEvent {}
|
||||||
// maker_out: bool,
|
|
||||||
// timestamp: u64,
|
|
||||||
// seq_num: usize,
|
|
||||||
// maker: Pubkey,
|
|
||||||
// maker_order_id: i128,
|
|
||||||
// maker_client_order_id: u64,
|
|
||||||
// maker_fee: I80F48,
|
|
||||||
// best_initial: i64,
|
|
||||||
// maker_timestamp: u64,
|
|
||||||
|
|
||||||
// taker: Pubkey,
|
impl FillEvent {
|
||||||
// taker_order_id: i128,
|
pub fn new(
|
||||||
// taker_client_order_id: u64,
|
taker_side: Side,
|
||||||
// taker_fee: I80F48,
|
maker_slot: u8,
|
||||||
// price: i64,
|
maker_out: bool,
|
||||||
// quantity: i64,
|
timestamp: u64,
|
||||||
// version: u8,
|
seq_num: usize,
|
||||||
// ) -> FillEvent {
|
maker: Pubkey,
|
||||||
// Self {
|
maker_order_id: i128,
|
||||||
// event_type: EventType::Fill as u8,
|
maker_client_order_id: u64,
|
||||||
// taker_side,
|
maker_fee: I80F48,
|
||||||
// maker_slot,
|
best_initial: i64,
|
||||||
// maker_out,
|
maker_timestamp: u64,
|
||||||
// version,
|
|
||||||
// market_fees_applied: true, // Since mango v3.3.5, market fees are adjusted at matching time
|
|
||||||
// padding: [0u8; 2],
|
|
||||||
// timestamp,
|
|
||||||
// seq_num,
|
|
||||||
// maker,
|
|
||||||
// maker_order_id,
|
|
||||||
// maker_client_order_id,
|
|
||||||
// maker_fee,
|
|
||||||
// best_initial,
|
|
||||||
// maker_timestamp,
|
|
||||||
// taker,
|
|
||||||
// taker_order_id,
|
|
||||||
// taker_client_order_id,
|
|
||||||
// taker_fee,
|
|
||||||
// price,
|
|
||||||
// quantity,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub fn base_quote_change(&self, side: Side) -> (i64, i64) {
|
taker: Pubkey,
|
||||||
// match side {
|
taker_order_id: i128,
|
||||||
// Side::Bid => (
|
taker_client_order_id: u64,
|
||||||
// self.quantity,
|
taker_fee: I80F48,
|
||||||
// -self.price.checked_mul(self.quantity).unwrap(),
|
price: i64,
|
||||||
// ),
|
quantity: i64,
|
||||||
// Side::Ask => (
|
version: u8,
|
||||||
// -self.quantity,
|
) -> FillEvent {
|
||||||
// self.price.checked_mul(self.quantity).unwrap(),
|
Self {
|
||||||
// ),
|
event_type: EventType::Fill as u8,
|
||||||
// }
|
taker_side,
|
||||||
// }
|
maker_slot,
|
||||||
|
maker_out,
|
||||||
|
version,
|
||||||
|
market_fees_applied: true, // Since mango v3.3.5, market fees are adjusted at matching time
|
||||||
|
padding: [0u8; 2],
|
||||||
|
timestamp,
|
||||||
|
seq_num,
|
||||||
|
maker,
|
||||||
|
maker_order_id,
|
||||||
|
maker_client_order_id,
|
||||||
|
maker_fee,
|
||||||
|
best_initial,
|
||||||
|
maker_timestamp,
|
||||||
|
taker,
|
||||||
|
taker_order_id,
|
||||||
|
taker_client_order_id,
|
||||||
|
taker_fee,
|
||||||
|
price,
|
||||||
|
quantity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// // pub fn to_fill_log(&self, mango_group: Pubkey, market_index: usize) -> FillLog {
|
pub fn base_quote_change(&self, side: Side) -> (i64, i64) {
|
||||||
// // FillLog {
|
match side {
|
||||||
// // mango_group,
|
Side::Bid => (
|
||||||
// // market_index: market_index as u64,
|
self.quantity,
|
||||||
// // taker_side: self.taker_side as u8,
|
-self.price.checked_mul(self.quantity).unwrap(),
|
||||||
// // maker_slot: self.maker_slot,
|
),
|
||||||
// // maker_out: self.maker_out,
|
Side::Ask => (
|
||||||
// // timestamp: self.timestamp,
|
-self.quantity,
|
||||||
// // seq_num: self.seq_num as u64,
|
self.price.checked_mul(self.quantity).unwrap(),
|
||||||
// // maker: self.maker,
|
),
|
||||||
// // maker_order_id: self.maker_order_id,
|
}
|
||||||
// // maker_client_order_id: self.maker_client_order_id,
|
}
|
||||||
// // maker_fee: self.maker_fee.to_bits(),
|
|
||||||
// // best_initial: self.best_initial,
|
|
||||||
// // maker_timestamp: self.maker_timestamp,
|
|
||||||
// // taker: self.taker,
|
|
||||||
// // taker_order_id: self.taker_order_id,
|
|
||||||
// // taker_client_order_id: self.taker_client_order_id,
|
|
||||||
// // taker_fee: self.taker_fee.to_bits(),
|
|
||||||
// // price: self.price,
|
|
||||||
// // quantity: self.quantity,
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[derive(Copy, Clone, Debug, Pod)]
|
// pub fn to_fill_log(&self, mango_group: Pubkey, market_index: usize) -> FillLog {
|
||||||
// #[repr(C)]
|
// FillLog {
|
||||||
// pub struct OutEvent {
|
// mango_group,
|
||||||
// pub event_type: u8,
|
// market_index: market_index as u64,
|
||||||
// pub side: Side,
|
// taker_side: self.taker_side as u8,
|
||||||
// pub slot: u8,
|
// maker_slot: self.maker_slot,
|
||||||
// padding0: [u8; 5],
|
// maker_out: self.maker_out,
|
||||||
// pub timestamp: u64,
|
// timestamp: self.timestamp,
|
||||||
// pub seq_num: usize,
|
// seq_num: self.seq_num as u64,
|
||||||
// pub owner: Pubkey,
|
// maker: self.maker,
|
||||||
// pub quantity: i64,
|
// maker_order_id: self.maker_order_id,
|
||||||
// padding1: [u8; EVENT_SIZE - 64],
|
// maker_client_order_id: self.maker_client_order_id,
|
||||||
// }
|
// maker_fee: self.maker_fee.to_bits(),
|
||||||
// // unsafe impl TriviallyTransmutable for OutEvent {}
|
// best_initial: self.best_initial,
|
||||||
// impl OutEvent {
|
// maker_timestamp: self.maker_timestamp,
|
||||||
// pub fn new(
|
// taker: self.taker,
|
||||||
// side: Side,
|
// taker_order_id: self.taker_order_id,
|
||||||
// slot: u8,
|
// taker_client_order_id: self.taker_client_order_id,
|
||||||
// timestamp: u64,
|
// taker_fee: self.taker_fee.to_bits(),
|
||||||
// seq_num: usize,
|
// price: self.price,
|
||||||
// owner: Pubkey,
|
// quantity: self.quantity,
|
||||||
// quantity: i64,
|
// }
|
||||||
// ) -> Self {
|
// }
|
||||||
// Self {
|
}
|
||||||
// event_type: EventType::Out.into(),
|
|
||||||
// side,
|
|
||||||
// slot,
|
|
||||||
// padding0: [0; 5],
|
|
||||||
// timestamp,
|
|
||||||
// seq_num,
|
|
||||||
// owner,
|
|
||||||
// quantity,
|
|
||||||
// padding1: [0; EVENT_SIZE - 64],
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[derive(Copy, Clone, Debug, Pod)]
|
#[derive(Copy, Clone, Debug, Pod)]
|
||||||
// #[repr(C)]
|
#[repr(C)]
|
||||||
// /// Liquidation for the PerpMarket this EventQueue is for
|
pub struct OutEvent {
|
||||||
// pub struct LiquidateEvent {
|
pub event_type: u8,
|
||||||
// pub event_type: u8,
|
pub side: Side,
|
||||||
// padding0: [u8; 7],
|
pub slot: u8,
|
||||||
// pub timestamp: u64,
|
padding0: [u8; 5],
|
||||||
// pub seq_num: usize,
|
pub timestamp: u64,
|
||||||
// pub liqee: Pubkey,
|
pub seq_num: usize,
|
||||||
// pub liqor: Pubkey,
|
pub owner: Pubkey,
|
||||||
// pub price: I80F48, // oracle price at the time of liquidation
|
pub quantity: i64,
|
||||||
// pub quantity: i64, // number of contracts that were moved from liqee to liqor
|
padding1: [u8; EVENT_SIZE - 64],
|
||||||
// pub liquidation_fee: I80F48, // liq fee for this earned for this market
|
}
|
||||||
// padding1: [u8; EVENT_SIZE - 128],
|
// unsafe impl TriviallyTransmutable for OutEvent {}
|
||||||
// }
|
impl OutEvent {
|
||||||
// // unsafe impl TriviallyTransmutable for LiquidateEvent {}
|
pub fn new(
|
||||||
// impl LiquidateEvent {
|
side: Side,
|
||||||
// pub fn new(
|
slot: u8,
|
||||||
// timestamp: u64,
|
timestamp: u64,
|
||||||
// seq_num: usize,
|
seq_num: usize,
|
||||||
// liqee: Pubkey,
|
owner: Pubkey,
|
||||||
// liqor: Pubkey,
|
quantity: i64,
|
||||||
// price: I80F48,
|
) -> Self {
|
||||||
// quantity: i64,
|
Self {
|
||||||
// liquidation_fee: I80F48,
|
event_type: EventType::Out.into(),
|
||||||
// ) -> Self {
|
side,
|
||||||
// Self {
|
slot,
|
||||||
// event_type: EventType::Liquidate.into(),
|
padding0: [0; 5],
|
||||||
// padding0: [0u8; 7],
|
timestamp,
|
||||||
// timestamp,
|
seq_num,
|
||||||
// seq_num,
|
owner,
|
||||||
// liqee,
|
quantity,
|
||||||
// liqor,
|
padding1: [0; EVENT_SIZE - 64],
|
||||||
// price,
|
}
|
||||||
// quantity,
|
}
|
||||||
// liquidation_fee,
|
}
|
||||||
// padding1: [0u8; EVENT_SIZE - 128],
|
|
||||||
// }
|
#[derive(Copy, Clone, Debug, Pod)]
|
||||||
// }
|
#[repr(C)]
|
||||||
// }
|
/// Liquidation for the PerpMarket this EventQueue is for
|
||||||
// const_assert_eq!(size_of::<AnyEvent>(), size_of::<FillEvent>());
|
pub struct LiquidateEvent {
|
||||||
// const_assert_eq!(size_of::<AnyEvent>(), size_of::<OutEvent>());
|
pub event_type: u8,
|
||||||
// const_assert_eq!(size_of::<AnyEvent>(), size_of::<LiquidateEvent>());
|
padding0: [u8; 7],
|
||||||
|
pub timestamp: u64,
|
||||||
|
pub seq_num: usize,
|
||||||
|
pub liqee: Pubkey,
|
||||||
|
pub liqor: Pubkey,
|
||||||
|
pub price: I80F48, // oracle price at the time of liquidation
|
||||||
|
pub quantity: i64, // number of contracts that were moved from liqee to liqor
|
||||||
|
pub liquidation_fee: I80F48, // liq fee for this earned for this market
|
||||||
|
padding1: [u8; EVENT_SIZE - 128],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LiquidateEvent {
|
||||||
|
pub fn new(
|
||||||
|
timestamp: u64,
|
||||||
|
seq_num: usize,
|
||||||
|
liqee: Pubkey,
|
||||||
|
liqor: Pubkey,
|
||||||
|
price: I80F48,
|
||||||
|
quantity: i64,
|
||||||
|
liquidation_fee: I80F48,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
event_type: EventType::Liquidate.into(),
|
||||||
|
padding0: [0u8; 7],
|
||||||
|
timestamp,
|
||||||
|
seq_num,
|
||||||
|
liqee,
|
||||||
|
liqor,
|
||||||
|
price,
|
||||||
|
quantity,
|
||||||
|
liquidation_fee,
|
||||||
|
padding1: [0u8; EVENT_SIZE - 128],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const_assert_eq!(size_of::<AnyEvent>(), size_of::<FillEvent>());
|
||||||
|
const_assert_eq!(size_of::<AnyEvent>(), size_of::<OutEvent>());
|
||||||
|
const_assert_eq!(size_of::<AnyEvent>(), size_of::<LiquidateEvent>());
|
||||||
|
|
|
@ -5,9 +5,6 @@ use crate::state::TokenIndex;
|
||||||
|
|
||||||
pub type PerpMarketIndex = u16;
|
pub type PerpMarketIndex = u16;
|
||||||
|
|
||||||
#[account(zero_copy)]
|
|
||||||
pub struct EventQueue {}
|
|
||||||
|
|
||||||
#[account(zero_copy)]
|
#[account(zero_copy)]
|
||||||
pub struct PerpMarket {
|
pub struct PerpMarket {
|
||||||
pub group: Pubkey,
|
pub group: Pubkey,
|
||||||
|
@ -17,8 +14,7 @@ pub struct PerpMarket {
|
||||||
pub bids: Pubkey,
|
pub bids: Pubkey,
|
||||||
pub asks: Pubkey,
|
pub asks: Pubkey,
|
||||||
|
|
||||||
/// Event queue of TODO
|
pub event_queue: Pubkey,
|
||||||
/// pub event_queue: Pubkey,
|
|
||||||
|
|
||||||
/// Number of quote native that reresents min tick
|
/// Number of quote native that reresents min tick
|
||||||
/// e.g. when base lot size is 100, and quote lot size is 10, then tick i.e. price increment is 10/100 i.e. 0.1
|
/// e.g. when base lot size is 100, and quote lot size is 10, then tick i.e. price increment is 10/100 i.e. 0.1
|
||||||
|
|
|
@ -12,6 +12,7 @@ macro_rules! zip {
|
||||||
zip!($($y), +))
|
zip!($($y), +))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
#[allow(unused_imports)]
|
||||||
pub(crate) use zip;
|
pub(crate) use zip;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|
|
@ -1086,6 +1086,7 @@ pub struct CreatePerpMarketInstruction<'keypair> {
|
||||||
pub oracle: Pubkey,
|
pub oracle: Pubkey,
|
||||||
pub asks: Pubkey,
|
pub asks: Pubkey,
|
||||||
pub bids: Pubkey,
|
pub bids: Pubkey,
|
||||||
|
pub event_queue: Pubkey,
|
||||||
pub payer: &'keypair Keypair,
|
pub payer: &'keypair Keypair,
|
||||||
pub perp_market_index: PerpMarketIndex,
|
pub perp_market_index: PerpMarketIndex,
|
||||||
pub base_token_index: TokenIndex,
|
pub base_token_index: TokenIndex,
|
||||||
|
@ -1127,6 +1128,7 @@ impl<'keypair> ClientInstruction for CreatePerpMarketInstruction<'keypair> {
|
||||||
perp_market,
|
perp_market,
|
||||||
asks: self.asks,
|
asks: self.asks,
|
||||||
bids: self.bids,
|
bids: self.bids,
|
||||||
|
event_queue: self.event_queue,
|
||||||
payer: self.payer.pubkey(),
|
payer: self.payer.pubkey(),
|
||||||
system_program: System::id(),
|
system_program: System::id(),
|
||||||
};
|
};
|
||||||
|
@ -1146,6 +1148,7 @@ pub struct PlacePerpOrderInstruction<'keypair> {
|
||||||
pub perp_market: Pubkey,
|
pub perp_market: Pubkey,
|
||||||
pub asks: Pubkey,
|
pub asks: Pubkey,
|
||||||
pub bids: Pubkey,
|
pub bids: Pubkey,
|
||||||
|
pub event_queue: Pubkey,
|
||||||
pub oracle: Pubkey,
|
pub oracle: Pubkey,
|
||||||
pub owner: &'keypair Keypair,
|
pub owner: &'keypair Keypair,
|
||||||
}
|
}
|
||||||
|
@ -1173,6 +1176,7 @@ impl<'keypair> ClientInstruction for PlacePerpOrderInstruction<'keypair> {
|
||||||
perp_market: self.perp_market,
|
perp_market: self.perp_market,
|
||||||
asks: self.asks,
|
asks: self.asks,
|
||||||
bids: self.bids,
|
bids: self.bids,
|
||||||
|
event_queue: self.event_queue,
|
||||||
oracle: self.oracle,
|
oracle: self.oracle,
|
||||||
owner: self.owner.pubkey(),
|
owner: self.owner.pubkey(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -83,7 +83,23 @@ impl SolanaCookie {
|
||||||
.newest()
|
.newest()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_account<T>(&self, owner: &Pubkey) -> Pubkey {
|
pub async fn create_account_from_len(&self, owner: &Pubkey, len: usize) -> Pubkey {
|
||||||
|
let key = Keypair::new();
|
||||||
|
let rent = self.rent.minimum_balance(len);
|
||||||
|
let create_account_instr = solana_sdk::system_instruction::create_account(
|
||||||
|
&self.context.borrow().payer.pubkey(),
|
||||||
|
&key.pubkey(),
|
||||||
|
rent,
|
||||||
|
len as u64,
|
||||||
|
&owner,
|
||||||
|
);
|
||||||
|
self.process_transaction(&[create_account_instr], Some(&[&key]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
key.pubkey()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_account_for_type<T>(&self, owner: &Pubkey) -> Pubkey {
|
||||||
let key = Keypair::new();
|
let key = Keypair::new();
|
||||||
let len = 8 + std::mem::size_of::<T>();
|
let len = 8 + std::mem::size_of::<T>();
|
||||||
let rent = self.rent.minimum_balance(len);
|
let rent = self.rent.minimum_balance(len);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#![cfg(feature = "test-bpf")]
|
#![cfg(feature = "test-bpf")]
|
||||||
|
|
||||||
use mango_v4::state::BookSide;
|
use mango_v4::state::*;
|
||||||
use solana_program_test::*;
|
use solana_program_test::*;
|
||||||
use solana_sdk::{signature::Keypair, transport::TransportError};
|
use solana_sdk::{signature::Keypair, transport::TransportError};
|
||||||
|
|
||||||
|
@ -82,6 +82,7 @@ async fn test_perp() -> Result<(), TransportError> {
|
||||||
perp_market,
|
perp_market,
|
||||||
asks,
|
asks,
|
||||||
bids,
|
bids,
|
||||||
|
event_queue,
|
||||||
..
|
..
|
||||||
} = send_tx(
|
} = send_tx(
|
||||||
solana,
|
solana,
|
||||||
|
@ -90,12 +91,20 @@ async fn test_perp() -> Result<(), TransportError> {
|
||||||
oracle: tokens[0].oracle,
|
oracle: tokens[0].oracle,
|
||||||
asks: context
|
asks: context
|
||||||
.solana
|
.solana
|
||||||
.create_account::<BookSide>(&mango_v4::id())
|
.create_account_for_type::<BookSide>(&mango_v4::id())
|
||||||
.await,
|
.await,
|
||||||
bids: context
|
bids: context
|
||||||
.solana
|
.solana
|
||||||
.create_account::<BookSide>(&mango_v4::id())
|
.create_account_for_type::<BookSide>(&mango_v4::id())
|
||||||
.await,
|
.await,
|
||||||
|
event_queue: {
|
||||||
|
let len = std::mem::size_of::<queue::EventQueue>()
|
||||||
|
+ std::mem::size_of::<AnyEvent>() * MAX_NUM_EVENTS;
|
||||||
|
context
|
||||||
|
.solana
|
||||||
|
.create_account_from_len(&mango_v4::id(), len)
|
||||||
|
.await
|
||||||
|
},
|
||||||
admin,
|
admin,
|
||||||
payer,
|
payer,
|
||||||
perp_market_index: 0,
|
perp_market_index: 0,
|
||||||
|
@ -117,6 +126,7 @@ async fn test_perp() -> Result<(), TransportError> {
|
||||||
perp_market,
|
perp_market,
|
||||||
asks,
|
asks,
|
||||||
bids,
|
bids,
|
||||||
|
event_queue,
|
||||||
oracle: tokens[0].oracle,
|
oracle: tokens[0].oracle,
|
||||||
owner,
|
owner,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue