From 0dc8ed38b24f7d40b44e70d5212cbbe74d6924e8 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Thu, 12 Oct 2023 16:56:57 -0400 Subject: [PATCH 1/3] new file structure --- programs/jit-proxy/src/error.rs | 14 + .../instructions/check_order_constraints.rs | 123 ++++++ programs/jit-proxy/src/instructions/jit.rs | 233 +++++++++++ programs/jit-proxy/src/instructions/mod.rs | 5 + programs/jit-proxy/src/lib.rs | 395 +----------------- programs/jit-proxy/src/state.rs | 42 ++ ts/sdk/src/types/jit_proxy.ts | 108 ++--- 7 files changed, 479 insertions(+), 441 deletions(-) create mode 100644 programs/jit-proxy/src/error.rs create mode 100644 programs/jit-proxy/src/instructions/check_order_constraints.rs create mode 100644 programs/jit-proxy/src/instructions/jit.rs create mode 100644 programs/jit-proxy/src/instructions/mod.rs create mode 100644 programs/jit-proxy/src/state.rs diff --git a/programs/jit-proxy/src/error.rs b/programs/jit-proxy/src/error.rs new file mode 100644 index 0000000..08e077b --- /dev/null +++ b/programs/jit-proxy/src/error.rs @@ -0,0 +1,14 @@ +use anchor_lang::prelude::*; + +#[error_code] +#[derive(PartialEq, Eq)] +pub enum ErrorCode { + #[msg("BidNotCrossed")] + BidNotCrossed, + #[msg("AskNotCrossed")] + AskNotCrossed, + #[msg("TakerOrderNotFound")] + TakerOrderNotFound, + #[msg("OrderSizeBreached")] + OrderSizeBreached, +} diff --git a/programs/jit-proxy/src/instructions/check_order_constraints.rs b/programs/jit-proxy/src/instructions/check_order_constraints.rs new file mode 100644 index 0000000..dde738c --- /dev/null +++ b/programs/jit-proxy/src/instructions/check_order_constraints.rs @@ -0,0 +1,123 @@ +use anchor_lang::prelude::*; +use drift::instructions::optional_accounts::{load_maps, AccountMaps}; +use drift::math::casting::Cast; +use drift::math::safe_math::SafeMath; +use drift::state::perp_market_map::MarketSet; +use drift::state::user::User; + +use crate::error::ErrorCode; +use crate::state::MarketType; + +pub fn check_order_constraints<'info>( + ctx: Context<'_, '_, '_, 'info, CheckOrderConstraints<'info>>, + constraints: Vec, +) -> Result<()> { + let clock = Clock::get()?; + let slot = clock.slot; + + let user = ctx.accounts.user.load()?; + + let remaining_accounts_iter = &mut ctx.remaining_accounts.iter().peekable(); + let AccountMaps { + perp_market_map, + spot_market_map, + mut oracle_map, + } = load_maps( + remaining_accounts_iter, + &MarketSet::new(), + &MarketSet::new(), + slot, + None, + )?; + + for constraint in constraints.iter() { + if constraint.market_type == MarketType::Spot { + let spot_market = spot_market_map.get_ref(&constraint.market_index)?; + let spot_position = match user.get_spot_position(constraint.market_index) { + Ok(spot_position) => spot_position, + Err(_) => continue, + }; + + let signed_token_amount = spot_position + .get_signed_token_amount(&spot_market)? + .cast::()?; + + constraint.check( + signed_token_amount, + spot_position.open_bids, + spot_position.open_asks, + )?; + } else { + let perp_market = perp_market_map.get_ref(&constraint.market_index)?; + let perp_position = match user.get_perp_position(constraint.market_index) { + Ok(perp_position) => perp_position, + Err(_) => continue, + }; + + let oracle_price = oracle_map.get_price_data(&perp_market.amm.oracle)?.price; + + let settled_perp_position = + perp_position.simulate_settled_lp_position(&perp_market, oracle_price)?; + + constraint.check( + settled_perp_position.base_asset_amount, + settled_perp_position.open_bids, + settled_perp_position.open_asks, + )?; + } + } + + Ok(()) +} + +#[derive(Accounts)] +pub struct CheckOrderConstraints<'info> { + pub user: AccountLoader<'info, User>, +} + +#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] +pub struct OrderConstraint { + pub max_position: i64, + pub min_position: i64, + pub market_index: u16, + pub market_type: MarketType, +} + +impl OrderConstraint { + pub fn check(&self, current_position: i64, open_bids: i64, open_asks: i64) -> Result<()> { + let max_long = current_position.safe_add(open_bids)?; + + if max_long > self.max_position { + msg!( + "market index {} market type {:?}", + self.market_index, + self.market_type + ); + msg!( + "max long {} current position {} open bids {}", + max_long, + current_position, + open_bids + ); + return Err(ErrorCode::OrderSizeBreached.into()); + } + + let max_short = current_position.safe_add(open_asks)?; + if max_short < self.min_position { + msg!( + "market index {} market type {:?}", + self.market_index, + self.market_type + ); + msg!( + "max short {} current position {} open asks {}", + max_short, + current_position, + open_asks + ); + return Err(ErrorCode::OrderSizeBreached.into()); + } + + Ok(()) + } +} diff --git a/programs/jit-proxy/src/instructions/jit.rs b/programs/jit-proxy/src/instructions/jit.rs new file mode 100644 index 0000000..c63aacf --- /dev/null +++ b/programs/jit-proxy/src/instructions/jit.rs @@ -0,0 +1,233 @@ +use anchor_lang::prelude::*; +use drift::controller::position::PositionDirection; +use drift::cpi::accounts::PlaceAndMake; +use drift::error::DriftResult; +use drift::instructions::optional_accounts::{load_maps, AccountMaps}; +use drift::instructions::OrderParams; +use drift::math::casting::Cast; +use drift::math::safe_math::SafeMath; +use drift::program::Drift; +use drift::state::perp_market_map::MarketSet; +use drift::state::state::State; +use drift::state::user::{MarketType as DriftMarketType, OrderTriggerCondition, OrderType}; +use drift::state::user::{User, UserStats}; + +use crate::error::ErrorCode; +use crate::state::{PostOnlyParam, PriceType}; + +pub fn jit<'info>(ctx: Context<'_, '_, '_, 'info, Jit<'info>>, params: JitParams) -> Result<()> { + let clock = Clock::get()?; + let slot = clock.slot; + + let taker = ctx.accounts.taker.load()?; + let maker = ctx.accounts.user.load()?; + + let taker_order = taker + .get_order(params.taker_order_id) + .ok_or(ErrorCode::TakerOrderNotFound)?; + let market_type = taker_order.market_type; + let market_index = taker_order.market_index; + let taker_direction = taker_order.direction; + + let remaining_accounts_iter = &mut ctx.remaining_accounts.iter().peekable(); + let AccountMaps { + perp_market_map, + spot_market_map, + mut oracle_map, + } = load_maps( + remaining_accounts_iter, + &MarketSet::new(), + &MarketSet::new(), + slot, + None, + )?; + + let (oracle_price, tick_size) = if market_type == DriftMarketType::Perp { + let perp_market = perp_market_map.get_ref(&market_index)?; + let oracle_price = oracle_map.get_price_data(&perp_market.amm.oracle)?.price; + + (oracle_price, perp_market.amm.order_tick_size) + } else { + let spot_market = spot_market_map.get_ref(&market_index)?; + let oracle_price = oracle_map.get_price_data(&spot_market.oracle)?.price; + + (oracle_price, spot_market.order_tick_size) + }; + + let taker_price = + taker_order.force_get_limit_price(Some(oracle_price), None, slot, tick_size)?; + + let maker_direction = taker_direction.opposite(); + let maker_worst_price = params.get_worst_price(oracle_price, taker_direction)?; + match maker_direction { + PositionDirection::Long => { + if taker_price > maker_worst_price { + msg!( + "taker price {} > worst bid {}", + taker_price, + maker_worst_price + ); + return Err(ErrorCode::BidNotCrossed.into()); + } + } + PositionDirection::Short => { + if taker_price < maker_worst_price { + msg!( + "taker price {} < worst ask {}", + taker_price, + maker_worst_price + ); + return Err(ErrorCode::AskNotCrossed.into()); + } + } + } + let maker_price = taker_price; + + let taker_base_asset_amount_unfilled = taker_order.get_base_asset_amount_unfilled(None)?; + let maker_existing_position = if market_type == DriftMarketType::Perp { + let perp_market = perp_market_map.get_ref(&market_index)?; + let perp_position = maker.get_perp_position(market_index); + match perp_position { + Ok(perp_position) => { + perp_position + .simulate_settled_lp_position(&perp_market, oracle_price)? + .base_asset_amount + } + Err(_) => 0, + } + } else { + let spot_market = spot_market_map.get_ref(&market_index)?; + maker + .get_spot_position(market_index) + .map_or(0, |p| p.get_signed_token_amount(&spot_market).unwrap()) + .cast::()? + }; + + let maker_base_asset_amount = if maker_direction == PositionDirection::Long { + let size = params.max_position.safe_sub(maker_existing_position)?; + + if size <= 0 { + msg!( + "maker existing position {} >= max position {}", + maker_existing_position, + params.max_position + ); + } + + size.unsigned_abs().min(taker_base_asset_amount_unfilled) + } else { + let size = maker_existing_position.safe_sub(params.min_position)?; + + if size <= 0 { + msg!( + "maker existing position {} <= max position {}", + maker_existing_position, + params.max_position + ); + } + + size.unsigned_abs().min(taker_base_asset_amount_unfilled) + }; + + let order_params = OrderParams { + order_type: OrderType::Limit, + market_type, + direction: maker_direction, + user_order_id: 0, + base_asset_amount: maker_base_asset_amount, + price: maker_price, + market_index, + reduce_only: false, + post_only: params + .post_only + .unwrap_or(PostOnlyParam::MustPostOnly) + .to_drift_param(), + immediate_or_cancel: true, + max_ts: None, + trigger_price: None, + trigger_condition: OrderTriggerCondition::Above, + oracle_price_offset: None, + auction_duration: None, + auction_start_price: None, + auction_end_price: None, + }; + + drop(taker); + drop(maker); + + place_and_make(ctx, params.taker_order_id, order_params)?; + + Ok(()) +} + +#[derive(Accounts)] +pub struct Jit<'info> { + pub state: Box>, + #[account(mut)] + pub user: AccountLoader<'info, User>, + #[account(mut)] + pub user_stats: AccountLoader<'info, UserStats>, + #[account(mut)] + pub taker: AccountLoader<'info, User>, + #[account(mut)] + pub taker_stats: AccountLoader<'info, UserStats>, + pub authority: Signer<'info>, + pub drift_program: Program<'info, Drift>, +} + +#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] +pub struct JitParams { + pub taker_order_id: u32, + pub max_position: i64, + pub min_position: i64, + pub bid: i64, + pub ask: i64, + pub price_type: PriceType, + pub post_only: Option, +} + +impl JitParams { + pub fn get_worst_price( + self, + oracle_price: i64, + taker_direction: PositionDirection, + ) -> DriftResult { + match (taker_direction, self.price_type) { + (PositionDirection::Long, PriceType::Limit) => Ok(self.ask.unsigned_abs()), + (PositionDirection::Short, PriceType::Limit) => Ok(self.bid.unsigned_abs()), + (PositionDirection::Long, PriceType::Oracle) => { + Ok(oracle_price.safe_add(self.ask)?.unsigned_abs()) + } + (PositionDirection::Short, PriceType::Oracle) => { + Ok(oracle_price.safe_add(self.bid)?.unsigned_abs()) + } + } + } +} + +fn place_and_make<'info>( + ctx: Context<'_, '_, '_, 'info, Jit<'info>>, + taker_order_id: u32, + order_params: OrderParams, +) -> Result<()> { + let drift_program = ctx.accounts.drift_program.to_account_info().clone(); + let cpi_accounts = PlaceAndMake { + state: ctx.accounts.state.to_account_info().clone(), + user: ctx.accounts.user.to_account_info().clone(), + user_stats: ctx.accounts.user_stats.to_account_info().clone(), + authority: ctx.accounts.authority.to_account_info().clone(), + taker: ctx.accounts.taker.to_account_info().clone(), + taker_stats: ctx.accounts.taker_stats.to_account_info().clone(), + }; + + let cpi_context = CpiContext::new(drift_program, cpi_accounts) + .with_remaining_accounts(ctx.remaining_accounts.into()); + + if order_params.market_type == DriftMarketType::Perp { + drift::cpi::place_and_make_perp_order(cpi_context, order_params, taker_order_id)?; + } else { + drift::cpi::place_and_make_spot_order(cpi_context, order_params, taker_order_id, None)?; + } + + Ok(()) +} diff --git a/programs/jit-proxy/src/instructions/mod.rs b/programs/jit-proxy/src/instructions/mod.rs new file mode 100644 index 0000000..309d8b9 --- /dev/null +++ b/programs/jit-proxy/src/instructions/mod.rs @@ -0,0 +1,5 @@ +mod check_order_constraints; +mod jit; + +pub use check_order_constraints::*; +pub use jit::*; diff --git a/programs/jit-proxy/src/lib.rs b/programs/jit-proxy/src/lib.rs index 23b7120..016408f 100644 --- a/programs/jit-proxy/src/lib.rs +++ b/programs/jit-proxy/src/lib.rs @@ -1,407 +1,28 @@ use anchor_lang::prelude::*; -use borsh::{BorshDeserialize, BorshSerialize}; -use drift::controller::position::PositionDirection; -use drift::cpi::accounts::PlaceAndMake; -use drift::error::DriftResult; -use drift::instructions::optional_accounts::{load_maps, AccountMaps}; -use drift::instructions::OrderParams; -use drift::instructions::PostOnlyParam as DriftPostOnlyParam; -use drift::math::safe_math::SafeMath; -use drift::program::Drift; -use drift::state::perp_market_map::MarketSet; -use drift::state::state::State; -use drift::state::user::{MarketType as DriftMarketType, OrderTriggerCondition, OrderType}; -use drift::state::user::{User, UserStats}; + +pub mod error; +pub mod instructions; +pub mod state; + +use instructions::*; declare_id!("J1TnP8zvVxbtF5KFp5xRmWuvG9McnhzmBd9XGfCyuxFP"); #[program] pub mod jit_proxy { use super::*; - use drift::math::casting::Cast; pub fn jit<'info>( ctx: Context<'_, '_, '_, 'info, Jit<'info>>, params: JitParams, ) -> Result<()> { - let clock = Clock::get()?; - let slot = clock.slot; - - let taker = ctx.accounts.taker.load()?; - let maker = ctx.accounts.user.load()?; - - let taker_order = taker - .get_order(params.taker_order_id) - .ok_or(ErrorCode::TakerOrderNotFound)?; - let market_type = taker_order.market_type; - let market_index = taker_order.market_index; - let taker_direction = taker_order.direction; - - let remaining_accounts_iter = &mut ctx.remaining_accounts.iter().peekable(); - let AccountMaps { - perp_market_map, - spot_market_map, - mut oracle_map, - } = load_maps( - remaining_accounts_iter, - &MarketSet::new(), - &MarketSet::new(), - slot, - None, - )?; - - let (oracle_price, tick_size) = if market_type == DriftMarketType::Perp { - let perp_market = perp_market_map.get_ref(&market_index)?; - let oracle_price = oracle_map.get_price_data(&perp_market.amm.oracle)?.price; - - (oracle_price, perp_market.amm.order_tick_size) - } else { - let spot_market = spot_market_map.get_ref(&market_index)?; - let oracle_price = oracle_map.get_price_data(&spot_market.oracle)?.price; - - (oracle_price, spot_market.order_tick_size) - }; - - let taker_price = - taker_order.force_get_limit_price(Some(oracle_price), None, slot, tick_size)?; - - let maker_direction = taker_direction.opposite(); - let maker_worst_price = params.get_worst_price(oracle_price, taker_direction)?; - match maker_direction { - PositionDirection::Long => { - if taker_price > maker_worst_price { - msg!( - "taker price {} > worst bid {}", - taker_price, - maker_worst_price - ); - return Err(ErrorCode::BidNotCrossed.into()); - } - } - PositionDirection::Short => { - if taker_price < maker_worst_price { - msg!( - "taker price {} < worst ask {}", - taker_price, - maker_worst_price - ); - return Err(ErrorCode::AskNotCrossed.into()); - } - } - } - let maker_price = taker_price; - - let taker_base_asset_amount_unfilled = taker_order.get_base_asset_amount_unfilled(None)?; - let maker_existing_position = if market_type == DriftMarketType::Perp { - let perp_market = perp_market_map.get_ref(&market_index)?; - let perp_position = maker.get_perp_position(market_index); - match perp_position { - Ok(perp_position) => { - perp_position - .simulate_settled_lp_position(&perp_market, oracle_price)? - .base_asset_amount - } - Err(_) => 0, - } - } else { - let spot_market = spot_market_map.get_ref(&market_index)?; - maker - .get_spot_position(market_index) - .map_or(0, |p| p.get_signed_token_amount(&spot_market).unwrap()) - .cast::()? - }; - - let maker_base_asset_amount = if maker_direction == PositionDirection::Long { - let size = params.max_position.safe_sub(maker_existing_position)?; - - if size <= 0 { - msg!( - "maker existing position {} >= max position {}", - maker_existing_position, - params.max_position - ); - } - - size.unsigned_abs().min(taker_base_asset_amount_unfilled) - } else { - let size = maker_existing_position.safe_sub(params.min_position)?; - - if size <= 0 { - msg!( - "maker existing position {} <= max position {}", - maker_existing_position, - params.max_position - ); - } - - size.unsigned_abs().min(taker_base_asset_amount_unfilled) - }; - - let order_params = OrderParams { - order_type: OrderType::Limit, - market_type, - direction: maker_direction, - user_order_id: 0, - base_asset_amount: maker_base_asset_amount, - price: maker_price, - market_index, - reduce_only: false, - post_only: params - .post_only - .unwrap_or(PostOnlyParam::MustPostOnly) - .to_drift_param(), - immediate_or_cancel: true, - max_ts: None, - trigger_price: None, - trigger_condition: OrderTriggerCondition::Above, - oracle_price_offset: None, - auction_duration: None, - auction_start_price: None, - auction_end_price: None, - }; - - drop(taker); - drop(maker); - - place_and_make(ctx, params.taker_order_id, order_params)?; - - Ok(()) + instructions::jit(ctx, params) } pub fn check_order_constraints<'info>( ctx: Context<'_, '_, '_, 'info, CheckOrderConstraints<'info>>, constraints: Vec, ) -> Result<()> { - let clock = Clock::get()?; - let slot = clock.slot; - - let user = ctx.accounts.user.load()?; - - let remaining_accounts_iter = &mut ctx.remaining_accounts.iter().peekable(); - let AccountMaps { - perp_market_map, - spot_market_map, - mut oracle_map, - } = load_maps( - remaining_accounts_iter, - &MarketSet::new(), - &MarketSet::new(), - slot, - None, - )?; - - for constraint in constraints.iter() { - if constraint.market_type == MarketType::Spot { - let spot_market = spot_market_map.get_ref(&constraint.market_index)?; - let spot_position = match user.get_spot_position(constraint.market_index) { - Ok(spot_position) => spot_position, - Err(_) => continue, - }; - - let signed_token_amount = spot_position - .get_signed_token_amount(&spot_market)? - .cast::()?; - - constraint.check( - signed_token_amount, - spot_position.open_bids, - spot_position.open_asks, - )?; - } else { - let perp_market = perp_market_map.get_ref(&constraint.market_index)?; - let perp_position = match user.get_perp_position(constraint.market_index) { - Ok(perp_position) => perp_position, - Err(_) => continue, - }; - - let oracle_price = oracle_map.get_price_data(&perp_market.amm.oracle)?.price; - - let settled_perp_position = - perp_position.simulate_settled_lp_position(&perp_market, oracle_price)?; - - constraint.check( - settled_perp_position.base_asset_amount, - settled_perp_position.open_bids, - settled_perp_position.open_asks, - )?; - } - } - - Ok(()) - } -} - -#[derive(Accounts)] -pub struct Jit<'info> { - pub state: Box>, - #[account(mut)] - pub user: AccountLoader<'info, User>, - #[account(mut)] - pub user_stats: AccountLoader<'info, UserStats>, - #[account(mut)] - pub taker: AccountLoader<'info, User>, - #[account(mut)] - pub taker_stats: AccountLoader<'info, UserStats>, - pub authority: Signer<'info>, - pub drift_program: Program<'info, Drift>, -} - -#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] -pub struct JitParams { - pub taker_order_id: u32, - pub max_position: i64, - pub min_position: i64, - pub bid: i64, - pub ask: i64, - pub price_type: PriceType, - pub post_only: Option, -} - -impl JitParams { - pub fn get_worst_price( - self, - oracle_price: i64, - taker_direction: PositionDirection, - ) -> DriftResult { - match (taker_direction, self.price_type) { - (PositionDirection::Long, PriceType::Limit) => Ok(self.ask.unsigned_abs()), - (PositionDirection::Short, PriceType::Limit) => Ok(self.bid.unsigned_abs()), - (PositionDirection::Long, PriceType::Oracle) => { - Ok(oracle_price.safe_add(self.ask)?.unsigned_abs()) - } - (PositionDirection::Short, PriceType::Oracle) => { - Ok(oracle_price.safe_add(self.bid)?.unsigned_abs()) - } - } + instructions::check_order_constraints(ctx, constraints) } } - -#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, PartialEq, Debug, Eq)] -pub enum PostOnlyParam { - None, - MustPostOnly, // Tx fails if order can't be post only - TryPostOnly, // Tx succeeds and order not placed if can't be post only -} - -impl PostOnlyParam { - pub fn to_drift_param(self) -> DriftPostOnlyParam { - match self { - PostOnlyParam::None => DriftPostOnlyParam::None, - PostOnlyParam::MustPostOnly => DriftPostOnlyParam::MustPostOnly, - PostOnlyParam::TryPostOnly => DriftPostOnlyParam::TryPostOnly, - } - } -} - -#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, PartialEq, Debug, Eq)] -pub enum PriceType { - Limit, - Oracle, -} - -#[derive(Accounts)] -pub struct CheckOrderConstraints<'info> { - pub user: AccountLoader<'info, User>, -} - -#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] -pub struct OrderConstraint { - pub max_position: i64, - pub min_position: i64, - pub market_index: u16, - pub market_type: MarketType, -} - -impl OrderConstraint { - pub fn check(&self, current_position: i64, open_bids: i64, open_asks: i64) -> Result<()> { - let max_long = current_position.safe_add(open_bids)?; - - if max_long > self.max_position { - msg!( - "market index {} market type {:?}", - self.market_index, - self.market_type - ); - msg!( - "max long {} current position {} open bids {}", - max_long, - current_position, - open_bids - ); - return Err(ErrorCode::OrderSizeBreached.into()); - } - - let max_short = current_position.safe_add(open_asks)?; - if max_short < self.min_position { - msg!( - "market index {} market type {:?}", - self.market_index, - self.market_type - ); - msg!( - "max short {} current position {} open asks {}", - max_short, - current_position, - open_asks - ); - return Err(ErrorCode::OrderSizeBreached.into()); - } - - Ok(()) - } -} - -#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, PartialEq, Debug, Eq)] -pub enum MarketType { - Perp, - Spot, -} - -impl MarketType { - pub fn to_drift_param(self) -> DriftMarketType { - match self { - MarketType::Spot => DriftMarketType::Spot, - MarketType::Perp => DriftMarketType::Perp, - } - } -} - -#[error_code] -#[derive(PartialEq, Eq)] -pub enum ErrorCode { - #[msg("BidNotCrossed")] - BidNotCrossed, - #[msg("AskNotCrossed")] - AskNotCrossed, - #[msg("TakerOrderNotFound")] - TakerOrderNotFound, - #[msg("OrderSizeBreached")] - OrderSizeBreached, -} - -fn place_and_make<'info>( - ctx: Context<'_, '_, '_, 'info, Jit<'info>>, - taker_order_id: u32, - order_params: OrderParams, -) -> Result<()> { - let drift_program = ctx.accounts.drift_program.to_account_info().clone(); - let cpi_accounts = PlaceAndMake { - state: ctx.accounts.state.to_account_info().clone(), - user: ctx.accounts.user.to_account_info().clone(), - user_stats: ctx.accounts.user_stats.to_account_info().clone(), - authority: ctx.accounts.authority.to_account_info().clone(), - taker: ctx.accounts.taker.to_account_info().clone(), - taker_stats: ctx.accounts.taker_stats.to_account_info().clone(), - }; - - let cpi_context = CpiContext::new(drift_program, cpi_accounts) - .with_remaining_accounts(ctx.remaining_accounts.into()); - - if order_params.market_type == DriftMarketType::Perp { - drift::cpi::place_and_make_perp_order(cpi_context, order_params, taker_order_id)?; - } else { - drift::cpi::place_and_make_spot_order(cpi_context, order_params, taker_order_id, None)?; - } - - Ok(()) -} diff --git a/programs/jit-proxy/src/state.rs b/programs/jit-proxy/src/state.rs new file mode 100644 index 0000000..4620cb7 --- /dev/null +++ b/programs/jit-proxy/src/state.rs @@ -0,0 +1,42 @@ +use anchor_lang::prelude::*; +use borsh::{BorshDeserialize, BorshSerialize}; +use drift::instructions::PostOnlyParam as DriftPostOnlyParam; +use drift::state::user::MarketType as DriftMarketType; + +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, PartialEq, Debug, Eq)] +pub enum PostOnlyParam { + None, + MustPostOnly, // Tx fails if order can't be post only + TryPostOnly, // Tx succeeds and order not placed if can't be post only +} + +impl PostOnlyParam { + pub fn to_drift_param(self) -> DriftPostOnlyParam { + match self { + PostOnlyParam::None => DriftPostOnlyParam::None, + PostOnlyParam::MustPostOnly => DriftPostOnlyParam::MustPostOnly, + PostOnlyParam::TryPostOnly => DriftPostOnlyParam::TryPostOnly, + } + } +} + +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, PartialEq, Debug, Eq)] +pub enum PriceType { + Limit, + Oracle, +} + +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, PartialEq, Debug, Eq)] +pub enum MarketType { + Perp, + Spot, +} + +impl MarketType { + pub fn to_drift_param(self) -> DriftMarketType { + match self { + MarketType::Spot => DriftMarketType::Spot, + MarketType::Perp => DriftMarketType::Perp, + } + } +} diff --git a/ts/sdk/src/types/jit_proxy.ts b/ts/sdk/src/types/jit_proxy.ts index 8fd557e..0086208 100644 --- a/ts/sdk/src/types/jit_proxy.ts +++ b/ts/sdk/src/types/jit_proxy.ts @@ -1,5 +1,5 @@ export type JitProxy = { - version: '0.10.3'; + version: '0.10.2'; name: 'jit_proxy'; instructions: [ { @@ -73,14 +73,10 @@ export type JitProxy = { ]; types: [ { - name: 'JitParams'; + name: 'OrderConstraint'; type: { kind: 'struct'; fields: [ - { - name: 'takerOrderId'; - type: 'u32'; - }, { name: 'maxPosition'; type: 'i64'; @@ -90,35 +86,27 @@ export type JitProxy = { type: 'i64'; }, { - name: 'bid'; - type: 'i64'; - }, - { - name: 'ask'; - type: 'i64'; - }, - { - name: 'priceType'; - type: { - defined: 'PriceType'; - }; + name: 'marketIndex'; + type: 'u16'; }, { - name: 'postOnly'; + name: 'marketType'; type: { - option: { - defined: 'PostOnlyParam'; - }; + defined: 'MarketType'; }; } ]; }; }, { - name: 'OrderConstraint'; + name: 'JitParams'; type: { kind: 'struct'; fields: [ + { + name: 'takerOrderId'; + type: 'u32'; + }, { name: 'maxPosition'; type: 'i64'; @@ -128,13 +116,25 @@ export type JitProxy = { type: 'i64'; }, { - name: 'marketIndex'; - type: 'u16'; + name: 'bid'; + type: 'i64'; }, { - name: 'marketType'; + name: 'ask'; + type: 'i64'; + }, + { + name: 'priceType'; type: { - defined: 'MarketType'; + defined: 'PriceType'; + }; + }, + { + name: 'postOnly'; + type: { + option: { + defined: 'PostOnlyParam'; + }; }; } ]; @@ -211,7 +211,7 @@ export type JitProxy = { }; export const IDL: JitProxy = { - version: '0.10.3', + version: '0.10.2', name: 'jit_proxy', instructions: [ { @@ -285,14 +285,10 @@ export const IDL: JitProxy = { ], types: [ { - name: 'JitParams', + name: 'OrderConstraint', type: { kind: 'struct', fields: [ - { - name: 'takerOrderId', - type: 'u32', - }, { name: 'maxPosition', type: 'i64', @@ -302,35 +298,27 @@ export const IDL: JitProxy = { type: 'i64', }, { - name: 'bid', - type: 'i64', - }, - { - name: 'ask', - type: 'i64', - }, - { - name: 'priceType', - type: { - defined: 'PriceType', - }, + name: 'marketIndex', + type: 'u16', }, { - name: 'postOnly', + name: 'marketType', type: { - option: { - defined: 'PostOnlyParam', - }, + defined: 'MarketType', }, }, ], }, }, { - name: 'OrderConstraint', + name: 'JitParams', type: { kind: 'struct', fields: [ + { + name: 'takerOrderId', + type: 'u32', + }, { name: 'maxPosition', type: 'i64', @@ -340,13 +328,25 @@ export const IDL: JitProxy = { type: 'i64', }, { - name: 'marketIndex', - type: 'u16', + name: 'bid', + type: 'i64', }, { - name: 'marketType', + name: 'ask', + type: 'i64', + }, + { + name: 'priceType', type: { - defined: 'MarketType', + defined: 'PriceType', + }, + }, + { + name: 'postOnly', + type: { + option: { + defined: 'PostOnlyParam', + }, }, }, ], From a0295ffdccc311bf6e9f307f9776de229643334e Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Thu, 12 Oct 2023 17:52:29 -0400 Subject: [PATCH 2/3] add arb_perp --- Cargo.lock | 280 +++++++++--------- programs/jit-proxy/Cargo.toml | 6 +- programs/jit-proxy/src/error.rs | 8 + .../jit-proxy/src/instructions/arb_perp.rs | 135 +++++++++ programs/jit-proxy/src/instructions/mod.rs | 2 + programs/jit-proxy/src/lib.rs | 7 + ts/sdk/src/types/jit_proxy.ts | 40 +++ 7 files changed, 334 insertions(+), 144 deletions(-) create mode 100644 programs/jit-proxy/src/instructions/arb_perp.rs diff --git a/Cargo.lock b/Cargo.lock index e69bd2e..e6757b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,8 +66,9 @@ checksum = "6b2d54853319fd101b8dd81de382bcbf3e03410a64d8928bbee85a3e7dcde483" [[package]] name = "anchor-attribute-access-control" -version = "0.26.0" -source = "git+https://github.com/drift-labs/anchor.git?rev=ed950fe#ed950fe5fc46d5eb38f1d5ef991e0123766fef6a" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5e1a413b311b039d29b61d0dbb401c9dbf04f792497ceca87593454bf6d7dd" dependencies = [ "anchor-syn", "anyhow", @@ -79,8 +80,9 @@ dependencies = [ [[package]] name = "anchor-attribute-account" -version = "0.26.0" -source = "git+https://github.com/drift-labs/anchor.git?rev=ed950fe#ed950fe5fc46d5eb38f1d5ef991e0123766fef6a" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca9aeaf633c6e2365fed0525dcac68610be58eee5dc69d3b86fe0b1d4b320b9" dependencies = [ "anchor-syn", "anyhow", @@ -93,8 +95,9 @@ dependencies = [ [[package]] name = "anchor-attribute-constant" -version = "0.26.0" -source = "git+https://github.com/drift-labs/anchor.git?rev=ed950fe#ed950fe5fc46d5eb38f1d5ef991e0123766fef6a" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "788e44f9e8501dabeb6f9229da0f3268fb2ae3208912608ffaa056a72031296f" dependencies = [ "anchor-syn", "proc-macro2", @@ -103,8 +106,9 @@ dependencies = [ [[package]] name = "anchor-attribute-error" -version = "0.26.0" -source = "git+https://github.com/drift-labs/anchor.git?rev=ed950fe#ed950fe5fc46d5eb38f1d5ef991e0123766fef6a" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0c4d8c7e4a2605ede6fcdced9690288b2f74e24768619a85229d57e597bc97" dependencies = [ "anchor-syn", "proc-macro2", @@ -114,24 +118,12 @@ dependencies = [ [[package]] name = "anchor-attribute-event" -version = "0.26.0" -source = "git+https://github.com/drift-labs/anchor.git?rev=ed950fe#ed950fe5fc46d5eb38f1d5ef991e0123766fef6a" -dependencies = [ - "anchor-syn", - "anyhow", - "proc-macro2", - "quote", - "syn 1.0.92", -] - -[[package]] -name = "anchor-attribute-interface" -version = "0.26.0" -source = "git+https://github.com/drift-labs/anchor.git?rev=ed950fe#ed950fe5fc46d5eb38f1d5ef991e0123766fef6a" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3b07d5c5d87b5edc72428b447b8e9ee1143b83dd1afc6a6b1d352c6a6164d8" dependencies = [ "anchor-syn", "anyhow", - "heck", "proc-macro2", "quote", "syn 1.0.92", @@ -139,8 +131,9 @@ dependencies = [ [[package]] name = "anchor-attribute-program" -version = "0.26.0" -source = "git+https://github.com/drift-labs/anchor.git?rev=ed950fe#ed950fe5fc46d5eb38f1d5ef991e0123766fef6a" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22ad0445115dbea5869b1d062da49ae125abed9132fc20c33227f25e42dfa6b" dependencies = [ "anchor-syn", "anyhow", @@ -150,9 +143,10 @@ dependencies = [ ] [[package]] -name = "anchor-attribute-state" -version = "0.26.0" -source = "git+https://github.com/drift-labs/anchor.git?rev=ed950fe#ed950fe5fc46d5eb38f1d5ef991e0123766fef6a" +name = "anchor-derive-accounts" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48daeff6781ba2f02961b0ad211feb9a2de75af345d42c62b1a252fd4dfb0724" dependencies = [ "anchor-syn", "anyhow", @@ -162,12 +156,11 @@ dependencies = [ ] [[package]] -name = "anchor-derive-accounts" -version = "0.26.0" -source = "git+https://github.com/drift-labs/anchor.git?rev=ed950fe#ed950fe5fc46d5eb38f1d5ef991e0123766fef6a" +name = "anchor-derive-space" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4fe2886f92c4f33ec1b2b8b2b43ca1b9070cf4929e63c7eaaa09a9f2c0d5123" dependencies = [ - "anchor-syn", - "anyhow", "proc-macro2", "quote", "syn 1.0.92", @@ -175,18 +168,18 @@ dependencies = [ [[package]] name = "anchor-lang" -version = "0.26.0" -source = "git+https://github.com/drift-labs/anchor.git?rev=ed950fe#ed950fe5fc46d5eb38f1d5ef991e0123766fef6a" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbbe5d1c7c057c6d63b4f2f538a320e4a22111126c9966340c3d9490e2f15ed1" dependencies = [ "anchor-attribute-access-control", "anchor-attribute-account", "anchor-attribute-constant", "anchor-attribute-error", "anchor-attribute-event", - "anchor-attribute-interface", "anchor-attribute-program", - "anchor-attribute-state", "anchor-derive-accounts", + "anchor-derive-space", "arrayref", "base64 0.13.0", "bincode", @@ -198,25 +191,27 @@ dependencies = [ [[package]] name = "anchor-spl" -version = "0.26.0" -source = "git+https://github.com/drift-labs/anchor.git?rev=ed950fe#ed950fe5fc46d5eb38f1d5ef991e0123766fef6a" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75cc8066fbd45e0e03edf48342c79265aa34ca76cefeace48ef6c402b6946665" dependencies = [ "anchor-lang", "solana-program", "spl-associated-token-account", "spl-token", + "spl-token-2022", ] [[package]] name = "anchor-syn" -version = "0.26.0" -source = "git+https://github.com/drift-labs/anchor.git?rev=ed950fe#ed950fe5fc46d5eb38f1d5ef991e0123766fef6a" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11cb31fe143aedb36fc41409ea072aa0b840cbea727e62eb2ff6e7b6cea036ff" dependencies = [ "anyhow", "bs58 0.3.1", "heck", "proc-macro2", - "proc-macro2-diagnostics", "quote", "serde", "serde_json", @@ -313,7 +308,7 @@ dependencies = [ "cc", "cfg-if", "constant_time_eq", - "digest 0.10.3", + "digest 0.10.7", ] [[package]] @@ -442,11 +437,12 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cc" -version = "1.0.68" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -457,11 +453,10 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" dependencies = [ - "num-integer", "num-traits", ] @@ -549,7 +544,7 @@ dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset 0.6.5", + "memoffset", "once_cell", "scopeguard", ] @@ -630,9 +625,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.2", "crypto-common", @@ -641,8 +636,8 @@ dependencies = [ [[package]] name = "drift" -version = "2.31.0-beta.6" -source = "git+https://github.com/drift-labs/protocol-v2.git?rev=099cbca#099cbca35f23c6840c90271c436c9ca5ee455d73" +version = "2.41.0" +source = "git+https://github.com/drift-labs/protocol-v2.git?rev=e5cdd4#e5cdd497bec48e83e8b9d99dc67e1493d82940a9" dependencies = [ "anchor-lang", "anchor-spl", @@ -695,7 +690,7 @@ dependencies = [ "derivation-path", "ed25519-dalek", "hmac 0.12.1", - "sha2 0.10.5", + "sha2 0.10.7", ] [[package]] @@ -759,12 +754,12 @@ checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" [[package]] name = "field-offset" -version = "0.3.6" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" dependencies = [ - "memoffset 0.9.0", - "rustc_version", + "memoffset", + "rustc_version 0.3.3", ] [[package]] @@ -868,7 +863,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.3", + "digest 0.10.7", ] [[package]] @@ -1086,15 +1081,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - [[package]] name = "merlin" version = "3.0.0" @@ -1218,20 +1204,28 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest 0.10.3", + "digest 0.10.7", ] [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] -name = "phoenix-v1" -version = "0.2.3" +name = "pest" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a35e8990b3ae5c2026bc274c08ad675da1fcd2630851ce5b7e1e78189d3a602" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "phoenix-v1" +version = "0.2.4" +source = "git+https://github.com/drift-labs/phoenix-v1?rev=4c65c9#4c65c97cd62493e27425ea2830447c833152bed1" dependencies = [ "borsh", "bytemuck", @@ -1295,19 +1289,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.92", - "version_check", - "yansi", -] - [[package]] name = "pyth-client" version = "0.2.2" @@ -1447,13 +1428,22 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver", + "semver 1.0.18", ] [[package]] @@ -1482,9 +1472,27 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" -version = "1.0.9" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] [[package]] name = "serde" @@ -1564,13 +1572,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.5" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9db03534dff993187064c4e0c05a5708d2a9728ace9a8959b77bedf415dac5" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest 0.10.7", ] [[package]] @@ -1591,7 +1599,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaedf34ed289ea47c2b741bb72e5357a209512d67bcd4bda44359e5bf0470f56" dependencies = [ - "digest 0.10.3", + "digest 0.10.7", "keccak", ] @@ -1665,9 +1673,9 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "solana-frozen-abi" -version = "1.14.9" +version = "1.14.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a5fb9c1bd1cf7ccc2b96209f0a9061afb7f0e94ca1ada6caf268fd4bc5274ff" +checksum = "23b4953578272ac0fadec245e85e83ae86454611f0c0a7fff7d906835124bdcf" dependencies = [ "ahash", "blake3", @@ -1686,12 +1694,12 @@ dependencies = [ "memmap2", "once_cell", "rand_core 0.6.4", - "rustc_version", + "rustc_version 0.4.0", "serde", "serde_bytes", "serde_derive", "serde_json", - "sha2 0.10.5", + "sha2 0.10.7", "solana-frozen-abi-macro", "subtle", "thiserror", @@ -1699,21 +1707,21 @@ dependencies = [ [[package]] name = "solana-frozen-abi-macro" -version = "1.14.9" +version = "1.14.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091d54072f4c79ecf31bb472fcd53c15329666c33b8c2a94f13475b2a263712a" +checksum = "57892538250428ad3dc3cbe05f6cd75ad14f4f16734fcb91bc7cd5fbb63d6315" dependencies = [ "proc-macro2", "quote", - "rustc_version", + "rustc_version 0.4.0", "syn 1.0.92", ] [[package]] name = "solana-logger" -version = "1.14.9" +version = "1.14.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1ce09bef8af337028d7a1ebf814b7ae060530a3947ceb2d5bed177b943e38c" +checksum = "06aa701c49493e93085dd1e800c05475baca15a9d4d527b59794f2ed0b66e055" dependencies = [ "env_logger", "lazy_static", @@ -1722,9 +1730,9 @@ dependencies = [ [[package]] name = "solana-program" -version = "1.14.9" +version = "1.14.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bee7b596fdf962d5619b6331b9b6a05144d5f04a22f95cd8706d940036135a" +checksum = "3f99052873619df68913cb8e92e28ff251a5483828925e87fa97ba15a9cbad51" dependencies = [ "base64 0.13.0", "bincode", @@ -1746,19 +1754,19 @@ dependencies = [ "libc", "libsecp256k1", "log", - "memoffset 0.6.5", + "memoffset", "num-derive", "num-traits", "parking_lot", "rand", "rand_chacha", - "rustc_version", + "rustc_version 0.4.0", "rustversion", "serde", "serde_bytes", "serde_derive", "serde_json", - "sha2 0.10.5", + "sha2 0.10.7", "sha3 0.10.4", "solana-frozen-abi", "solana-frozen-abi-macro", @@ -1771,9 +1779,9 @@ dependencies = [ [[package]] name = "solana-sdk" -version = "1.14.9" +version = "1.14.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dab8992a688a99747c31b346444518a9ca44ba23faa7079f54c89eda5a947f3" +checksum = "edb47da3e18cb669f6ace0b40cee0610e278903783e0c9f7fce1e1beb881a1b7" dependencies = [ "assert_matches", "base64 0.13.0", @@ -1785,7 +1793,7 @@ dependencies = [ "byteorder", "chrono", "derivation-path", - "digest 0.10.3", + "digest 0.10.7", "ed25519-dalek", "ed25519-dalek-bip32", "generic-array", @@ -1802,13 +1810,13 @@ dependencies = [ "qstring", "rand", "rand_chacha", - "rustc_version", + "rustc_version 0.4.0", "rustversion", "serde", "serde_bytes", "serde_derive", "serde_json", - "sha2 0.10.5", + "sha2 0.10.7", "sha3 0.10.4", "solana-frozen-abi", "solana-frozen-abi-macro", @@ -1822,9 +1830,9 @@ dependencies = [ [[package]] name = "solana-sdk-macro" -version = "1.14.9" +version = "1.14.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb026ece5b73ec6cefcba5ef96496a28c53c99e767cc77d8abffa36127783d" +checksum = "7d41a09b9cecd0a4df63c78a192adee99ebf2d3757c19713a68246e1d9789c7c" dependencies = [ "bs58 0.4.0", "proc-macro2", @@ -1841,9 +1849,9 @@ checksum = "7e0461f3afb29d8591300b3dd09b5472b3772d65688a2826ad960b8c0d5fa605" [[package]] name = "solana-zk-token-sdk" -version = "1.14.9" +version = "1.14.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cef8396585fd3172a926e170299eee11d8dd1a445a75fc97771e1ab387534d" +checksum = "7ab38abd096769f79fd8e3fe8465070f04742395db724606a5263c8ebc215567" dependencies = [ "aes-gcm-siv", "arrayref", @@ -1872,9 +1880,9 @@ dependencies = [ [[package]] name = "spl-associated-token-account" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a33ecc83137583902c3e13c02f34151c8b2f2b74120f9c2b3ff841953e083d" +checksum = "fbc000f0fdf1f12f99d77d398137c1751345b18c88258ce0f99b7872cf6c9bd6" dependencies = [ "assert_matches", "borsh", @@ -1912,9 +1920,9 @@ dependencies = [ [[package]] name = "spl-token-2022" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c0ebca4740cc4c892aa31e07d0b4dc1a24cac4748376d4b34f8eb0fee9ff46" +checksum = "0edb869dbe159b018f17fb9bfa67118c30f232d7f54a73742bc96794dff77ed8" dependencies = [ "arrayref", "bytemuck", @@ -2040,11 +2048,17 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +[[package]] +name = "ucd-trie" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" + [[package]] name = "uint" -version = "0.9.5" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f" dependencies = [ "byteorder", "crunchy", @@ -2089,15 +2103,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "unsize" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa7a7a734c1a5664a662ddcea0b6c9472a21da8888c957c7f1eaa09dba7a939" -dependencies = [ - "autocfg", -] - [[package]] name = "uriparse" version = "0.6.4" @@ -2266,20 +2271,13 @@ checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "without-alloc" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375db0478b203b950ef10d1cce23cdbe5f30c2454fd9e7673ff56656df23adbb" +checksum = "5e34736feff52a0b3e5680927e947a4d8fac1f0b80dc8120b080dd8de24d75e2" dependencies = [ "alloc-traits", - "unsize", ] -[[package]] -name = "yansi" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" - [[package]] name = "zeroize" version = "1.3.0" diff --git a/programs/jit-proxy/Cargo.toml b/programs/jit-proxy/Cargo.toml index 35dcdfa..c3055e3 100644 --- a/programs/jit-proxy/Cargo.toml +++ b/programs/jit-proxy/Cargo.toml @@ -14,8 +14,8 @@ cpi = ["no-entrypoint"] default = [] [dependencies] -anchor-lang = { git = "https://github.com/drift-labs/anchor.git", rev = "ed950fe", version = "0.26.0" } -anchor-spl = { git = "https://github.com/drift-labs/anchor.git", rev = "ed950fe", version = "0.26.0" } -drift = { git = "https://github.com/drift-labs/protocol-v2.git", rev = "099cbca", features = ["cpi", "mainnet-beta"]} +anchor-lang = "0.27.0" +anchor-spl = "0.27.0" +drift = { git = "https://github.com/drift-labs/protocol-v2.git", rev = "e5cdd4", features = ["cpi", "mainnet-beta"]} bytemuck = { version = "1.4.0" } static_assertions = "1.1.0" diff --git a/programs/jit-proxy/src/error.rs b/programs/jit-proxy/src/error.rs index 08e077b..f164a8d 100644 --- a/programs/jit-proxy/src/error.rs +++ b/programs/jit-proxy/src/error.rs @@ -11,4 +11,12 @@ pub enum ErrorCode { TakerOrderNotFound, #[msg("OrderSizeBreached")] OrderSizeBreached, + #[msg("NoBestBid")] + NoBestBid, + #[msg("NoBestAsk")] + NoBestAsk, + #[msg("NoArbOpportunity")] + NoArbOpportunity, + #[msg("UnprofitableArb")] + UnprofitableArb, } diff --git a/programs/jit-proxy/src/instructions/arb_perp.rs b/programs/jit-proxy/src/instructions/arb_perp.rs new file mode 100644 index 0000000..63aedfb --- /dev/null +++ b/programs/jit-proxy/src/instructions/arb_perp.rs @@ -0,0 +1,135 @@ +use anchor_lang::prelude::*; +use drift::controller::position::PositionDirection; +use drift::cpi::accounts::PlaceAndTake; +use drift::instructions::optional_accounts::{load_maps, AccountMaps}; +use drift::instructions::{OrderParams, PostOnlyParam}; +use drift::program::Drift; +use drift::state::perp_market_map::MarketSet; + +use drift::math::orders::find_bids_and_asks_from_users; +use drift::state::state::State; +use drift::state::user::{MarketType, OrderTriggerCondition, OrderType, User, UserStats}; +use drift::state::user_map::load_user_maps; + +use crate::error::ErrorCode; + +pub fn arb_perp<'info>( + ctx: Context<'_, '_, '_, 'info, ArbPerp<'info>>, + market_index: u16, +) -> Result<()> { + let clock = Clock::get()?; + let slot = clock.slot; + let now = clock.unix_timestamp; + + let taker = ctx.accounts.user.load()?; + + let quote_init = taker + .get_perp_position(market_index) + .map_or(0, |p| p.quote_asset_amount); + + let remaining_accounts_iter = &mut ctx.remaining_accounts.iter().peekable(); + let AccountMaps { + perp_market_map, + mut oracle_map, + .. + } = load_maps( + remaining_accounts_iter, + &MarketSet::new(), + &MarketSet::new(), + slot, + None, + )?; + + let (makers, _) = load_user_maps(remaining_accounts_iter, true)?; + + let perp_market = perp_market_map.get_ref(&market_index)?; + let oracle_price_data = oracle_map.get_price_data(&perp_market.amm.oracle)?; + + let (bids, asks) = + find_bids_and_asks_from_users(&perp_market, oracle_price_data, &makers, slot, now)?; + + let best_bid = bids.first().ok_or(ErrorCode::NoBestBid)?; + let best_ask = asks.first().ok_or(ErrorCode::NoBestAsk)?; + + if best_bid.price < best_ask.price { + return Err(ErrorCode::NoArbOpportunity.into()); + } + + let base_asset_amount = best_bid.base_asset_amount.min(best_ask.base_asset_amount); + + let get_order_params = |taker_direction: PositionDirection, taker_price: u64| -> OrderParams { + OrderParams { + order_type: OrderType::Limit, + market_type: MarketType::Perp, + direction: taker_direction, + user_order_id: 0, + base_asset_amount, + price: taker_price, + market_index, + reduce_only: false, + post_only: PostOnlyParam::None, + immediate_or_cancel: true, + max_ts: None, + trigger_price: None, + trigger_condition: OrderTriggerCondition::Above, + oracle_price_offset: None, + auction_duration: None, + auction_start_price: None, + auction_end_price: None, + } + }; + + let order_params = vec![ + get_order_params(PositionDirection::Long, best_ask.price), + get_order_params(PositionDirection::Short, best_bid.price), + ]; + + drop(taker); + drop(perp_market); + + place_and_take(&ctx, order_params)?; + + let taker = ctx.accounts.user.load()?; + let quote_end = taker + .get_perp_position(market_index) + .map_or(0, |p| p.quote_asset_amount); + + if quote_end <= quote_init { + return Err(ErrorCode::NoArbOpportunity.into()); + } + + Ok(()) +} + +#[derive(Accounts)] +pub struct ArbPerp<'info> { + pub state: Box>, + #[account(mut)] + pub user: AccountLoader<'info, User>, + #[account(mut)] + pub user_stats: AccountLoader<'info, UserStats>, + pub authority: Signer<'info>, + pub drift_program: Program<'info, Drift>, +} + +fn place_and_take<'info>( + ctx: &Context<'_, '_, '_, 'info, ArbPerp<'info>>, + orders_params: Vec, +) -> Result<()> { + for order_params in orders_params { + let drift_program = ctx.accounts.drift_program.to_account_info().clone(); + let cpi_accounts = PlaceAndTake { + state: ctx.accounts.state.to_account_info().clone(), + user: ctx.accounts.user.to_account_info().clone(), + user_stats: ctx.accounts.user_stats.to_account_info().clone(), + authority: ctx.accounts.authority.to_account_info().clone(), + }; + + let cpi_context = CpiContext::new(drift_program, cpi_accounts) + .with_remaining_accounts(ctx.remaining_accounts.into()); + + drift::cpi::place_and_take_perp_order(cpi_context, order_params, None)?; + } + + Ok(()) +} diff --git a/programs/jit-proxy/src/instructions/mod.rs b/programs/jit-proxy/src/instructions/mod.rs index 309d8b9..4493d0a 100644 --- a/programs/jit-proxy/src/instructions/mod.rs +++ b/programs/jit-proxy/src/instructions/mod.rs @@ -1,5 +1,7 @@ +mod arb_perp; mod check_order_constraints; mod jit; +pub use arb_perp::*; pub use check_order_constraints::*; pub use jit::*; diff --git a/programs/jit-proxy/src/lib.rs b/programs/jit-proxy/src/lib.rs index 016408f..bf79f72 100644 --- a/programs/jit-proxy/src/lib.rs +++ b/programs/jit-proxy/src/lib.rs @@ -25,4 +25,11 @@ pub mod jit_proxy { ) -> Result<()> { instructions::check_order_constraints(ctx, constraints) } + + pub fn arb_perp<'info>( + ctx: Context<'_, '_, '_, 'info, ArbPerp<'info>>, + market_index: u16, + ) -> Result<()> { + instructions::arb_perp(ctx, market_index) + } } diff --git a/ts/sdk/src/types/jit_proxy.ts b/ts/sdk/src/types/jit_proxy.ts index 0086208..0d9cb6f 100644 --- a/ts/sdk/src/types/jit_proxy.ts +++ b/ts/sdk/src/types/jit_proxy.ts @@ -206,6 +206,26 @@ export type JitProxy = { code: 6003; name: 'OrderSizeBreached'; msg: 'OrderSizeBreached'; + }, + { + code: 6004; + name: 'NoBestBid'; + msg: 'NoBestBid'; + }, + { + code: 6005; + name: 'NoBestAsk'; + msg: 'NoBestAsk'; + }, + { + code: 6006; + name: 'NoArbOpportunity'; + msg: 'NoArbOpportunity'; + }, + { + code: 6007; + name: 'UnprofitableArb'; + msg: 'UnprofitableArb'; } ]; }; @@ -419,5 +439,25 @@ export const IDL: JitProxy = { name: 'OrderSizeBreached', msg: 'OrderSizeBreached', }, + { + code: 6004, + name: 'NoBestBid', + msg: 'NoBestBid', + }, + { + code: 6005, + name: 'NoBestAsk', + msg: 'NoBestAsk', + }, + { + code: 6006, + name: 'NoArbOpportunity', + msg: 'NoArbOpportunity', + }, + { + code: 6007, + name: 'UnprofitableArb', + msg: 'UnprofitableArb', + }, ], }; From 354fd93a75602a9ab486605779d77b03090be45f Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Fri, 13 Oct 2023 09:05:12 -0400 Subject: [PATCH 3/3] add sdk method --- ts/sdk/src/jitProxyClient.ts | 55 ++++++++++++++++++++++++++ ts/sdk/src/types/jit_proxy.ts | 72 +++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/ts/sdk/src/jitProxyClient.ts b/ts/sdk/src/jitProxyClient.ts index 4b77125..f70ccad 100644 --- a/ts/sdk/src/jitProxyClient.ts +++ b/ts/sdk/src/jitProxyClient.ts @@ -2,6 +2,7 @@ import { BN, DriftClient, isVariant, + MakerInfo, MarketType, PostOnlyParams, QUOTE_SPOT_MARKET_INDEX, @@ -180,4 +181,58 @@ export class JitProxyClient { .remainingAccounts(remainingAccounts) .instruction(); } + + public async arbPerp( + params: { + makerInfos: MakerInfo[]; + marketIndex: number; + }, + txParams?: TxParams + ): Promise { + const ix = await this.getArbPerpIx(params); + const tx = await this.driftClient.buildTransaction([ix], txParams); + return await this.driftClient.sendTransaction(tx); + } + + public async getArbPerpIx({ + makerInfos, + marketIndex, + }: { + makerInfos: MakerInfo[]; + marketIndex: number; + }): Promise { + const userAccounts = [this.driftClient.getUserAccount()]; + for (const makerInfo of makerInfos) { + userAccounts.push(makerInfo.makerUserAccount); + } + + const remainingAccounts = this.driftClient.getRemainingAccounts({ + userAccounts, + writablePerpMarketIndexes: [marketIndex], + }); + + for (const makerInfo of makerInfos) { + remainingAccounts.push({ + pubkey: makerInfo.maker, + isWritable: true, + isSigner: false, + }); + remainingAccounts.push({ + pubkey: makerInfo.makerStats, + isWritable: true, + isSigner: false, + }); + } + + return this.program.methods + .arbPerp(marketIndex) + .accounts({ + state: await this.driftClient.getStatePublicKey(), + user: await this.driftClient.getUserAccountPublicKey(), + userStats: this.driftClient.getUserStatsAccountPublicKey(), + driftProgram: this.driftClient.program.programId, + }) + .remainingAccounts(remainingAccounts) + .instruction(); + } } diff --git a/ts/sdk/src/types/jit_proxy.ts b/ts/sdk/src/types/jit_proxy.ts index 0d9cb6f..d5e8a33 100644 --- a/ts/sdk/src/types/jit_proxy.ts +++ b/ts/sdk/src/types/jit_proxy.ts @@ -69,6 +69,42 @@ export type JitProxy = { }; } ]; + }, + { + name: 'arbPerp'; + accounts: [ + { + name: 'state'; + isMut: false; + isSigner: false; + }, + { + name: 'user'; + isMut: true; + isSigner: false; + }, + { + name: 'userStats'; + isMut: true; + isSigner: false; + }, + { + name: 'authority'; + isMut: false; + isSigner: true; + }, + { + name: 'driftProgram'; + isMut: false; + isSigner: false; + } + ]; + args: [ + { + name: 'marketIndex'; + type: 'u16'; + } + ]; } ]; types: [ @@ -302,6 +338,42 @@ export const IDL: JitProxy = { }, ], }, + { + name: 'arbPerp', + accounts: [ + { + name: 'state', + isMut: false, + isSigner: false, + }, + { + name: 'user', + isMut: true, + isSigner: false, + }, + { + name: 'userStats', + isMut: true, + isSigner: false, + }, + { + name: 'authority', + isMut: false, + isSigner: true, + }, + { + name: 'driftProgram', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'marketIndex', + type: 'u16', + }, + ], + }, ], types: [ {