Expose instruction builders for Openbook (#601)

This commit is contained in:
Maximilian Schneider 2023-06-15 17:20:31 +02:00 committed by GitHub
parent eb662f34fc
commit c956f153b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 214 additions and 193 deletions

View File

@ -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::<f64>();
let bid_price = fresh_price * 1.1;
let fresh_price = fresh_price.to_num::<f64>();
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,

View File

@ -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<Signature> {
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<Signature> {
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<Serum3Data<'a>, 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<Serum3Data<'a>, 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<Signature> {
let s3 = self.serum3_data_by_market_name(name)?;
) -> anyhow::Result<Instruction> {
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<Signature> {
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<Signature> {
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<Instruction> {
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<Vec<u128>, 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<Signature> {
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<Signature> {
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: {}",

View File

@ -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<Self> {
let program = mango_v4::ID;