diff --git a/bin/keeper/src/taker.rs b/bin/keeper/src/taker.rs index 02f48a7b3..eaf3d1da2 100644 --- a/bin/keeper/src/taker.rs +++ b/bin/keeper/src/taker.rs @@ -138,7 +138,7 @@ pub async fn loop_blocking_price_update( log::info!("{} Updated price is {:?}", token_name, fresh_price.price); if let Ok(mut price) = price.write() { *price = I80F48::from_num(fresh_price.price) - / I80F48::from_num(10u64.pow(-fresh_price.expo as u32)); + / I80F48::from_num(10f64.powi(-fresh_price.expo)); } } } @@ -157,6 +157,18 @@ pub async fn loop_blocking_orders( .unwrap(); log::info!("Cancelled orders - {:?} for {}", orders, market_name); + let market_index = mango_client.context.serum3_market_index(&market_name); + let s3 = mango_client.context.serum3(market_index); + + let base_decimals = mango_client + .context + .token(s3.market.base_token_index) + .decimals as i32; + let quote_decimals = mango_client + .context + .token(s3.market.quote_token_index) + .decimals as i32; + loop { interval.tick().await; @@ -164,25 +176,23 @@ pub async fn loop_blocking_orders( let market_name = market_name.clone(); let price = price.clone(); - let res = (|| async move { + let res: anyhow::Result<()> = (|| async move { client.serum3_settle_funds(&market_name).await?; - let fresh_price = match price.read() { - Ok(price) => *price, - Err(_) => { - anyhow::bail!("Price RwLock PoisonError!"); - } - }; + let fresh_price = price.read().unwrap().to_num::(); + let bid_price = fresh_price * 1.1; - let fresh_price = fresh_price.to_num::(); + let bid_price_lots = + bid_price * 10f64.powi(quote_decimals - base_decimals) * s3.coin_lot_size as f64 + / s3.pc_lot_size as f64; - let bid_price = fresh_price + fresh_price * 0.1; let res = client .serum3_place_order( &market_name, Serum3Side::Bid, - bid_price, - 0.0001, + bid_price_lots.round() as u64, + 1, + u64::MAX, Serum3SelfTradeBehavior::DecrementTake, Serum3OrderType::ImmediateOrCancel, SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() as u64, @@ -195,13 +205,18 @@ pub async fn loop_blocking_orders( log::info!("Placed bid at {} for {}", bid_price, market_name) } - let ask_price = fresh_price - fresh_price * 0.1; + let ask_price = fresh_price * 0.9; + let ask_price_lots = + ask_price * 10f64.powi(quote_decimals - base_decimals) * s3.coin_lot_size as f64 + / s3.pc_lot_size as f64; + let res = client .serum3_place_order( &market_name, Serum3Side::Ask, - ask_price, - 0.0001, + ask_price_lots.round() as u64, + 1, + u64::MAX, Serum3SelfTradeBehavior::DecrementTake, Serum3OrderType::ImmediateOrCancel, SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() as u64, diff --git a/lib/client/src/client.rs b/lib/client/src/client.rs index 9d45d034d..dbbec4f3d 100644 --- a/lib/client/src/client.rs +++ b/lib/client/src/client.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use std::sync::Arc; use std::time::Duration; -use anchor_client::{ClientError, Cluster}; +use anchor_client::Cluster; use anchor_lang::__private::bytemuck; use anchor_lang::prelude::System; @@ -31,7 +31,7 @@ use solana_sdk::signer::keypair; use solana_sdk::transaction::TransactionError; use crate::account_fetcher::*; -use crate::context::{MangoGroupContext, Serum3MarketContext, TokenContext}; +use crate::context::MangoGroupContext; use crate::gpa::{fetch_anchor_account, fetch_mango_accounts}; use crate::jupiter; @@ -429,36 +429,32 @@ impl MangoClient { // Serum3 // - pub async fn serum3_create_open_orders(&self, name: &str) -> anyhow::Result { + pub fn serum3_create_open_orders_instruction( + &self, + market_index: Serum3MarketIndex, + ) -> Instruction { let account_pubkey = self.mango_account_address; - - let market_index = *self - .context - .serum3_market_indexes_by_name - .get(name) - .unwrap(); - let serum3_info = self.context.serum3_markets.get(&market_index).unwrap(); + let s3 = self.context.serum3(market_index); let open_orders = Pubkey::find_program_address( &[ - account_pubkey.as_ref(), b"Serum3OO".as_ref(), - serum3_info.address.as_ref(), + account_pubkey.as_ref(), + s3.address.as_ref(), ], &mango_v4::ID, ) .0; - let ix = Instruction { + Instruction { program_id: mango_v4::id(), accounts: anchor_lang::ToAccountMetas::to_account_metas( &mango_v4::accounts::Serum3CreateOpenOrders { group: self.group(), account: account_pubkey, - - serum_market: serum3_info.address, - serum_program: serum3_info.market.serum_program, - serum_market_external: serum3_info.market.serum_market_external, + serum_market: s3.address, + serum_program: s3.market.serum_program, + serum_market_external: s3.market.serum_market_external, open_orders, owner: self.owner(), payer: self.owner(), @@ -470,116 +466,47 @@ impl MangoClient { data: anchor_lang::InstructionData::data( &mango_v4::instruction::Serum3CreateOpenOrders {}, ), - }; + } + } + + pub async fn serum3_create_open_orders(&self, name: &str) -> anyhow::Result { + let market_index = self.context.serum3_market_index(name); + let ix = self.serum3_create_open_orders_instruction(market_index); self.send_and_confirm_owner_tx(vec![ix]).await } - fn serum3_data_by_market_name<'a>(&'a self, name: &str) -> Result, ClientError> { - let market_index = *self - .context - .serum3_market_indexes_by_name - .get(name) - .unwrap(); - self.serum3_data_by_market_index(market_index) - } - - fn serum3_data_by_market_index<'a>( - &'a self, - market_index: Serum3MarketIndex, - ) -> Result, ClientError> { - let serum3_info = self.context.serum3_markets.get(&market_index).unwrap(); - - let quote_info = self.context.token(serum3_info.market.quote_token_index); - let base_info = self.context.token(serum3_info.market.base_token_index); - - Ok(Serum3Data { - market_index, - market: serum3_info, - quote: quote_info, - base: base_info, - }) - } - #[allow(clippy::too_many_arguments)] - pub async fn serum3_place_order( + pub fn serum3_place_order_instruction( &self, - name: &str, + account: &MangoAccountValue, + market_index: Serum3MarketIndex, side: Serum3Side, - price: f64, - size: f64, + limit_price: u64, + max_base_qty: u64, + max_native_quote_qty_including_fees: u64, self_trade_behavior: Serum3SelfTradeBehavior, order_type: Serum3OrderType, client_order_id: u64, limit: u16, - ) -> anyhow::Result { - let s3 = self.serum3_data_by_market_name(name)?; + ) -> anyhow::Result { + let s3 = self.context.serum3(market_index); + let base = self.context.serum3_base_token(market_index); + let quote = self.context.serum3_quote_token(market_index); + let open_orders = account + .serum3_orders(market_index) + .expect("oo is created") + .open_orders; - let account = self.mango_account().await?; - let open_orders = account.serum3_orders(s3.market_index).unwrap().open_orders; + let health_check_metas = self.context.derive_health_check_remaining_account_metas( + account, + vec![], + vec![], + vec![], + )?; - let health_check_metas = self - .derive_health_check_remaining_account_metas(vec![], vec![], vec![]) - .await?; - - // https://github.com/project-serum/serum-ts/blob/master/packages/serum/src/market.ts#L1306 - let limit_price = { - (price * ((10u64.pow(s3.quote.decimals as u32) * s3.market.coin_lot_size) as f64)) - as u64 - / (10u64.pow(s3.base.decimals as u32) * s3.market.pc_lot_size) - }; - // https://github.com/project-serum/serum-ts/blob/master/packages/serum/src/market.ts#L1333 - let max_base_qty = - { (size * 10u64.pow(s3.base.decimals as u32) as f64) as u64 / s3.market.coin_lot_size }; - let max_native_quote_qty_including_fees = { - fn get_fee_tier(msrm_balance: u64, srm_balance: u64) -> u64 { - if msrm_balance >= 1 { - 6 - } else if srm_balance >= 1_000_000 { - 5 - } else if srm_balance >= 100_000 { - 4 - } else if srm_balance >= 10_000 { - 3 - } else if srm_balance >= 1_000 { - 2 - } else if srm_balance >= 100 { - 1 - } else { - 0 - } - } - - fn get_fee_rates(fee_tier: u64) -> (f64, f64) { - if fee_tier == 1 { - // SRM2 - return (0.002, -0.0003); - } else if fee_tier == 2 { - // SRM3 - return (0.0018, -0.0003); - } else if fee_tier == 3 { - // SRM4 - return (0.0016, -0.0003); - } else if fee_tier == 4 { - // SRM5 - return (0.0014, -0.0003); - } else if fee_tier == 5 { - // SRM6 - return (0.0012, -0.0003); - } else if fee_tier == 6 { - // MSRM - return (0.001, -0.0005); - } - // Base - (0.0022, -0.0003) - } - - let fee_tier = get_fee_tier(0, 0); - let rates = get_fee_rates(fee_tier); - (s3.market.pc_lot_size as f64 * (1f64 + rates.0)) as u64 * (limit_price * max_base_qty) - }; let payer_mint_info = match side { - Serum3Side::Bid => s3.quote.mint_info, - Serum3Side::Ask => s3.base.mint_info, + Serum3Side::Bid => quote.mint_info, + Serum3Side::Ask => base.mint_info, }; let ix = Instruction { @@ -593,16 +520,16 @@ impl MangoClient { payer_bank: payer_mint_info.first_bank(), payer_vault: payer_mint_info.first_vault(), payer_oracle: payer_mint_info.oracle, - serum_market: s3.market.address, - serum_program: s3.market.market.serum_program, - serum_market_external: s3.market.market.serum_market_external, - market_bids: s3.market.bids, - market_asks: s3.market.asks, - market_event_queue: s3.market.event_q, - market_request_queue: s3.market.req_q, - market_base_vault: s3.market.coin_vault, - market_quote_vault: s3.market.pc_vault, - market_vault_signer: s3.market.vault_signer, + serum_market: s3.address, + serum_program: s3.market.serum_program, + serum_market_external: s3.market.serum_market_external, + market_bids: s3.bids, + market_asks: s3.asks, + market_event_queue: s3.event_q, + market_request_queue: s3.req_q, + market_base_vault: s3.coin_vault, + market_quote_vault: s3.pc_vault, + market_vault_signer: s3.vault_signer, owner: self.owner(), token_program: Token::id(), }, @@ -622,14 +549,48 @@ impl MangoClient { limit, }), }; + + Ok(ix) + } + + #[allow(clippy::too_many_arguments)] + pub async fn serum3_place_order( + &self, + name: &str, + side: Serum3Side, + limit_price: u64, + max_base_qty: u64, + max_native_quote_qty_including_fees: u64, + self_trade_behavior: Serum3SelfTradeBehavior, + order_type: Serum3OrderType, + client_order_id: u64, + limit: u16, + ) -> anyhow::Result { + let account = self.mango_account().await?; + let market_index = self.context.serum3_market_index(name); + let ix = self.serum3_place_order_instruction( + &account, + market_index, + side, + limit_price, + max_base_qty, + max_native_quote_qty_including_fees, + self_trade_behavior, + order_type, + client_order_id, + limit, + )?; self.send_and_confirm_owner_tx(vec![ix]).await } pub async fn serum3_settle_funds(&self, name: &str) -> anyhow::Result { - let s3 = self.serum3_data_by_market_name(name)?; + let market_index = self.context.serum3_market_index(name); + let s3 = self.context.serum3(market_index); + let base = self.context.serum3_base_token(market_index); + let quote = self.context.serum3_quote_token(market_index); let account = self.mango_account().await?; - let open_orders = account.serum3_orders(s3.market_index).unwrap().open_orders; + let open_orders = account.serum3_orders(market_index).unwrap().open_orders; let ix = Instruction { program_id: mango_v4::id(), @@ -638,16 +599,16 @@ impl MangoClient { group: self.group(), account: self.mango_account_address, open_orders, - quote_bank: s3.quote.mint_info.first_bank(), - quote_vault: s3.quote.mint_info.first_vault(), - base_bank: s3.base.mint_info.first_bank(), - base_vault: s3.base.mint_info.first_vault(), - serum_market: s3.market.address, - serum_program: s3.market.market.serum_program, - serum_market_external: s3.market.market.serum_market_external, - market_base_vault: s3.market.coin_vault, - market_quote_vault: s3.market.pc_vault, - market_vault_signer: s3.market.vault_signer, + quote_bank: quote.mint_info.first_bank(), + quote_vault: quote.mint_info.first_vault(), + base_bank: base.mint_info.first_bank(), + base_vault: base.mint_info.first_vault(), + serum_market: s3.address, + serum_program: s3.market.serum_program, + serum_market_external: s3.market.serum_market_external, + market_base_vault: s3.coin_vault, + market_quote_vault: s3.pc_vault, + market_vault_signer: s3.vault_signer, owner: self.owner(), token_program: Token::id(), }, @@ -658,15 +619,45 @@ impl MangoClient { self.send_and_confirm_owner_tx(vec![ix]).await } + pub fn serum3_cancel_all_orders_instruction( + &self, + account: &MangoAccountValue, + market_index: Serum3MarketIndex, + limit: u8, + ) -> anyhow::Result { + let s3 = self.context.serum3(market_index); + let open_orders = account.serum3_orders(market_index)?.open_orders; + + let ix = Instruction { + program_id: mango_v4::id(), + accounts: anchor_lang::ToAccountMetas::to_account_metas( + &mango_v4::accounts::Serum3CancelAllOrders { + group: self.group(), + account: self.mango_account_address, + open_orders, + market_bids: s3.bids, + market_asks: s3.asks, + market_event_queue: s3.event_q, + serum_market: s3.address, + serum_program: s3.market.serum_program, + serum_market_external: s3.market.serum_market_external, + owner: self.owner(), + }, + None, + ), + data: anchor_lang::InstructionData::data( + &mango_v4::instruction::Serum3CancelAllOrders { limit }, + ), + }; + + Ok(ix) + } + pub async fn serum3_cancel_all_orders( &self, market_name: &str, ) -> Result, anyhow::Error> { - let market_index = *self - .context - .serum3_market_indexes_by_name - .get(market_name) - .unwrap(); + let market_index = self.context.serum3_market_index(market_name); let account = self.mango_account().await?; let open_orders = account.serum3_orders(market_index).unwrap().open_orders; let open_orders_acc = self.account_fetcher.fetch_raw_account(&open_orders).await?; @@ -699,7 +690,9 @@ impl MangoClient { market_index: Serum3MarketIndex, open_orders: &Pubkey, ) -> anyhow::Result { - let s3 = self.serum3_data_by_market_index(market_index)?; + let s3 = self.context.serum3(market_index); + let base = self.context.serum3_base_token(market_index); + let quote = self.context.serum3_quote_token(market_index); let health_remaining_ams = self .context @@ -714,19 +707,19 @@ impl MangoClient { group: self.group(), account: *liqee.0, open_orders: *open_orders, - serum_market: s3.market.address, - serum_program: s3.market.market.serum_program, - serum_market_external: s3.market.market.serum_market_external, - market_bids: s3.market.bids, - market_asks: s3.market.asks, - market_event_queue: s3.market.event_q, - market_base_vault: s3.market.coin_vault, - market_quote_vault: s3.market.pc_vault, - market_vault_signer: s3.market.vault_signer, - quote_bank: s3.quote.mint_info.first_bank(), - quote_vault: s3.quote.mint_info.first_vault(), - base_bank: s3.base.mint_info.first_bank(), - base_vault: s3.base.mint_info.first_vault(), + serum_market: s3.address, + serum_program: s3.market.serum_program, + serum_market_external: s3.market.serum_market_external, + market_bids: s3.bids, + market_asks: s3.asks, + market_event_queue: s3.event_q, + market_base_vault: s3.coin_vault, + market_quote_vault: s3.pc_vault, + market_vault_signer: s3.vault_signer, + quote_bank: quote.mint_info.first_bank(), + quote_vault: quote.mint_info.first_vault(), + base_bank: base.mint_info.first_bank(), + base_vault: base.mint_info.first_vault(), token_program: Token::id(), }, None, @@ -747,10 +740,11 @@ impl MangoClient { side: Serum3Side, order_id: u128, ) -> anyhow::Result { - let s3 = self.serum3_data_by_market_name(market_name)?; + let market_index = self.context.serum3_market_index(market_name); + let s3 = self.context.serum3(market_index); let account = self.mango_account().await?; - let open_orders = account.serum3_orders(s3.market_index).unwrap().open_orders; + let open_orders = account.serum3_orders(market_index).unwrap().open_orders; let ix = Instruction { program_id: mango_v4::id(), @@ -759,13 +753,13 @@ impl MangoClient { &mango_v4::accounts::Serum3CancelOrder { group: self.group(), account: self.mango_account_address, - serum_market: s3.market.address, - serum_program: s3.market.market.serum_program, - serum_market_external: s3.market.market.serum_market_external, + serum_market: s3.address, + serum_program: s3.market.serum_program, + serum_market_external: s3.market.serum_market_external, open_orders, - market_bids: s3.market.bids, - market_asks: s3.market.asks, - market_event_queue: s3.market.event_q, + market_bids: s3.bids, + market_asks: s3.asks, + market_event_queue: s3.event_q, owner: self.owner(), }, None, @@ -782,6 +776,8 @@ impl MangoClient { // // Perps // + + #[allow(clippy::too_many_arguments)] pub fn perp_place_order_instruction( &self, account: &MangoAccountValue, @@ -841,6 +837,7 @@ impl MangoClient { Ok(ix) } + #[allow(clippy::too_many_arguments)] pub async fn perp_place_order( &self, market_index: PerpMarketIndex, @@ -1653,13 +1650,6 @@ impl MangoClient { } } -struct Serum3Data<'a> { - market_index: Serum3MarketIndex, - market: &'a Serum3MarketContext, - quote: &'a TokenContext, - base: &'a TokenContext, -} - #[derive(Debug, thiserror::Error)] pub enum MangoClientError { #[error("Transaction simulation error. Error: {err:?}, Logs: {}", diff --git a/lib/client/src/context.rs b/lib/client/src/context.rs index e120af999..4c34482a9 100644 --- a/lib/client/src/context.rs +++ b/lib/client/src/context.rs @@ -78,14 +78,34 @@ impl MangoGroupContext { self.token(token_index).mint_info } - pub fn token(&self, token_index: TokenIndex) -> &TokenContext { - self.tokens.get(&token_index).unwrap() - } - pub fn perp(&self, perp_market_index: PerpMarketIndex) -> &PerpMarketContext { self.perp_markets.get(&perp_market_index).unwrap() } + pub fn perp_market_address(&self, perp_market_index: PerpMarketIndex) -> Pubkey { + self.perp(perp_market_index).address + } + + pub fn serum3_market_index(&self, name: &str) -> Serum3MarketIndex { + *self.serum3_market_indexes_by_name.get(name).unwrap() + } + + pub fn serum3(&self, market_index: Serum3MarketIndex) -> &Serum3MarketContext { + self.serum3_markets.get(&market_index).unwrap() + } + + pub fn serum3_base_token(&self, market_index: Serum3MarketIndex) -> &TokenContext { + self.token(self.serum3(market_index).market.base_token_index) + } + + pub fn serum3_quote_token(&self, market_index: Serum3MarketIndex) -> &TokenContext { + self.token(self.serum3(market_index).market.quote_token_index) + } + + pub fn token(&self, token_index: TokenIndex) -> &TokenContext { + self.tokens.get(&token_index).unwrap() + } + pub fn token_by_mint(&self, mint: &Pubkey) -> anyhow::Result<&TokenContext> { self.tokens .iter() @@ -93,10 +113,6 @@ impl MangoGroupContext { .ok_or_else(|| anyhow::anyhow!("no token for mint {}", mint)) } - pub fn perp_market_address(&self, perp_market_index: PerpMarketIndex) -> Pubkey { - self.perp(perp_market_index).address - } - pub async fn new_from_rpc(rpc: &RpcClientAsync, group: Pubkey) -> anyhow::Result { let program = mango_v4::ID;