1454 lines
44 KiB
Rust
1454 lines
44 KiB
Rust
|
#![allow(unused)]
|
||
|
use anyhow::{format_err, Result};
|
||
|
use clap::Clap;
|
||
|
use rand::prelude::*;
|
||
|
use log::{error, info, warn};
|
||
|
use rand::rngs::OsRng;
|
||
|
use safe_transmute::{
|
||
|
guard::{PermissiveGuard, SingleManyGuard, SingleValueGuard},
|
||
|
to_bytes::{transmute_one_to_bytes, transmute_to_bytes},
|
||
|
transmute_many, transmute_many_pedantic, transmute_many_permissive, transmute_one,
|
||
|
transmute_one_pedantic, try_copy,
|
||
|
};
|
||
|
use serum_dex::instruction::{MarketInstruction, NewOrderInstruction};
|
||
|
use serum_dex::matching::{OrderType, Side};
|
||
|
use serum_dex::state::gen_vault_signer_key;
|
||
|
use serum_dex::state::Event;
|
||
|
use serum_dex::state::EventQueueHeader;
|
||
|
use serum_dex::state::MarketState;
|
||
|
use serum_dex::state::QueueHeader;
|
||
|
use serum_dex::state::Request;
|
||
|
use serum_dex::state::RequestQueueHeader;
|
||
|
use solana_client::rpc_client::RpcClient;
|
||
|
use solana_client::rpc_config::RpcSendTransactionConfig;
|
||
|
use solana_sdk::commitment_config::CommitmentConfig;
|
||
|
use solana_sdk::instruction::{AccountMeta, Instruction};
|
||
|
use solana_sdk::pubkey::Pubkey;
|
||
|
use solana_sdk::signature::Signature;
|
||
|
use solana_sdk::signature::{Keypair, Signer};
|
||
|
use solana_sdk::system_instruction::SystemInstruction;
|
||
|
use solana_sdk::transaction::Transaction;
|
||
|
use spl_token::instruction as token_instruction;
|
||
|
use spl_token::pack::Pack;
|
||
|
use std::borrow::Cow;
|
||
|
use std::collections::BTreeSet;
|
||
|
use std::mem::size_of;
|
||
|
use std::num::NonZeroU64;
|
||
|
use std::str::FromStr;
|
||
|
use std::{thread, time};
|
||
|
|
||
|
use std::sync::mpsc::{Sender, Receiver};
|
||
|
use sloggers::file::FileLoggerBuilder;
|
||
|
use sloggers::types::Severity;
|
||
|
use sloggers::Build;
|
||
|
|
||
|
pub fn with_logging<F: FnOnce()>(to: &str, fnc: F) {
|
||
|
fnc();
|
||
|
}
|
||
|
|
||
|
#[derive(Debug)]
|
||
|
enum Cluster {
|
||
|
Testnet,
|
||
|
Mainnet,
|
||
|
VipMainnet,
|
||
|
Devnet,
|
||
|
Localnet,
|
||
|
Debug,
|
||
|
}
|
||
|
|
||
|
impl FromStr for Cluster {
|
||
|
type Err = anyhow::Error;
|
||
|
fn from_str(s: &str) -> Result<Cluster> {
|
||
|
match s.to_lowercase().as_str() {
|
||
|
"t" | "testnet" => Ok(Cluster::Testnet),
|
||
|
"m" | "mainnet" => Ok(Cluster::Mainnet),
|
||
|
"v" | "vipmainnet" => Ok(Cluster::VipMainnet),
|
||
|
"d" | "devnet" => Ok(Cluster::Devnet),
|
||
|
"l" | "localnet" => Ok(Cluster::Localnet),
|
||
|
"g" | "debug" => Ok(Cluster::Debug),
|
||
|
_ => Err(anyhow::Error::msg(
|
||
|
"Cluster must be one of [testnet, mainnet, devnet]\n",
|
||
|
)),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Cluster {
|
||
|
fn url(&self) -> &'static str {
|
||
|
match self {
|
||
|
Cluster::Devnet => "https://devnet.solana.com",
|
||
|
Cluster::Testnet => "https://testnet.solana.com",
|
||
|
Cluster::Mainnet => "https://api.mainnet-beta.solana.com",
|
||
|
Cluster::VipMainnet => "https://vip-api.mainnet-beta.solana.com",
|
||
|
Cluster::Localnet => "http://127.0.0.1:8899",
|
||
|
Cluster::Debug => "http://34.90.18.145:8899",
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn read_keypair_file(s: &str) -> Result<Keypair> {
|
||
|
solana_sdk::signature::read_keypair_file(s)
|
||
|
.map_err(|_| format_err!("failed to read keypair from {}", s))
|
||
|
}
|
||
|
|
||
|
#[derive(Clap, Debug)]
|
||
|
struct Opts {
|
||
|
#[clap(default_value = "mainnet")]
|
||
|
cluster: Cluster,
|
||
|
#[clap(subcommand)]
|
||
|
command: Command,
|
||
|
}
|
||
|
|
||
|
#[derive(Clap, Debug)]
|
||
|
enum Command {
|
||
|
Genesis {
|
||
|
#[clap(long, short)]
|
||
|
payer: String,
|
||
|
|
||
|
#[clap(long, short)]
|
||
|
mint: String,
|
||
|
|
||
|
#[clap(long, short)]
|
||
|
owner_pubkey: Pubkey,
|
||
|
|
||
|
#[clap(long, short)]
|
||
|
decimals: u8,
|
||
|
},
|
||
|
Mint {
|
||
|
#[clap(long, short)]
|
||
|
payer: String,
|
||
|
|
||
|
#[clap(long, short)]
|
||
|
signer: String,
|
||
|
|
||
|
#[clap(long, short)]
|
||
|
mint_pubkey: Pubkey,
|
||
|
|
||
|
#[clap(long, short)]
|
||
|
recipient: Option<Pubkey>,
|
||
|
|
||
|
#[clap(long, short)]
|
||
|
quantity: u64,
|
||
|
},
|
||
|
CreateAccount {
|
||
|
mint_pubkey: Pubkey,
|
||
|
owner_pubkey: Pubkey,
|
||
|
payer: String,
|
||
|
},
|
||
|
ConsumeEvents {
|
||
|
#[clap(long, short)]
|
||
|
dex_program_id: Pubkey,
|
||
|
|
||
|
#[clap(long)]
|
||
|
payer: String,
|
||
|
|
||
|
#[clap(long, short)]
|
||
|
market: Pubkey,
|
||
|
|
||
|
#[clap(long, short)]
|
||
|
coin_wallet: Pubkey,
|
||
|
|
||
|
#[clap(long, short)]
|
||
|
pc_wallet: Pubkey,
|
||
|
|
||
|
#[clap(long, short)]
|
||
|
num_workers: usize,
|
||
|
|
||
|
#[clap(long, short)]
|
||
|
events_per_worker: usize,
|
||
|
|
||
|
#[clap(long)]
|
||
|
num_accounts: Option<usize>,
|
||
|
|
||
|
#[clap(long)]
|
||
|
log_directory: String,
|
||
|
},
|
||
|
MatchOrders {
|
||
|
#[clap(long, short)]
|
||
|
dex_program_id: Pubkey,
|
||
|
|
||
|
#[clap(long)]
|
||
|
payer: String,
|
||
|
|
||
|
#[clap(long, short)]
|
||
|
market: Pubkey,
|
||
|
|
||
|
#[clap(long, short)]
|
||
|
coin_wallet: Pubkey,
|
||
|
|
||
|
#[clap(long, short)]
|
||
|
pc_wallet: Pubkey,
|
||
|
},
|
||
|
MonitorQueue {
|
||
|
#[clap(long, short)]
|
||
|
dex_program_id: Pubkey,
|
||
|
|
||
|
#[clap(long, short)]
|
||
|
market: Pubkey,
|
||
|
|
||
|
#[clap(long)]
|
||
|
port: u16,
|
||
|
},
|
||
|
PrintEventQueue {
|
||
|
dex_program_id: Pubkey,
|
||
|
market: Pubkey,
|
||
|
},
|
||
|
WholeShebang {
|
||
|
payer: String,
|
||
|
dex_program_id: Pubkey,
|
||
|
},
|
||
|
SettleFunds {
|
||
|
payer: String,
|
||
|
dex_program_id: Pubkey,
|
||
|
market: Pubkey,
|
||
|
orders: Pubkey,
|
||
|
coin_wallet: Pubkey,
|
||
|
pc_wallet: Pubkey,
|
||
|
#[clap(long, short)]
|
||
|
signer: Option<String>,
|
||
|
},
|
||
|
ListMarket {
|
||
|
payer: String,
|
||
|
dex_program_id: Pubkey,
|
||
|
#[clap(long, short)]
|
||
|
coin_mint: Pubkey,
|
||
|
#[clap(long, short)]
|
||
|
pc_mint: Pubkey,
|
||
|
#[clap(long)]
|
||
|
coin_lot_size: Option<u64>,
|
||
|
#[clap(long)]
|
||
|
pc_lot_size: Option<u64>,
|
||
|
},
|
||
|
InitializeTokenAccount {
|
||
|
mint: Pubkey,
|
||
|
owner_account: String,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
impl Opts {
|
||
|
fn client(&self) -> RpcClient {
|
||
|
RpcClient::new(self.cluster.url().to_string())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn main() -> Result<()> {
|
||
|
let opts = Opts::parse();
|
||
|
|
||
|
let client = opts.client();
|
||
|
|
||
|
match opts.command {
|
||
|
Command::Genesis {
|
||
|
payer,
|
||
|
mint,
|
||
|
owner_pubkey,
|
||
|
decimals,
|
||
|
} => {
|
||
|
let payer = read_keypair_file(&payer)?;
|
||
|
let mint = read_keypair_file(&mint)?;
|
||
|
genesis(&client, &payer, &mint, &owner_pubkey, decimals)?;
|
||
|
}
|
||
|
Command::Mint {
|
||
|
payer,
|
||
|
signer,
|
||
|
mint_pubkey,
|
||
|
recipient,
|
||
|
quantity,
|
||
|
} => {
|
||
|
let payer = read_keypair_file(&payer)?;
|
||
|
let minter = read_keypair_file(&signer)?;
|
||
|
match recipient.as_ref() {
|
||
|
Some(recipient) => {
|
||
|
mint_to_existing_account(
|
||
|
&client,
|
||
|
&payer,
|
||
|
&minter,
|
||
|
&mint_pubkey,
|
||
|
recipient,
|
||
|
quantity,
|
||
|
)?;
|
||
|
}
|
||
|
None => {
|
||
|
mint_to_new_account(&client, &payer, &minter, &mint_pubkey, quantity)?;
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
Command::CreateAccount { .. } => unimplemented!(),
|
||
|
Command::MatchOrders {
|
||
|
ref dex_program_id,
|
||
|
ref payer,
|
||
|
ref market,
|
||
|
ref coin_wallet,
|
||
|
ref pc_wallet,
|
||
|
} => {
|
||
|
let payer = read_keypair_file(&payer)?;
|
||
|
|
||
|
println!("Getting market keys ...");
|
||
|
let market_keys = get_keys_for_market(&client, dex_program_id, &market)?;
|
||
|
println!("{:#?}", market_keys);
|
||
|
match_orders(
|
||
|
&client,
|
||
|
dex_program_id,
|
||
|
&payer,
|
||
|
&market_keys,
|
||
|
coin_wallet,
|
||
|
pc_wallet,
|
||
|
)?;
|
||
|
}
|
||
|
Command::ConsumeEvents {
|
||
|
ref dex_program_id,
|
||
|
ref payer,
|
||
|
ref market,
|
||
|
ref coin_wallet,
|
||
|
ref pc_wallet,
|
||
|
ref num_workers,
|
||
|
ref events_per_worker,
|
||
|
ref num_accounts,
|
||
|
ref log_directory,
|
||
|
} => {
|
||
|
consume_events_loop(
|
||
|
&opts,
|
||
|
&dex_program_id,
|
||
|
&payer,
|
||
|
&market,
|
||
|
&coin_wallet,
|
||
|
&pc_wallet,
|
||
|
num_workers,
|
||
|
*events_per_worker,
|
||
|
num_accounts.unwrap_or(32),
|
||
|
log_directory,
|
||
|
);
|
||
|
}
|
||
|
Command::MonitorQueue {
|
||
|
dex_program_id,
|
||
|
market,
|
||
|
port,
|
||
|
} => {
|
||
|
let (send, recv) = std::sync::mpsc::channel();
|
||
|
let queue_send = send.clone();
|
||
|
let client = opts.client();
|
||
|
let _ = std::thread::spawn(move || accept_loop(port, send));
|
||
|
let websockets = std::thread::spawn(move || websockets_loop(recv));
|
||
|
let _ = std::thread::spawn(move || read_queue_length_loop(client,
|
||
|
dex_program_id,
|
||
|
market,
|
||
|
queue_send));
|
||
|
// Failures in the others will propagate to this loop via timeout
|
||
|
websockets.join();
|
||
|
}
|
||
|
Command::PrintEventQueue {
|
||
|
ref dex_program_id,
|
||
|
ref market,
|
||
|
} => {
|
||
|
let market_keys = get_keys_for_market(&client, dex_program_id, &market)?;
|
||
|
let event_q_data = client.get_account_data(&market_keys.event_q)?;
|
||
|
let inner: Cow<[u64]> = remove_dex_account_padding(&event_q_data)?;
|
||
|
let (header, events_seg0, events_seg1) = parse_event_queue(&inner)?;
|
||
|
println!("Header:\n{:#x?}", header);
|
||
|
println!("Seg0:\n{:#x?}", events_seg0);
|
||
|
println!("Seg1:\n{:#x?}", events_seg1);
|
||
|
}
|
||
|
Command::WholeShebang {
|
||
|
ref dex_program_id,
|
||
|
ref payer,
|
||
|
} => {
|
||
|
let payer = read_keypair_file(payer)?;
|
||
|
whole_shebang(&client, dex_program_id, &payer)?;
|
||
|
}
|
||
|
Command::SettleFunds {
|
||
|
ref payer,
|
||
|
ref dex_program_id,
|
||
|
ref market,
|
||
|
ref orders,
|
||
|
ref coin_wallet,
|
||
|
ref pc_wallet,
|
||
|
ref signer,
|
||
|
} => {
|
||
|
let payer = read_keypair_file(payer)?;
|
||
|
let signer = signer.as_ref().map(|s| read_keypair_file(&s)).transpose()?;
|
||
|
let market_keys = get_keys_for_market(&client, dex_program_id, &market)?;
|
||
|
settle_funds(
|
||
|
&client,
|
||
|
dex_program_id,
|
||
|
&payer,
|
||
|
&market_keys,
|
||
|
signer.as_ref(),
|
||
|
orders,
|
||
|
coin_wallet,
|
||
|
pc_wallet,
|
||
|
)?;
|
||
|
}
|
||
|
Command::ListMarket {
|
||
|
ref payer,
|
||
|
ref dex_program_id,
|
||
|
ref coin_mint,
|
||
|
ref pc_mint,
|
||
|
coin_lot_size,
|
||
|
pc_lot_size,
|
||
|
} => {
|
||
|
let payer = read_keypair_file(payer)?;
|
||
|
let market_keys = list_market(
|
||
|
&client,
|
||
|
dex_program_id,
|
||
|
&payer,
|
||
|
coin_mint,
|
||
|
pc_mint,
|
||
|
coin_lot_size.unwrap_or(1_000_000),
|
||
|
pc_lot_size.unwrap_or(10_000),
|
||
|
)?;
|
||
|
println!("Listed market: {:#?}", market_keys);
|
||
|
}
|
||
|
Command::InitializeTokenAccount {
|
||
|
ref mint,
|
||
|
ref owner_account,
|
||
|
} => {
|
||
|
let owner = read_keypair_file(owner_account)?;
|
||
|
let initialized_account = initialize_token_account(&client, mint, &owner)?;
|
||
|
println!("Initialized account: {}", initialized_account.pubkey());
|
||
|
}
|
||
|
}
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
fn send_txn(client: &RpcClient, txn: &Transaction, simulate: bool) -> Result<Signature> {
|
||
|
Ok(client.send_and_confirm_transaction_with_spinner_and_config(
|
||
|
txn,
|
||
|
CommitmentConfig::single(),
|
||
|
RpcSendTransactionConfig {
|
||
|
skip_preflight: true,
|
||
|
},
|
||
|
)?)
|
||
|
}
|
||
|
|
||
|
#[derive(Debug)]
|
||
|
struct MarketPubkeys {
|
||
|
market: Box<Pubkey>,
|
||
|
req_q: Box<Pubkey>,
|
||
|
event_q: Box<Pubkey>,
|
||
|
bids: Box<Pubkey>,
|
||
|
asks: Box<Pubkey>,
|
||
|
coin_vault: Box<Pubkey>,
|
||
|
pc_vault: Box<Pubkey>,
|
||
|
vault_signer_key: Box<Pubkey>,
|
||
|
}
|
||
|
|
||
|
#[cfg(target_endian = "little")]
|
||
|
fn remove_dex_account_padding<'a>(data: &'a [u8]) -> Result<Cow<'a, [u64]>> {
|
||
|
use serum_dex::state::{ACCOUNT_HEAD_PADDING, ACCOUNT_TAIL_PADDING};
|
||
|
let head = &data[..ACCOUNT_HEAD_PADDING.len()];
|
||
|
if data.len() < ACCOUNT_HEAD_PADDING.len() + ACCOUNT_TAIL_PADDING.len() {
|
||
|
return Err(format_err!(
|
||
|
"dex account length {} is too small to contain valid padding",
|
||
|
data.len()
|
||
|
));
|
||
|
}
|
||
|
if head != ACCOUNT_HEAD_PADDING {
|
||
|
return Err(format_err!("dex account head padding mismatch"));
|
||
|
}
|
||
|
let tail = &data[data.len() - ACCOUNT_TAIL_PADDING.len()..];
|
||
|
if tail != ACCOUNT_TAIL_PADDING {
|
||
|
return Err(format_err!("dex account tail padding mismatch"));
|
||
|
}
|
||
|
let inner_data_range = ACCOUNT_HEAD_PADDING.len()..(data.len() - ACCOUNT_TAIL_PADDING.len());
|
||
|
let inner: &'a [u8] = &data[inner_data_range];
|
||
|
let words: Cow<'a, [u64]> = match transmute_many_pedantic::<u64>(inner) {
|
||
|
Ok(word_slice) => Cow::Borrowed(word_slice),
|
||
|
Err(transmute_error) => {
|
||
|
let word_vec = transmute_error.copy().map_err(|e| e.without_src())?;
|
||
|
Cow::Owned(word_vec)
|
||
|
}
|
||
|
};
|
||
|
Ok(words)
|
||
|
}
|
||
|
|
||
|
#[cfg(target_endian = "little")]
|
||
|
fn get_keys_for_market<'a>(
|
||
|
client: &'a RpcClient,
|
||
|
program_id: &'a Pubkey,
|
||
|
market: &'a Pubkey,
|
||
|
) -> Result<MarketPubkeys> {
|
||
|
let account_data: Vec<u8> = client.get_account_data(&market)?;
|
||
|
let words: Cow<[u64]> = remove_dex_account_padding(&account_data)?;
|
||
|
let market_state: MarketState =
|
||
|
transmute_one_pedantic::<MarketState>(transmute_to_bytes(&words))
|
||
|
.map_err(|e| e.without_src())?;
|
||
|
market_state.check_flags()?;
|
||
|
let vault_signer_key =
|
||
|
gen_vault_signer_key(market_state.vault_signer_nonce, market, program_id)?;
|
||
|
assert_eq!(
|
||
|
transmute_to_bytes(&market_state.own_address),
|
||
|
market.as_ref()
|
||
|
);
|
||
|
Ok(MarketPubkeys {
|
||
|
market: Box::new(*market),
|
||
|
req_q: Box::new(Pubkey::new(transmute_one_to_bytes(&market_state.req_q))),
|
||
|
event_q: Box::new(Pubkey::new(transmute_one_to_bytes(&market_state.event_q))),
|
||
|
bids: Box::new(Pubkey::new(transmute_one_to_bytes(&market_state.bids))),
|
||
|
asks: Box::new(Pubkey::new(transmute_one_to_bytes(&market_state.asks))),
|
||
|
coin_vault: Box::new(Pubkey::new(transmute_one_to_bytes(
|
||
|
&market_state.coin_vault,
|
||
|
))),
|
||
|
pc_vault: Box::new(Pubkey::new(transmute_one_to_bytes(&market_state.pc_vault))),
|
||
|
vault_signer_key: Box::new(vault_signer_key),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
fn parse_event_queue(data_words: &[u64]) -> Result<(EventQueueHeader, &[Event], &[Event])> {
|
||
|
let (header_words, event_words) = data_words.split_at(size_of::<EventQueueHeader>() >> 3);
|
||
|
let header: EventQueueHeader =
|
||
|
transmute_one_pedantic(transmute_to_bytes(header_words)).map_err(|e| e.without_src())?;
|
||
|
let events: &[Event] = transmute_many::<_, SingleManyGuard>(transmute_to_bytes(event_words))
|
||
|
.map_err(|e| e.without_src())?;
|
||
|
let (tail_seg, head_seg) = events.split_at(header.head() as usize);
|
||
|
let head_len = head_seg.len().min(header.count() as usize);
|
||
|
let tail_len = header.count() as usize - head_len;
|
||
|
Ok((header, &head_seg[..head_len], &tail_seg[..tail_len]))
|
||
|
}
|
||
|
|
||
|
fn parse_req_queue(data_words: &[u64]) -> Result<(RequestQueueHeader, &[Request], &[Request])> {
|
||
|
let (header_words, request_words) = data_words.split_at(size_of::<RequestQueueHeader>() >> 3);
|
||
|
let header: RequestQueueHeader =
|
||
|
transmute_one_pedantic(transmute_to_bytes(header_words)).map_err(|e| e.without_src())?;
|
||
|
let request: &[Request] =
|
||
|
transmute_many::<_, SingleManyGuard>(transmute_to_bytes(request_words))
|
||
|
.map_err(|e| e.without_src())?;
|
||
|
let (tail_seg, head_seg) = request.split_at(header.head() as usize);
|
||
|
let head_len = head_seg.len().min(header.count() as usize);
|
||
|
let tail_len = header.count() as usize - head_len;
|
||
|
Ok((header, &head_seg[..head_len], &tail_seg[..tail_len]))
|
||
|
}
|
||
|
|
||
|
fn hash_accounts(val: &[u64; 4]) -> u64 {
|
||
|
val.iter().fold(0, |a, b| b.wrapping_add(a))
|
||
|
}
|
||
|
|
||
|
fn consume_events_loop(
|
||
|
opts: &Opts,
|
||
|
program_id: &Pubkey,
|
||
|
payer_path: &String,
|
||
|
market: &Pubkey,
|
||
|
coin_wallet: &Pubkey,
|
||
|
pc_wallet: &Pubkey,
|
||
|
num_workers: &usize,
|
||
|
events_per_worker: usize,
|
||
|
num_accounts: usize,
|
||
|
log_directory: &str,
|
||
|
) -> Result<()> {
|
||
|
let path = std::path::Path::new(log_directory);
|
||
|
let parent = path.parent().unwrap();
|
||
|
std::fs::create_dir_all(parent).unwrap();
|
||
|
let mut builder = FileLoggerBuilder::new(log_directory);
|
||
|
builder.level(Severity::Info).rotate_size(8 * 1024 * 1024);
|
||
|
let log = builder.build().unwrap();
|
||
|
let _guard = slog_scope::set_global_logger(log);
|
||
|
slog_stdlog::init().unwrap();
|
||
|
|
||
|
info!("Getting market keys ...");
|
||
|
let client = opts.client();
|
||
|
let market_keys = get_keys_for_market(&client, &program_id, &market)?;
|
||
|
info!("{:#?}", market_keys);
|
||
|
let pool = threadpool::ThreadPool::new(*num_workers);
|
||
|
loop {
|
||
|
let loop_start = std::time::Instant::now();
|
||
|
let start_time = std::time::Instant::now();
|
||
|
let event_q_data = client
|
||
|
.get_account_with_commitment(&market_keys.event_q, CommitmentConfig::recent())?
|
||
|
.value
|
||
|
.expect("Failed to retrieve account")
|
||
|
.data;
|
||
|
let req_q_data = client
|
||
|
.get_account_with_commitment(&market_keys.req_q, CommitmentConfig::recent())?
|
||
|
.value
|
||
|
.expect("Failed to retrieve account")
|
||
|
.data;
|
||
|
let inner: Cow<[u64]> = remove_dex_account_padding(&event_q_data)?;
|
||
|
let (header, seg0, seg1) = parse_event_queue(&inner)?;
|
||
|
let req_inner: Cow<[u64]> = remove_dex_account_padding(&req_q_data)?;
|
||
|
let (req_header, req_seg0, req_seg1) = parse_event_queue(&req_inner)?;
|
||
|
let event_q_len = seg0.len() + seg1.len();
|
||
|
let req_q_len = req_seg0.len() + req_seg1.len();
|
||
|
info!("Size of request queue is {}", req_q_len);
|
||
|
|
||
|
if event_q_len == 0 {
|
||
|
println!("Total event queue length: 0, returning early");
|
||
|
let one_hundred_millis = time::Duration::from_millis(300);
|
||
|
thread::sleep(one_hundred_millis);
|
||
|
} else {
|
||
|
info!("Total event queue length: {}", event_q_len);
|
||
|
let accounts = seg0.iter().chain(seg1.iter()).map(|event| event.owner);
|
||
|
let mut used_accounts = BTreeSet::new();
|
||
|
for account in accounts {
|
||
|
used_accounts.insert(account);
|
||
|
if (used_accounts.len() >= num_accounts) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
let mut orders_accounts: Vec<_> = used_accounts.into_iter().collect();
|
||
|
info!("Number of unique order accounts: {}", orders_accounts.len());
|
||
|
info!(
|
||
|
"First 5 accouts: {:?}",
|
||
|
orders_accounts
|
||
|
.iter()
|
||
|
.take(5)
|
||
|
.map(hash_accounts)
|
||
|
.collect::<Vec::<_>>()
|
||
|
);
|
||
|
|
||
|
let mut account_metas = Vec::with_capacity(orders_accounts.len() + 4);
|
||
|
for pubkey_words in orders_accounts {
|
||
|
let pubkey = Pubkey::new(transmute_to_bytes(&pubkey_words));
|
||
|
account_metas.push(AccountMeta::new(pubkey, false));
|
||
|
}
|
||
|
for pubkey in [
|
||
|
&market_keys.market,
|
||
|
&market_keys.event_q,
|
||
|
coin_wallet,
|
||
|
pc_wallet,
|
||
|
]
|
||
|
.iter()
|
||
|
{
|
||
|
account_metas.push(AccountMeta::new(**pubkey, false));
|
||
|
}
|
||
|
println!("Number of workers: {}", num_workers);
|
||
|
let end_time = std::time::Instant::now();
|
||
|
info!(
|
||
|
"Fetching {} events from the queue took {}",
|
||
|
event_q_len,
|
||
|
end_time.duration_since(start_time).as_millis()
|
||
|
);
|
||
|
for thread_num in 0..*num_workers {
|
||
|
let payer = read_keypair_file(&payer_path)?;
|
||
|
let program_id = program_id.clone();
|
||
|
let client = opts.client();
|
||
|
let account_metas = account_metas.clone();
|
||
|
|
||
|
pool.execute(move || {
|
||
|
consume_events_wrapper(
|
||
|
&client,
|
||
|
&program_id,
|
||
|
&payer,
|
||
|
account_metas,
|
||
|
thread_num,
|
||
|
events_per_worker,
|
||
|
)
|
||
|
.unwrap()
|
||
|
});
|
||
|
}
|
||
|
pool.join();
|
||
|
let loop_end = std::time::Instant::now();
|
||
|
info!(
|
||
|
"Total loop time took {}",
|
||
|
loop_end.duration_since(loop_start).as_millis()
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
return Ok(());
|
||
|
}
|
||
|
|
||
|
#[cfg(target_endian = "little")]
|
||
|
fn consume_events_wrapper(
|
||
|
client: &RpcClient,
|
||
|
program_id: &Pubkey,
|
||
|
payer: &Keypair,
|
||
|
account_metas: Vec<AccountMeta>,
|
||
|
thread_num: usize,
|
||
|
to_consume: usize,
|
||
|
) -> Result<()> {
|
||
|
let result = consume_events_once(
|
||
|
&client,
|
||
|
program_id,
|
||
|
&payer,
|
||
|
account_metas,
|
||
|
to_consume,
|
||
|
thread_num,
|
||
|
);
|
||
|
match result {
|
||
|
Ok(()) => (info!("[thread {}] Successfully consumed events.", thread_num)),
|
||
|
Err(err) => {
|
||
|
error!("[thread {}] Received error: {:?}", thread_num, err);
|
||
|
warn!(
|
||
|
"[thread {}] Done consuming events. Sleeping for 100ms...",
|
||
|
thread_num
|
||
|
);
|
||
|
}
|
||
|
};
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
#[cfg(target_endian = "little")]
|
||
|
fn consume_events_once(
|
||
|
client: &RpcClient,
|
||
|
program_id: &Pubkey,
|
||
|
payer: &Keypair,
|
||
|
account_metas: Vec<AccountMeta>,
|
||
|
to_consume: usize,
|
||
|
thread_number: usize,
|
||
|
) -> Result<()> {
|
||
|
let start = std::time::Instant::now();
|
||
|
let instruction_data: Vec<u8> = MarketInstruction::ConsumeEvents(to_consume as u16).pack();
|
||
|
|
||
|
let instruction = Instruction {
|
||
|
program_id: *program_id,
|
||
|
accounts: account_metas,
|
||
|
data: instruction_data,
|
||
|
};
|
||
|
let random_instruction = solana_sdk::system_instruction::transfer(
|
||
|
&payer.pubkey(),
|
||
|
&payer.pubkey(),
|
||
|
rand::random::<u64>() % 10000 + 1,
|
||
|
);
|
||
|
let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
|
||
|
info!("Consuming events ...");
|
||
|
let txn = Transaction::new_signed_with_payer(
|
||
|
&[instruction, random_instruction],
|
||
|
Some(&payer.pubkey()),
|
||
|
&[payer],
|
||
|
recent_hash,
|
||
|
);
|
||
|
info!("Consuming events ...");
|
||
|
let rval = send_txn(client, &txn, false).map(|_| ());
|
||
|
let end = std::time::Instant::now();
|
||
|
|
||
|
info!(
|
||
|
"Thread {} took {} milliseconds to consume {} events",
|
||
|
thread_number,
|
||
|
end.duration_since(start).as_millis(),
|
||
|
to_consume
|
||
|
);
|
||
|
rval
|
||
|
}
|
||
|
|
||
|
#[cfg(target_endian = "little")]
|
||
|
fn consume_events(
|
||
|
client: &RpcClient,
|
||
|
program_id: &Pubkey,
|
||
|
payer: &Keypair,
|
||
|
state: &MarketPubkeys,
|
||
|
coin_wallet: &Pubkey,
|
||
|
pc_wallet: &Pubkey,
|
||
|
) -> Result<()> {
|
||
|
let event_q_data = client.get_account_data(&state.event_q)?;
|
||
|
let inner: Cow<[u64]> = remove_dex_account_padding(&event_q_data)?;
|
||
|
let (header, seg0, seg1) = parse_event_queue(&inner)?;
|
||
|
|
||
|
if seg0.len() + seg1.len() == 0 {
|
||
|
info!("Total event queue length: 0, returning early");
|
||
|
return Ok(());
|
||
|
} else {
|
||
|
info!("Total event queue length: {}", seg0.len() + seg1.len());
|
||
|
}
|
||
|
let accounts = seg0.iter().chain(seg1.iter()).map(|event| event.owner);
|
||
|
let mut orders_accounts: Vec<_> = accounts.collect();
|
||
|
orders_accounts.sort_unstable();
|
||
|
orders_accounts.dedup();
|
||
|
// todo: Shuffle the accounts before truncating, to avoid favoring low sort order accounts
|
||
|
orders_accounts.truncate(32);
|
||
|
info!("Number of unique order accounts: {}", orders_accounts.len());
|
||
|
|
||
|
let mut account_metas = Vec::with_capacity(orders_accounts.len() + 4);
|
||
|
for pubkey_words in orders_accounts {
|
||
|
let pubkey = Pubkey::new(transmute_to_bytes(&pubkey_words));
|
||
|
account_metas.push(AccountMeta::new(pubkey, false));
|
||
|
}
|
||
|
for pubkey in [&state.market, &state.event_q, coin_wallet, pc_wallet].iter() {
|
||
|
account_metas.push(AccountMeta::new(**pubkey, false));
|
||
|
}
|
||
|
|
||
|
let instruction_data: Vec<u8> =
|
||
|
MarketInstruction::ConsumeEvents(account_metas.len() as u16).pack();
|
||
|
|
||
|
let instruction = Instruction {
|
||
|
program_id: *program_id,
|
||
|
accounts: account_metas,
|
||
|
data: instruction_data,
|
||
|
};
|
||
|
|
||
|
let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
|
||
|
info!("Consuming events ...");
|
||
|
let txn = Transaction::new_signed_with_payer(
|
||
|
std::slice::from_ref(&instruction),
|
||
|
Some(&payer.pubkey()),
|
||
|
&[payer],
|
||
|
recent_hash,
|
||
|
);
|
||
|
info!("Consuming events ...");
|
||
|
send_txn(client, &txn, false)?;
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
fn whole_shebang(client: &RpcClient, program_id: &Pubkey, payer: &Keypair) -> Result<()> {
|
||
|
let coin_mint = Keypair::generate(&mut OsRng);
|
||
|
println!("Coin mint: {}", coin_mint.pubkey());
|
||
|
genesis(client, payer, &coin_mint, &payer.pubkey(), 3)?;
|
||
|
|
||
|
let pc_mint = Keypair::generate(&mut OsRng);
|
||
|
println!("Pc mint: {}", pc_mint.pubkey());
|
||
|
genesis(client, payer, &pc_mint, &payer.pubkey(), 3)?;
|
||
|
|
||
|
let market_keys = list_market(
|
||
|
client,
|
||
|
program_id,
|
||
|
payer,
|
||
|
&coin_mint.pubkey(),
|
||
|
&pc_mint.pubkey(),
|
||
|
1_000_000,
|
||
|
10_000,
|
||
|
)?;
|
||
|
println!("Market keys: {:#?}", market_keys);
|
||
|
|
||
|
println!("Minting coin...");
|
||
|
let coin_wallet = mint_to_new_account(
|
||
|
client,
|
||
|
payer,
|
||
|
payer,
|
||
|
&coin_mint.pubkey(),
|
||
|
1_000_000_000_000_000,
|
||
|
)?;
|
||
|
println!("Minted {}", coin_wallet.pubkey());
|
||
|
|
||
|
println!("Minting price currency...");
|
||
|
let pc_wallet = mint_to_new_account(
|
||
|
client,
|
||
|
payer,
|
||
|
payer,
|
||
|
&pc_mint.pubkey(),
|
||
|
1_000_000_000_000_000,
|
||
|
)?;
|
||
|
println!("Minted {}", pc_wallet.pubkey());
|
||
|
|
||
|
println!("Placing bid...");
|
||
|
let mut orders = None;
|
||
|
place_order(
|
||
|
client,
|
||
|
program_id,
|
||
|
payer,
|
||
|
&pc_wallet.pubkey(),
|
||
|
&market_keys,
|
||
|
&mut orders,
|
||
|
NewOrderInstruction {
|
||
|
side: Side::Bid,
|
||
|
limit_price: NonZeroU64::new(500).unwrap(),
|
||
|
max_qty: NonZeroU64::new(1_000).unwrap(),
|
||
|
order_type: OrderType::Limit,
|
||
|
client_id: 019269,
|
||
|
},
|
||
|
)?;
|
||
|
|
||
|
println!("Bid account: {}", orders.unwrap());
|
||
|
|
||
|
println!("Placing offer...");
|
||
|
let mut orders = None;
|
||
|
place_order(
|
||
|
client,
|
||
|
program_id,
|
||
|
payer,
|
||
|
&coin_wallet.pubkey(),
|
||
|
&market_keys,
|
||
|
&mut orders,
|
||
|
NewOrderInstruction {
|
||
|
side: Side::Ask,
|
||
|
limit_price: NonZeroU64::new(499).unwrap(),
|
||
|
max_qty: NonZeroU64::new(1_000).unwrap(),
|
||
|
order_type: OrderType::Limit,
|
||
|
client_id: 985982,
|
||
|
},
|
||
|
)?;
|
||
|
|
||
|
println!("Ask account: {}", orders.unwrap());
|
||
|
|
||
|
println!("Matching orders in 15s ...");
|
||
|
std::thread::sleep(std::time::Duration::new(15, 0));
|
||
|
match_orders(
|
||
|
client,
|
||
|
program_id,
|
||
|
payer,
|
||
|
&market_keys,
|
||
|
&coin_wallet.pubkey(),
|
||
|
&pc_wallet.pubkey(),
|
||
|
)?;
|
||
|
println!("Consuming events in 15s ...");
|
||
|
std::thread::sleep(std::time::Duration::new(15, 0));
|
||
|
consume_events(
|
||
|
client,
|
||
|
program_id,
|
||
|
payer,
|
||
|
&market_keys,
|
||
|
&coin_wallet.pubkey(),
|
||
|
&pc_wallet.pubkey(),
|
||
|
)?;
|
||
|
settle_funds(
|
||
|
client,
|
||
|
program_id,
|
||
|
payer,
|
||
|
&market_keys,
|
||
|
Some(payer),
|
||
|
&orders.unwrap(),
|
||
|
&coin_wallet.pubkey(),
|
||
|
&pc_wallet.pubkey(),
|
||
|
)?;
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
fn place_order(
|
||
|
client: &RpcClient,
|
||
|
program_id: &Pubkey,
|
||
|
payer: &Keypair,
|
||
|
wallet: &Pubkey,
|
||
|
state: &MarketPubkeys,
|
||
|
orders: &mut Option<Pubkey>,
|
||
|
|
||
|
new_order: NewOrderInstruction,
|
||
|
) -> Result<()> {
|
||
|
let mut instructions = Vec::new();
|
||
|
let orders_keypair;
|
||
|
let mut signers = Vec::new();
|
||
|
let orders_pubkey = match *orders {
|
||
|
Some(pk) => pk,
|
||
|
None => {
|
||
|
let (orders_key, instruction) = create_dex_account(
|
||
|
client,
|
||
|
program_id,
|
||
|
&payer.pubkey(),
|
||
|
size_of::<serum_dex::state::OpenOrders>(),
|
||
|
)?;
|
||
|
orders_keypair = orders_key;
|
||
|
signers.push(&orders_keypair);
|
||
|
instructions.push(instruction);
|
||
|
orders_keypair.pubkey()
|
||
|
}
|
||
|
};
|
||
|
*orders = Some(orders_pubkey);
|
||
|
let side = new_order.side;
|
||
|
let data = MarketInstruction::NewOrder(new_order).pack();
|
||
|
let instruction = Instruction {
|
||
|
program_id: *program_id,
|
||
|
data,
|
||
|
accounts: vec![
|
||
|
AccountMeta::new(*state.market, false),
|
||
|
AccountMeta::new(orders_pubkey, false),
|
||
|
AccountMeta::new(*state.req_q, false),
|
||
|
AccountMeta::new(*wallet, false),
|
||
|
AccountMeta::new(payer.pubkey(), true),
|
||
|
AccountMeta::new(*state.coin_vault, false),
|
||
|
AccountMeta::new(*state.pc_vault, false),
|
||
|
AccountMeta::new(spl_token::ID, false),
|
||
|
AccountMeta::new_readonly(solana_sdk::sysvar::rent::ID, false),
|
||
|
],
|
||
|
};
|
||
|
instructions.push(instruction);
|
||
|
signers.push(payer);
|
||
|
|
||
|
let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
|
||
|
let txn = Transaction::new_signed_with_payer(
|
||
|
&instructions,
|
||
|
Some(&payer.pubkey()),
|
||
|
&signers,
|
||
|
recent_hash,
|
||
|
);
|
||
|
send_txn(client, &txn, false)?;
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
fn settle_funds(
|
||
|
client: &RpcClient,
|
||
|
program_id: &Pubkey,
|
||
|
payer: &Keypair,
|
||
|
state: &MarketPubkeys,
|
||
|
signer: Option<&Keypair>,
|
||
|
orders: &Pubkey,
|
||
|
coin_wallet: &Pubkey,
|
||
|
pc_wallet: &Pubkey,
|
||
|
) -> Result<()> {
|
||
|
let data = MarketInstruction::SettleFunds.pack();
|
||
|
let instruction = Instruction {
|
||
|
program_id: *program_id,
|
||
|
data,
|
||
|
accounts: vec![
|
||
|
AccountMeta::new(*state.market, false),
|
||
|
AccountMeta::new(*orders, false),
|
||
|
AccountMeta::new_readonly(signer.unwrap_or(payer).pubkey(), true),
|
||
|
AccountMeta::new(*state.coin_vault, false),
|
||
|
AccountMeta::new(*state.pc_vault, false),
|
||
|
AccountMeta::new(*coin_wallet, false),
|
||
|
AccountMeta::new(*pc_wallet, false),
|
||
|
AccountMeta::new_readonly(*state.vault_signer_key, false),
|
||
|
AccountMeta::new_readonly(spl_token::ID, false),
|
||
|
],
|
||
|
};
|
||
|
let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
|
||
|
let mut signers = vec![payer];
|
||
|
if let Some(s) = signer {
|
||
|
signers.push(s);
|
||
|
}
|
||
|
let txn = Transaction::new_signed_with_payer(
|
||
|
&[instruction],
|
||
|
Some(&payer.pubkey()),
|
||
|
&signers,
|
||
|
recent_hash,
|
||
|
);
|
||
|
let mut i = 0;
|
||
|
loop {
|
||
|
i += 1;
|
||
|
assert!(i < 10);
|
||
|
println!("Simulating SettleFunds instruction ...");
|
||
|
let result = client.simulate_transaction(&txn, true)?;
|
||
|
println!("{:#?}", result.value);
|
||
|
if result.value.err.is_none() {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
println!("Settling ...");
|
||
|
send_txn(client, &txn, false)?;
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
fn list_market(
|
||
|
client: &RpcClient,
|
||
|
program_id: &Pubkey,
|
||
|
payer: &Keypair,
|
||
|
coin_mint: &Pubkey,
|
||
|
pc_mint: &Pubkey,
|
||
|
coin_lot_size: u64,
|
||
|
pc_lot_size: u64,
|
||
|
) -> Result<MarketPubkeys> {
|
||
|
let (listing_keys, mut instructions) =
|
||
|
gen_listing_params(client, program_id, &payer.pubkey(), coin_mint, pc_mint)?;
|
||
|
let ListingKeys {
|
||
|
market_key,
|
||
|
req_q_key,
|
||
|
event_q_key,
|
||
|
bids_key,
|
||
|
asks_key,
|
||
|
vault_signer_pk,
|
||
|
vault_signer_nonce,
|
||
|
} = listing_keys;
|
||
|
|
||
|
println!("Creating coin vault...");
|
||
|
let coin_vault = create_account(client, coin_mint, &vault_signer_pk, payer)?;
|
||
|
|
||
|
println!("Creating pc vault...");
|
||
|
let pc_vault = create_account(client, pc_mint, &listing_keys.vault_signer_pk, payer)?;
|
||
|
|
||
|
let init_market_instruction = serum_dex::instruction::initialize_market(
|
||
|
&market_key.pubkey(),
|
||
|
program_id,
|
||
|
coin_mint,
|
||
|
pc_mint,
|
||
|
&coin_vault.pubkey(),
|
||
|
&pc_vault.pubkey(),
|
||
|
&bids_key.pubkey(),
|
||
|
&asks_key.pubkey(),
|
||
|
&req_q_key.pubkey(),
|
||
|
&event_q_key.pubkey(),
|
||
|
coin_lot_size,
|
||
|
pc_lot_size,
|
||
|
vault_signer_nonce,
|
||
|
100,
|
||
|
)?;
|
||
|
println!(
|
||
|
"initialize_market_instruction: {:#?}",
|
||
|
&init_market_instruction
|
||
|
);
|
||
|
|
||
|
instructions.push(init_market_instruction);
|
||
|
|
||
|
let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
|
||
|
let signers = vec![
|
||
|
payer,
|
||
|
&market_key,
|
||
|
&req_q_key,
|
||
|
&event_q_key,
|
||
|
&bids_key,
|
||
|
&asks_key,
|
||
|
&req_q_key,
|
||
|
&event_q_key,
|
||
|
];
|
||
|
let txn = Transaction::new_signed_with_payer(
|
||
|
&instructions,
|
||
|
Some(&payer.pubkey()),
|
||
|
&signers,
|
||
|
recent_hash,
|
||
|
);
|
||
|
|
||
|
println!("txn:\n{:#x?}", txn);
|
||
|
let result = client.simulate_transaction(&txn, true)?;
|
||
|
println!("{:#?}", result.value);
|
||
|
println!("Listing {} ...", market_key.pubkey());
|
||
|
send_txn(client, &txn, false)?;
|
||
|
|
||
|
Ok(MarketPubkeys {
|
||
|
market: Box::new(market_key.pubkey()),
|
||
|
req_q: Box::new(req_q_key.pubkey()),
|
||
|
event_q: Box::new(event_q_key.pubkey()),
|
||
|
bids: Box::new(bids_key.pubkey()),
|
||
|
asks: Box::new(asks_key.pubkey()),
|
||
|
coin_vault: Box::new(coin_vault.pubkey()),
|
||
|
pc_vault: Box::new(pc_vault.pubkey()),
|
||
|
vault_signer_key: Box::new(vault_signer_pk),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
struct ListingKeys {
|
||
|
market_key: Keypair,
|
||
|
req_q_key: Keypair,
|
||
|
event_q_key: Keypair,
|
||
|
bids_key: Keypair,
|
||
|
asks_key: Keypair,
|
||
|
vault_signer_pk: Pubkey,
|
||
|
vault_signer_nonce: u64,
|
||
|
}
|
||
|
|
||
|
fn gen_listing_params(
|
||
|
client: &RpcClient,
|
||
|
program_id: &Pubkey,
|
||
|
payer: &Pubkey,
|
||
|
coin_mint: &Pubkey,
|
||
|
pc_mint: &Pubkey,
|
||
|
) -> Result<(ListingKeys, Vec<Instruction>)> {
|
||
|
let (market_key, create_market) = create_dex_account(client, program_id, payer, 368)?;
|
||
|
let (req_q_key, create_req_q) = create_dex_account(client, program_id, payer, 640)?;
|
||
|
let (event_q_key, create_event_q) = create_dex_account(client, program_id, payer, 1 << 20)?;
|
||
|
let (bids_key, create_bids) = create_dex_account(client, program_id, payer, 1 << 16)?;
|
||
|
let (asks_key, create_asks) = create_dex_account(client, program_id, payer, 1 << 16)?;
|
||
|
let (vault_signer_nonce, vault_signer_pk) = {
|
||
|
let mut i = 0;
|
||
|
loop {
|
||
|
assert!(i < 100);
|
||
|
if let Ok(pk) = gen_vault_signer_key(i, &market_key.pubkey(), program_id) {
|
||
|
break (i, pk);
|
||
|
}
|
||
|
i += 1;
|
||
|
}
|
||
|
};
|
||
|
let info = ListingKeys {
|
||
|
market_key,
|
||
|
req_q_key,
|
||
|
event_q_key,
|
||
|
bids_key,
|
||
|
asks_key,
|
||
|
vault_signer_pk,
|
||
|
vault_signer_nonce,
|
||
|
};
|
||
|
let instructions = vec![
|
||
|
create_market,
|
||
|
create_req_q,
|
||
|
create_event_q,
|
||
|
create_bids,
|
||
|
create_asks,
|
||
|
];
|
||
|
Ok((info, instructions))
|
||
|
}
|
||
|
|
||
|
fn create_dex_account(
|
||
|
client: &RpcClient,
|
||
|
program_id: &Pubkey,
|
||
|
payer: &Pubkey,
|
||
|
unpadded_len: usize,
|
||
|
) -> Result<(Keypair, Instruction)> {
|
||
|
let len = unpadded_len + 12;
|
||
|
let key = Keypair::generate(&mut OsRng);
|
||
|
let create_account_instr = solana_sdk::system_instruction::create_account(
|
||
|
payer,
|
||
|
&key.pubkey(),
|
||
|
client.get_minimum_balance_for_rent_exemption(len)?,
|
||
|
len as u64,
|
||
|
program_id,
|
||
|
);
|
||
|
Ok((key, create_account_instr))
|
||
|
}
|
||
|
|
||
|
fn match_orders(
|
||
|
client: &RpcClient,
|
||
|
program_id: &Pubkey,
|
||
|
payer: &Keypair,
|
||
|
state: &MarketPubkeys,
|
||
|
coin_wallet: &Pubkey,
|
||
|
pc_wallet: &Pubkey,
|
||
|
) -> Result<()> {
|
||
|
let instruction_data: Vec<u8> = MarketInstruction::MatchOrders(2).pack();
|
||
|
|
||
|
let instruction = Instruction {
|
||
|
program_id: *program_id,
|
||
|
accounts: vec![
|
||
|
AccountMeta::new(*state.market, false),
|
||
|
AccountMeta::new(*state.req_q, false),
|
||
|
AccountMeta::new(*state.event_q, false),
|
||
|
AccountMeta::new(*state.bids, false),
|
||
|
AccountMeta::new(*state.asks, false),
|
||
|
AccountMeta::new(*coin_wallet, false),
|
||
|
AccountMeta::new(*pc_wallet, false),
|
||
|
],
|
||
|
data: instruction_data,
|
||
|
};
|
||
|
|
||
|
let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
|
||
|
let txn = Transaction::new_signed_with_payer(
|
||
|
std::slice::from_ref(&instruction),
|
||
|
Some(&payer.pubkey()),
|
||
|
&[payer],
|
||
|
recent_hash,
|
||
|
);
|
||
|
|
||
|
println!("Simulating order matching ...");
|
||
|
let result = client.simulate_transaction(&txn, true)?;
|
||
|
println!("{:#?}", result.value);
|
||
|
if result.value.err.is_none() {
|
||
|
println!("Matching orders ...");
|
||
|
send_txn(client, &txn, false)?;
|
||
|
}
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
fn create_account(
|
||
|
client: &RpcClient,
|
||
|
mint_pubkey: &Pubkey,
|
||
|
owner_pubkey: &Pubkey,
|
||
|
payer: &Keypair,
|
||
|
) -> Result<Keypair> {
|
||
|
let spl_account = Keypair::generate(&mut OsRng);
|
||
|
let signers = vec![payer, &spl_account];
|
||
|
|
||
|
let lamports = client.get_minimum_balance_for_rent_exemption(spl_token::state::Account::LEN)?;
|
||
|
|
||
|
let create_account_instr = solana_sdk::system_instruction::create_account(
|
||
|
&payer.pubkey(),
|
||
|
&spl_account.pubkey(),
|
||
|
lamports,
|
||
|
spl_token::state::Account::LEN as u64,
|
||
|
&spl_token::ID,
|
||
|
);
|
||
|
|
||
|
let init_account_instr = token_instruction::initialize_account(
|
||
|
&spl_token::ID,
|
||
|
&spl_account.pubkey(),
|
||
|
&mint_pubkey,
|
||
|
&owner_pubkey,
|
||
|
)?;
|
||
|
|
||
|
let instructions = vec![create_account_instr, init_account_instr];
|
||
|
|
||
|
let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
|
||
|
|
||
|
let txn = Transaction::new_signed_with_payer(
|
||
|
&instructions,
|
||
|
Some(&payer.pubkey()),
|
||
|
&signers,
|
||
|
recent_hash,
|
||
|
);
|
||
|
|
||
|
println!("Creating account: {} ...", spl_account.pubkey());
|
||
|
send_txn(client, &txn, false)?;
|
||
|
Ok(spl_account)
|
||
|
}
|
||
|
|
||
|
fn mint_to_existing_account(
|
||
|
client: &RpcClient,
|
||
|
payer: &Keypair,
|
||
|
minting_key: &Keypair,
|
||
|
mint: &Pubkey,
|
||
|
recipient: &Pubkey,
|
||
|
quantity: u64,
|
||
|
) -> Result<()> {
|
||
|
let signers = vec![payer, minting_key];
|
||
|
|
||
|
let mint_tokens_instr = token_instruction::mint_to(
|
||
|
&spl_token::ID,
|
||
|
mint,
|
||
|
recipient,
|
||
|
&minting_key.pubkey(),
|
||
|
&[],
|
||
|
quantity,
|
||
|
)?;
|
||
|
|
||
|
let instructions = vec![mint_tokens_instr];
|
||
|
let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
|
||
|
let txn = Transaction::new_signed_with_payer(
|
||
|
&instructions,
|
||
|
Some(&payer.pubkey()),
|
||
|
&signers,
|
||
|
recent_hash,
|
||
|
);
|
||
|
send_txn(client, &txn, false)?;
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
fn mint_to_new_account(
|
||
|
client: &RpcClient,
|
||
|
payer: &Keypair,
|
||
|
minting_key: &Keypair,
|
||
|
mint: &Pubkey,
|
||
|
quantity: u64,
|
||
|
) -> Result<Keypair> {
|
||
|
let recip_keypair = Keypair::generate(&mut OsRng);
|
||
|
|
||
|
let lamports = client.get_minimum_balance_for_rent_exemption(spl_token::state::Account::LEN)?;
|
||
|
|
||
|
let signers = vec![payer, minting_key, &recip_keypair];
|
||
|
|
||
|
let create_recip_instr = solana_sdk::system_instruction::create_account(
|
||
|
&payer.pubkey(),
|
||
|
&recip_keypair.pubkey(),
|
||
|
lamports,
|
||
|
spl_token::state::Account::LEN as u64,
|
||
|
&spl_token::ID,
|
||
|
);
|
||
|
|
||
|
let init_recip_instr = token_instruction::initialize_account(
|
||
|
&spl_token::ID,
|
||
|
&recip_keypair.pubkey(),
|
||
|
mint,
|
||
|
&payer.pubkey(),
|
||
|
)?;
|
||
|
|
||
|
let mint_tokens_instr = token_instruction::mint_to(
|
||
|
&spl_token::ID,
|
||
|
mint,
|
||
|
&recip_keypair.pubkey(),
|
||
|
&minting_key.pubkey(),
|
||
|
&[],
|
||
|
quantity,
|
||
|
)?;
|
||
|
|
||
|
let instructions = vec![create_recip_instr, init_recip_instr, mint_tokens_instr];
|
||
|
|
||
|
let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
|
||
|
let txn = Transaction::new_signed_with_payer(
|
||
|
&instructions,
|
||
|
Some(&payer.pubkey()),
|
||
|
&signers,
|
||
|
recent_hash,
|
||
|
);
|
||
|
|
||
|
send_txn(client, &txn, false)?;
|
||
|
Ok(recip_keypair)
|
||
|
}
|
||
|
|
||
|
fn initialize_token_account(client: &RpcClient, mint: &Pubkey, owner: &Keypair) -> Result<Keypair> {
|
||
|
let recip_keypair = Keypair::generate(&mut OsRng);
|
||
|
let lamports = client.get_minimum_balance_for_rent_exemption(spl_token::state::Account::LEN)?;
|
||
|
let create_recip_instr = solana_sdk::system_instruction::create_account(
|
||
|
&owner.pubkey(),
|
||
|
&recip_keypair.pubkey(),
|
||
|
lamports,
|
||
|
spl_token::state::Account::LEN as u64,
|
||
|
&spl_token::ID,
|
||
|
);
|
||
|
let init_recip_instr = token_instruction::initialize_account(
|
||
|
&spl_token::ID,
|
||
|
&recip_keypair.pubkey(),
|
||
|
mint,
|
||
|
&owner.pubkey(),
|
||
|
)?;
|
||
|
let signers = vec![owner, &recip_keypair];
|
||
|
let instructions = vec![create_recip_instr, init_recip_instr];
|
||
|
let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
|
||
|
let txn = Transaction::new_signed_with_payer(
|
||
|
&instructions,
|
||
|
Some(&owner.pubkey()),
|
||
|
&signers,
|
||
|
recent_hash,
|
||
|
);
|
||
|
send_txn(client, &txn, false)?;
|
||
|
Ok(recip_keypair)
|
||
|
}
|
||
|
|
||
|
fn genesis(
|
||
|
client: &RpcClient,
|
||
|
payer_keypair: &Keypair,
|
||
|
mint_keypair: &Keypair,
|
||
|
owner_pubkey: &Pubkey,
|
||
|
decimals: u8,
|
||
|
) -> Result<()> {
|
||
|
let signers = vec![payer_keypair, mint_keypair];
|
||
|
|
||
|
let lamports = client.get_minimum_balance_for_rent_exemption(spl_token::state::Mint::LEN)?;
|
||
|
|
||
|
let create_mint_account_instruction = solana_sdk::system_instruction::create_account(
|
||
|
&payer_keypair.pubkey(),
|
||
|
&mint_keypair.pubkey(),
|
||
|
lamports,
|
||
|
spl_token::state::Mint::LEN as u64,
|
||
|
&spl_token::ID,
|
||
|
);
|
||
|
let initialize_mint_instruction = token_instruction::initialize_mint(
|
||
|
&spl_token::ID,
|
||
|
&mint_keypair.pubkey(),
|
||
|
owner_pubkey,
|
||
|
None,
|
||
|
decimals,
|
||
|
)?;
|
||
|
let instructions = vec![create_mint_account_instruction, initialize_mint_instruction];
|
||
|
|
||
|
let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
|
||
|
let txn = Transaction::new_signed_with_payer(
|
||
|
&instructions,
|
||
|
Some(&payer_keypair.pubkey()),
|
||
|
&signers,
|
||
|
recent_hash,
|
||
|
);
|
||
|
|
||
|
send_txn(client, &txn, false)?;
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
enum MonitorEvent {
|
||
|
NumEvents(usize),
|
||
|
NewConn(std::net::TcpStream),
|
||
|
}
|
||
|
|
||
|
fn accept_loop(port: u16, mut send: Sender<MonitorEvent>) {
|
||
|
let address = format!("127.0.0.1:{}", port);
|
||
|
let listener = std::net::TcpListener::bind(&address).unwrap();
|
||
|
for stream in listener.incoming() {
|
||
|
send.send(MonitorEvent::NewConn(stream.unwrap())).unwrap();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn websockets_loop(mut recv: Receiver<MonitorEvent>) {
|
||
|
let mut websockets: Vec<tungstenite::WebSocket<std::net::TcpStream>> = Vec::new();
|
||
|
let recv_every = time::Duration::from_millis(10000);
|
||
|
while let Ok(value) = recv.recv_timeout(recv_every) {
|
||
|
match value {
|
||
|
MonitorEvent::NumEvents(events) => {
|
||
|
let message = format!("{{ \"events_in_queue\": {} }}", events);
|
||
|
let message = tungstenite::Message::Text(message);
|
||
|
for socket in &mut websockets {
|
||
|
socket.write_message(message.clone()).unwrap();
|
||
|
}
|
||
|
},
|
||
|
MonitorEvent::NewConn(conn) => {
|
||
|
// Tungstenite errors don't implement debug so we can't unwrap?
|
||
|
// Generally we just die here anyways
|
||
|
if let Ok(conn) = tungstenite::accept(conn) {
|
||
|
websockets.push(conn);
|
||
|
} else {
|
||
|
panic!("Couldn't accept websocket stream for unknown reason");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn read_queue_length_loop(
|
||
|
client: RpcClient,
|
||
|
program_id: Pubkey,
|
||
|
market: Pubkey,
|
||
|
sender: std::sync::mpsc::Sender<MonitorEvent>
|
||
|
) -> Result<()> {
|
||
|
let market_keys = get_keys_for_market(&client, &program_id, &market)?;
|
||
|
loop {
|
||
|
let event_q_data = client
|
||
|
.get_account_with_commitment(&market_keys.event_q, CommitmentConfig::recent())?
|
||
|
.value
|
||
|
.expect("Failed to retrieve account")
|
||
|
.data;
|
||
|
let inner: Cow<[u64]> = remove_dex_account_padding(&event_q_data)?;
|
||
|
let (header, seg0, seg1) = parse_event_queue(&inner)?;
|
||
|
let event_q_len = seg0.len() + seg1.len();
|
||
|
|
||
|
sender.send(MonitorEvent::NumEvents(event_q_len)).unwrap();
|
||
|
|
||
|
let send_every = time::Duration::from_millis(3000);
|
||
|
thread::sleep(send_every);
|
||
|
}
|
||
|
}
|