2022-03-20 02:11:36 -07:00
|
|
|
use anchor_lang::prelude::*;
|
|
|
|
|
2022-06-08 04:43:12 -07:00
|
|
|
use crate::accounts_zerocopy::*;
|
2022-03-30 01:00:52 -07:00
|
|
|
use crate::error::*;
|
2022-03-24 09:29:30 -07:00
|
|
|
use crate::state::{
|
2022-06-22 03:45:42 -07:00
|
|
|
compute_health_from_fixed_accounts, oracle_price, Book, BookSide, EventQueue, Group,
|
|
|
|
HealthType, MangoAccount, OrderType, PerpMarket, Side,
|
2022-03-24 09:29:30 -07:00
|
|
|
};
|
2022-03-21 12:29:28 -07:00
|
|
|
|
2022-03-20 02:11:36 -07:00
|
|
|
#[derive(Accounts)]
|
2022-03-31 23:01:56 -07:00
|
|
|
pub struct PerpPlaceOrder<'info> {
|
2022-03-21 12:29:28 -07:00
|
|
|
pub group: AccountLoader<'info, Group>,
|
|
|
|
|
|
|
|
#[account(
|
|
|
|
mut,
|
|
|
|
has_one = group,
|
|
|
|
has_one = owner,
|
|
|
|
)]
|
|
|
|
pub account: AccountLoader<'info, MangoAccount>,
|
|
|
|
|
|
|
|
#[account(
|
|
|
|
mut,
|
|
|
|
has_one = group,
|
|
|
|
has_one = bids,
|
|
|
|
has_one = asks,
|
2022-03-28 12:13:16 -07:00
|
|
|
has_one = event_queue,
|
2022-03-21 12:29:28 -07:00
|
|
|
has_one = oracle,
|
|
|
|
)]
|
|
|
|
pub perp_market: AccountLoader<'info, PerpMarket>,
|
|
|
|
#[account(mut)]
|
2022-06-22 03:45:42 -07:00
|
|
|
pub asks: AccountLoader<'info, BookSide>,
|
2022-03-21 12:29:28 -07:00
|
|
|
#[account(mut)]
|
2022-06-22 03:45:42 -07:00
|
|
|
pub bids: AccountLoader<'info, BookSide>,
|
2022-03-24 06:40:08 -07:00
|
|
|
#[account(mut)]
|
2022-05-18 08:16:14 -07:00
|
|
|
pub event_queue: AccountLoader<'info, EventQueue>,
|
2022-03-24 06:40:08 -07:00
|
|
|
|
2022-06-22 03:45:42 -07:00
|
|
|
/// CHECK: The oracle can be one of several different account types and the pubkey is checked above
|
2022-03-21 12:29:28 -07:00
|
|
|
pub oracle: UncheckedAccount<'info>,
|
|
|
|
|
|
|
|
pub owner: Signer<'info>,
|
|
|
|
}
|
|
|
|
|
2022-03-22 03:19:12 -07:00
|
|
|
// TODO
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
2022-03-31 23:01:56 -07:00
|
|
|
pub fn perp_place_order(
|
|
|
|
ctx: Context<PerpPlaceOrder>,
|
2022-03-26 09:06:55 -07:00
|
|
|
side: Side,
|
2022-04-01 06:47:12 -07:00
|
|
|
|
|
|
|
// Price in quote lots per base lots.
|
|
|
|
//
|
|
|
|
// Effect is based on order type, it's usually
|
|
|
|
// - fill orders on the book up to this price or
|
|
|
|
// - place an order on the book at this price.
|
|
|
|
//
|
|
|
|
// Ignored for Market orders and potentially adjusted for PostOnlySlide orders.
|
|
|
|
price_lots: i64,
|
|
|
|
|
|
|
|
// Max base lots to buy/sell.
|
|
|
|
max_base_lots: i64,
|
|
|
|
|
|
|
|
// Max quote lots to pay/receive (not taking fees into account).
|
|
|
|
max_quote_lots: i64,
|
|
|
|
|
|
|
|
// Arbitrary user-controlled order id.
|
2022-03-21 12:29:28 -07:00
|
|
|
client_order_id: u64,
|
2022-04-01 06:47:12 -07:00
|
|
|
|
2022-03-21 12:29:28 -07:00
|
|
|
order_type: OrderType,
|
2022-04-01 06:47:12 -07:00
|
|
|
|
|
|
|
// Timestamp of when order expires
|
|
|
|
//
|
|
|
|
// Send 0 if you want the order to never expire.
|
|
|
|
// Timestamps in the past mean the instruction is skipped.
|
|
|
|
// Timestamps in the future are reduced to now + 255s.
|
2022-03-21 12:29:28 -07:00
|
|
|
expiry_timestamp: u64,
|
2022-04-01 06:47:12 -07:00
|
|
|
|
|
|
|
// Maximum number of orders from the book to fill.
|
|
|
|
//
|
|
|
|
// Use this to limit compute used during order matching.
|
|
|
|
// When the limit is reached, processing stops and the instruction succeeds.
|
2022-03-21 12:29:28 -07:00
|
|
|
limit: u8,
|
|
|
|
) -> Result<()> {
|
2022-03-26 09:06:55 -07:00
|
|
|
let mut mango_account = ctx.accounts.account.load_mut()?;
|
2022-03-31 05:01:08 -07:00
|
|
|
require!(mango_account.is_bankrupt == 0, MangoError::IsBankrupt);
|
2022-03-21 12:29:28 -07:00
|
|
|
let mango_account_pk = ctx.accounts.account.key();
|
|
|
|
|
2022-05-25 10:29:53 -07:00
|
|
|
{
|
|
|
|
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
2022-06-22 03:45:42 -07:00
|
|
|
let bids = ctx.accounts.bids.load_mut()?;
|
|
|
|
let asks = ctx.accounts.asks.load_mut()?;
|
|
|
|
let mut book = Book::new(bids, asks);
|
2022-05-25 10:29:53 -07:00
|
|
|
|
|
|
|
let mut event_queue = ctx.accounts.event_queue.load_mut()?;
|
|
|
|
|
2022-06-02 01:36:04 -07:00
|
|
|
let oracle_price = oracle_price(
|
2022-06-08 04:43:12 -07:00
|
|
|
&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?,
|
2022-06-18 07:38:46 -07:00
|
|
|
perp_market.oracle_config.conf_filter,
|
2022-06-02 01:36:04 -07:00
|
|
|
perp_market.base_token_decimals,
|
|
|
|
)?;
|
2022-05-25 10:29:53 -07:00
|
|
|
|
|
|
|
let now_ts = Clock::get()?.unix_timestamp as u64;
|
|
|
|
let time_in_force = if expiry_timestamp != 0 {
|
|
|
|
// If expiry is far in the future, clamp to 255 seconds
|
|
|
|
let tif = expiry_timestamp.saturating_sub(now_ts).min(255);
|
|
|
|
if tif == 0 {
|
|
|
|
// If expiry is in the past, ignore the order
|
|
|
|
msg!("Order is already expired");
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
tif as u8
|
|
|
|
} else {
|
|
|
|
// Never expire
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
|
|
|
// TODO reduce_only based on event queue
|
|
|
|
|
|
|
|
book.new_order(
|
|
|
|
side,
|
|
|
|
&mut perp_market,
|
|
|
|
&mut event_queue,
|
|
|
|
oracle_price,
|
|
|
|
&mut mango_account.perps,
|
|
|
|
&mango_account_pk,
|
|
|
|
price_lots,
|
|
|
|
max_base_lots,
|
|
|
|
max_quote_lots,
|
|
|
|
order_type,
|
|
|
|
time_in_force,
|
|
|
|
client_order_id,
|
|
|
|
now_ts,
|
|
|
|
limit,
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let health = compute_health_from_fixed_accounts(
|
|
|
|
&mango_account,
|
|
|
|
HealthType::Init,
|
|
|
|
ctx.remaining_accounts,
|
2022-03-21 12:29:28 -07:00
|
|
|
)?;
|
2022-05-25 10:29:53 -07:00
|
|
|
msg!("health: {}", health);
|
|
|
|
require!(health >= 0, MangoError::HealthMustBePositive);
|
2022-03-20 02:11:36 -07:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|