From 6b3e753f9b03760f4b56167af00c468a86b83002 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Fri, 22 Mar 2024 16:57:06 +0800 Subject: [PATCH] copy jit-client across (#19) --- Cargo.lock | 13 +++ Cargo.toml | 3 + src/dlob/dlob.rs | 3 +- src/event_subscriber.rs | 18 +++ src/jit_client.rs | 235 ++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 8 +- 6 files changed, 275 insertions(+), 5 deletions(-) create mode 100644 src/jit_client.rs diff --git a/Cargo.lock b/Cargo.lock index df8fcce..6cf99ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1123,6 +1123,7 @@ dependencies = [ "futures-util", "hex", "hex-literal", + "jit-proxy", "log", "pyth", "rayon", @@ -1877,6 +1878,18 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "jit-proxy" +version = "0.10.2" +source = "git+https://github.com/drift-labs/jit-proxy#068bdfea5709b78cddc81ffb6dad8cd8db58fa51" +dependencies = [ + "anchor-lang", + "anchor-spl", + "bytemuck", + "drift", + "static_assertions", +] + [[package]] name = "jobserver" version = "0.1.28" diff --git a/Cargo.toml b/Cargo.toml index 5a2c89e..1255a1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ categories = ["solana", "trading", "defi", "dex"] keywords = ["solana", "trading", "defi", "dex", "drift", "protocol", "sdk"] [features] +# enable JIT client +jit = ["jit-proxy"] rpc_tests = [] test_utils = [] @@ -27,6 +29,7 @@ drift = { git = "https://github.com/drift-labs/protocol-v2.git", tag = "v2.71.0" env_logger = "0.10.1" fnv = "1.0.7" futures-util = "0.3.29" +jit-proxy = { git = "https://github.com/drift-labs/jit-proxy", optional = true } log = "0.4.20" reqwest = "*" serde = { version = "*", features = ["derive"] } diff --git a/src/dlob/dlob.rs b/src/dlob/dlob.rs index 429fb5c..eda1154 100644 --- a/src/dlob/dlob.rs +++ b/src/dlob/dlob.rs @@ -4,8 +4,7 @@ use drift::state::user::{MarketType, Order}; use rayon::prelude::*; use solana_sdk::pubkey::Pubkey; use std::any::Any; -use std::cell::Cell; -use std::collections::{BinaryHeap, HashMap}; +use std::collections::BinaryHeap; use std::str::FromStr; use std::sync::Arc; diff --git a/src/event_subscriber.rs b/src/event_subscriber.rs index f09b9d3..03e675b 100644 --- a/src/event_subscriber.rs +++ b/src/event_subscriber.rs @@ -843,6 +843,24 @@ mod test { dbg!(result); } + #[test] + fn parses_jit_proxy_logs() { + let cpi_logs = &[ + "Program log: 4DRDR8LtbQFOKvplAAAAAAAAGAABAAAAAAAAAAAAAAFGJn8TpIimFlKv8ZWRhmuU81x+ojkf3K4d+++MbslDfAGZcTYAAQEBAM5q/TIAAAABAAAAAAAAAAABAAAAAAAAAAAAAAAAAACTWxEAAAAAAA==", + "Program log: aBNAOFkVAlpOKvplAAAAAEYmfxOkiKYWUq/xlZGGa5TzXH6iOR/crh3774xuyUN8qZQ2DwAAAABMTREAAAAAAADOav0yAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJlxNgAYAAEBAQAAAQAAAQAAAAAA", + "Program log: 4DRDR8LtbQFOKvplAAAAAAIIGAABAUYmfxOkiKYWUq/xlZGGa5TzXH6iOR/crh3774xuyUN8AQAAAAAAAAAAAceaAwAAAAAAAQDOav0yAAAAAQQgzQ4AAAAAAQIjAQAAAAAAAQA+////////AAAAAUYmfxOkiKYWUq/xlZGGa5TzXH6iOR/crh3774xuyUN8AZlxNgABAQEAzmr9MgAAAAEAzmr9MgAAAAEEIM0OAAAAAAHpAf4sI0TDV0Ec0LWHs9mO40bjfKEm3A+yye5HFCQQQQEzPgAAAQABANraQssAAAABANraQssAAAABLJgAOwAAAACTWxEAAAAAAA==", + "Program log: 4DRDR8LtbQFOKvplAAAAAAAAGAABAAAAAAAAAAAAAAFGJn8TpIimFlKv8ZWRhmuU81x+ojkf3K4d+++MbslDfAGacTYAAQABAM5q/TIAAAABAAAAAAAAAAABAAAAAAAAAAAAAAAAAACTWxEAAAAAAA==", + "Program log: aBNAOFkVAlpOKvplAAAAAEYmfxOkiKYWUq/xlZGGa5TzXH6iOR/crh3774xuyUN8qZQ2DwAAAACAPBEAAAAAAADOav0yAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJpxNgAYAAEBAQABAAAAAQAAAAAA", + "Program log: 4DRDR8LtbQFOKvplAAAAAAIQGAABAUYmfxOkiKYWUq/xlZGGa5TzXH6iOR/crh3774xuyUN8AQAAAAAAAAAAAciaAwAAAAAAAQDgBS0LAAAAAQBYOwMAAAAAAYs/AAAAAAAAAAAB+Ejx//////8AAUYmfxOkiKYWUq/xlZGGa5TzXH6iOR/crh3774xuyUN8AZpxNgABAAEAzmr9MgAAAAEA4AUtCwAAAAEAWDsDAAAAAAAAAAAAAJNbEQAAAAAA", + "Program log: 4DRDR8LtbQFOKvplAAAAAAIIGAABAUYmfxOkiKYWUq/xlZGGa5TzXH6iOR/crh3774xuyUN8AQAAAAAAAAAAAcmaAwAAAAAAAQDuZNAnAAAAAYBpgwsAAAAAAV3iAAAAAAAAARhp////////AAAAAUYmfxOkiKYWUq/xlZGGa5TzXH6iOR/crh3774xuyUN8AZpxNgABAAEAzmr9MgAAAAEAzmr9MgAAAAGAwb4OAAAAAAFmQRGN8PRJqt5D5pVvCspbc3f0ZBdTB1Kcw0YfuzxCOAH2/poHAQEBAIjmn+sAAAABAFrDjp4AAAABgPDZLQAAAACTWxEAAAAAAA==", + ]; + + for log in cpi_logs { + let result = try_parse_log(log, "sig", 0); + dbg!(log, result); + } + } + #[tokio::test] async fn polled_event_stream_caching() { let _ = env_logger::try_init(); diff --git a/src/jit_client.rs b/src/jit_client.rs new file mode 100644 index 0000000..8cccde1 --- /dev/null +++ b/src/jit_client.rs @@ -0,0 +1,235 @@ +//! JIT proxy client +//! +//! Routes JIT maker orders via onchain jit-proxy program + +use anchor_lang::InstructionData; +use drift::{ + math::constants::QUOTE_SPOT_MARKET_INDEX, + state::user::{MarketType, User}, +}; +use jit_proxy::state::{PostOnlyParam, PriceType}; +use solana_sdk::{ + compute_budget::ComputeBudgetInstruction, + instruction::{AccountMeta, Instruction}, + message::v0, + signature::Signature, +}; + +use crate::{ + build_accounts, + constants::{state_account, PROGRAM_ID}, + types::{MarketId, ReferrerInfo, RpcSendTransactionConfig, VersionedMessage}, + AccountProvider, DriftClient, Pubkey, SdkResult, Wallet, +}; + +/// Ix parameters for jit-proxy program +pub struct JitIxParams { + taker_key: Pubkey, + taker_stats_key: Pubkey, + taker: User, + taker_order_id: u32, + max_position: i64, + min_position: i64, + bid: i64, + ask: i64, + price_type: Option, + referrer_info: Option, + post_only: Option, +} + +impl JitIxParams { + pub fn new( + taker_key: Pubkey, + taker_stats_key: Pubkey, + taker: User, + taker_order_id: u32, + max_position: i64, + min_position: i64, + bid: i64, + ask: i64, + price_type: Option, + referrer_info: Option, + post_only: Option, + ) -> Self { + Self { + taker_key, + taker_stats_key, + taker, + taker_order_id, + max_position, + min_position, + bid, + ask, + price_type, + referrer_info, + post_only, + } + } +} + +#[derive(Clone)] +pub struct JitProxyClient { + drift_client: DriftClient, + config: Option, + cu_params: Option, +} + +impl JitProxyClient { + pub fn new( + drift_client: DriftClient, + config: Option, + cu_params: Option, + ) -> Self { + Self { + drift_client, + config, + cu_params, + } + } + + pub fn update_config(&mut self, config: RpcSendTransactionConfig) { + self.config = Some(config); + } + + pub fn update_cu_params(&mut self, cu_params: ComputeBudgetParams) { + self.cu_params = Some(cu_params); + } + + pub async fn jit( + &self, + params: JitIxParams, + sub_account_id: Option, + ) -> SdkResult { + let wallet = self.drift_client.wallet(); + if let Some(order) = params + .taker + .orders + .iter() + .find(|order| order.order_id == params.taker_order_id) + { + let tx_builder = self + .drift_client + .init_tx(&wallet.sub_account(sub_account_id.unwrap_or(0)), false) + .await + .unwrap(); + let program_data = tx_builder.program_data(); + let account_data = tx_builder.account_data(); + + let writable_markets = match order.market_type { + MarketType::Perp => { + vec![MarketId::perp(order.market_index)] + } + MarketType::Spot => { + vec![MarketId::spot(order.market_index), MarketId::QUOTE_SPOT] + } + }; + + let mut accounts = build_accounts( + program_data, + jit_proxy::accounts::Jit { + state: *state_account(), + user: wallet.default_sub_account(), + user_stats: Wallet::derive_stats_account(wallet.authority(), &PROGRAM_ID), + taker: params.taker_key, + taker_stats: params.taker_stats_key, + authority: *wallet.authority(), + drift_program: PROGRAM_ID, + }, + &[¶ms.taker, account_data], + &[], + writable_markets.as_slice(), + ); + + if let Some(referrer_info) = params.referrer_info { + accounts.push(AccountMeta::new(referrer_info.referrer(), false)); + accounts.push(AccountMeta::new(referrer_info.referrer_stats(), false)); + } + + if order.market_type == MarketType::Spot { + let spot_market_vault = self + .drift_client + .get_spot_market_info(order.market_index) + .await? + .vault; + let quote_spot_market_vault = self + .drift_client + .get_spot_market_info(QUOTE_SPOT_MARKET_INDEX) + .await? + .vault; + accounts.push(AccountMeta::new_readonly(spot_market_vault, false)); + accounts.push(AccountMeta::new_readonly(quote_spot_market_vault, false)); + } + + let jit_params = jit_proxy::instructions::JitParams { + taker_order_id: params.taker_order_id, + max_position: params.max_position, + min_position: params.min_position, + bid: params.bid, + ask: params.ask, + price_type: params.price_type.unwrap(), + post_only: params.post_only, + }; + + let ix = Instruction { + program_id: jit_proxy::id(), + accounts, + data: jit_proxy::instruction::Jit { params: jit_params }.data(), + }; + + let mut ixs = Vec::with_capacity(3); + if let Some(cu_params) = self.cu_params { + let cu_limit_ix = ComputeBudgetInstruction::set_compute_unit_price( + cu_params.microlamports_per_cu(), + ); + let cu_price_ix = + ComputeBudgetInstruction::set_compute_unit_limit(cu_params.cu_limit()); + + ixs.push(cu_limit_ix); + ixs.push(cu_price_ix); + } + ixs.push(ix); + + let lut = program_data.lookup_table.clone(); + + let message = v0::Message::try_compile( + self.drift_client.wallet().authority(), + ixs.as_slice(), + &[lut], + Default::default(), + ) + .expect("failed to compile message"); + + let tx = VersionedMessage::V0(message); + + self.drift_client + .sign_and_send_with_config(tx, self.config.unwrap_or_default()) + .await + } else { + log::warn!("Order: {} not found", params.taker_order_id); + Ok(Signature::default()) // this is checked against in the jitters, a default signature isn't a successful fill, i just don't want to return errors + } + } +} + +#[derive(Clone, Copy)] +pub struct ComputeBudgetParams { + microlamports_per_cu: u64, + cu_limit: u32, +} + +impl ComputeBudgetParams { + pub fn new(microlamports_per_cu: u64, cu_limit: u32) -> Self { + Self { + microlamports_per_cu, + cu_limit, + } + } + + pub fn microlamports_per_cu(&self) -> u64 { + self.microlamports_per_cu + } + + pub fn cu_limit(&self) -> u32 { + self.cu_limit + } +} diff --git a/src/lib.rs b/src/lib.rs index 662e35e..f4f22ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,6 +69,8 @@ pub mod websocket_program_account_subscriber; pub mod auction_subscriber; pub mod dlob_client; pub mod event_subscriber; +#[cfg(feature = "jit")] +pub mod jit_client; pub mod slot_subscriber; pub mod dlob; @@ -1429,15 +1431,15 @@ pub async fn get_market_accounts( client: &RpcClient, ) -> SdkResult<(Vec, Vec)> { let state_data = client - .get_account_data(&state_account()) + .get_account_data(state_account()) .await .expect("state account fetch"); let state = State::try_deserialize(&mut state_data.as_slice()).expect("state deserializes"); let spot_market_pdas: Vec = (0..state.number_of_spot_markets) - .map(|x| derive_spot_market_account(x)) + .map(derive_spot_market_account) .collect(); let perp_market_pdas: Vec = (0..state.number_of_markets) - .map(|x| derive_perp_market_account(x)) + .map(derive_perp_market_account) .collect(); let (spot_markets, perp_markets) = tokio::join!(