mango-v3/program/tests/program_test/mod.rs

1959 lines
69 KiB
Rust

use std::borrow::Borrow;
use std::mem::size_of;
use bincode::deserialize;
use fixed::types::I80F48;
use serum_dex::instruction::NewOrderInstructionV3;
use solana_program::entrypoint::ProgramResult;
use solana_program::{
account_info::AccountInfo,
clock::{Clock, UnixTimestamp},
program_option::COption,
program_pack::Pack,
pubkey::*,
rent::*,
system_instruction, sysvar,
};
use solana_program_test::*;
use solana_sdk::commitment_config::CommitmentLevel;
use solana_sdk::{
account::ReadableAccount,
instruction::Instruction,
signature::{Keypair, Signer},
transaction::Transaction,
transport::TransportError,
};
use spl_token::{state::*, *};
use mango::{entrypoint::*, ids::*, instruction::*, matching::*, oracle::*, state::*, utils::*};
use mango_common::Loadable;
use self::cookies::*;
pub mod assertions;
pub mod cookies;
pub mod scenarios;
const RUST_LOG_DEFAULT: &str = "solana_rbpf::vm=info,\
solana_program_runtime::stable_log=debug,\
solana_runtime::message_processor=debug,\
solana_runtime::system_instruction_processor=info,\
solana_program_test=info,\
solana_bpf_loader_program=debug"; // for - Program ... consumed 5857 of 200000 compute units
trait AddPacked {
fn add_packable_account<T: Pack>(
&mut self,
pubkey: Pubkey,
amount: u64,
data: &T,
owner: &Pubkey,
);
}
impl AddPacked for ProgramTest {
fn add_packable_account<T: Pack>(
&mut self,
pubkey: Pubkey,
amount: u64,
data: &T,
owner: &Pubkey,
) {
let mut account = solana_sdk::account::Account::new(amount, T::get_packed_len(), owner);
data.pack_into_slice(&mut account.data);
self.add_account(pubkey, account);
}
}
pub 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,
}
#[derive(Copy, Clone)]
pub struct MarketPubkeys {
pub market: Pubkey,
pub req_q: Pubkey,
pub event_q: Pubkey,
pub bids: Pubkey,
pub asks: Pubkey,
pub coin_vault: Pubkey,
pub pc_vault: Pubkey,
pub vault_signer_key: Pubkey,
}
pub struct MangoProgramTestConfig {
pub compute_limit: u64,
pub num_users: usize,
pub num_mints: usize,
pub consume_perp_events_count: usize,
}
impl MangoProgramTestConfig {
#[allow(dead_code)]
pub fn default() -> Self {
MangoProgramTestConfig {
compute_limit: 200_000,
num_users: 2,
num_mints: 16,
consume_perp_events_count: 3,
}
}
#[allow(dead_code)]
pub fn default_two_mints() -> Self {
MangoProgramTestConfig { num_mints: 2, ..Self::default() }
}
}
pub struct MangoProgramTest {
pub context: ProgramTestContext,
pub rent: Rent,
pub mango_program_id: Pubkey,
pub serum_program_id: Pubkey,
pub num_mints: usize,
pub quote_index: usize,
pub quote_mint: MintCookie,
pub mints: Vec<MintCookie>,
pub num_users: usize,
pub users: Vec<Keypair>,
pub token_accounts: Vec<Pubkey>, // user x mint
pub consume_perp_events_count: usize,
}
impl MangoProgramTest {
#[allow(dead_code)]
pub async fn start_new(config: &MangoProgramTestConfig) -> Self {
let mango_program_id = Pubkey::new_unique();
let serum_program_id = Pubkey::new_unique();
// Predefined mints, maybe can even add symbols to them
// TODO: Figure out where to put MNGO and MSRM mint
let mut mints: Vec<MintCookie> = vec![
MintCookie {
index: 0,
decimals: 6,
unit: 10u64.pow(6) as f64,
base_lot: 100 as f64,
quote_lot: 10 as f64,
pubkey: None, //Some(mngo_token::ID),
}, // symbol: "MNGO".to_string()
MintCookie {
index: 1,
decimals: 6,
unit: 10u64.pow(6) as f64,
base_lot: 100 as f64,
quote_lot: 10 as f64,
pubkey: None, //Some(msrm_token::ID),
}, // symbol: "MSRM".to_string()
MintCookie {
index: 2,
decimals: 6,
unit: 10u64.pow(6) as f64,
base_lot: 100 as f64,
quote_lot: 10 as f64,
pubkey: None,
}, // symbol: "BTC".to_string()
MintCookie {
index: 3,
decimals: 6,
unit: 10u64.pow(6) as f64,
base_lot: 1000 as f64,
quote_lot: 10 as f64,
pubkey: None,
}, // symbol: "ETH".to_string()
MintCookie {
index: 4,
decimals: 9,
unit: 10u64.pow(9) as f64,
base_lot: 100000000 as f64,
quote_lot: 100 as f64,
pubkey: None,
}, // symbol: "SOL".to_string()
MintCookie {
index: 5,
decimals: 6,
unit: 10u64.pow(6) as f64,
base_lot: 100000 as f64,
quote_lot: 100 as f64,
pubkey: None,
}, // symbol: "SRM".to_string()
MintCookie {
index: 6,
decimals: 6,
unit: 10u64.pow(6) as f64,
base_lot: 100 as f64,
quote_lot: 10 as f64,
pubkey: None,
}, // symbol: "BTC".to_string()
MintCookie {
index: 7,
decimals: 6,
unit: 10u64.pow(6) as f64,
base_lot: 100 as f64,
quote_lot: 10 as f64,
pubkey: None,
}, // symbol: "BTC".to_string()
MintCookie {
index: 8,
decimals: 6,
unit: 10u64.pow(6) as f64,
base_lot: 100 as f64,
quote_lot: 10 as f64,
pubkey: None,
}, // symbol: "BTC".to_string()
MintCookie {
index: 9,
decimals: 6,
unit: 10u64.pow(6) as f64,
base_lot: 100 as f64,
quote_lot: 10 as f64,
pubkey: None,
}, // symbol: "BTC".to_string()
MintCookie {
index: 10,
decimals: 6,
unit: 10u64.pow(6) as f64,
base_lot: 100 as f64,
quote_lot: 10 as f64,
pubkey: None,
}, // symbol: "BTC".to_string()
MintCookie {
index: 11,
decimals: 6,
unit: 10u64.pow(6) as f64,
base_lot: 100 as f64,
quote_lot: 10 as f64,
pubkey: None,
}, // symbol: "BTC".to_string()
MintCookie {
index: 12,
decimals: 6,
unit: 10u64.pow(6) as f64,
base_lot: 100 as f64,
quote_lot: 10 as f64,
pubkey: None,
}, // symbol: "BTC".to_string()
MintCookie {
index: 13,
decimals: 6,
unit: 10u64.pow(6) as f64,
base_lot: 100 as f64,
quote_lot: 10 as f64,
pubkey: None,
}, // symbol: "BTC".to_string()
MintCookie {
index: 14,
decimals: 6,
unit: 10u64.pow(6) as f64,
base_lot: 100 as f64,
quote_lot: 10 as f64,
pubkey: None,
}, // symbol: "BTC".to_string()
MintCookie {
index: 15,
decimals: 6,
unit: 10u64.pow(6) as f64,
base_lot: 0 as f64,
quote_lot: 0 as f64,
pubkey: None,
}, // symbol: "USDC".to_string()
];
let num_mints = config.num_mints as usize;
let quote_index = num_mints - 1;
let mut quote_mint = mints[(mints.len() - 1) as usize];
let num_users = config.num_users as usize;
// Make sure that the user defined length of mint list always have the quote_mint as last
quote_mint.index = quote_index;
mints[quote_index] = quote_mint;
let mut test = ProgramTest::new("mango", mango_program_id, processor!(process_instruction));
test.add_program("serum_dex", serum_program_id, processor!(process_serum_instruction));
// TODO: add more programs (oracles)
// limit to track compute unit increase
test.set_compute_max_units(config.compute_limit);
// Supress some of the logs
solana_logger::setup_with_default(RUST_LOG_DEFAULT);
// Add MNGO mint
test.add_packable_account(
mngo_token::ID,
u32::MAX as u64,
&Mint {
is_initialized: true,
mint_authority: COption::Some(Pubkey::new_unique()),
decimals: 6,
..Mint::default()
},
&spl_token::id(),
);
// Add MSRM mint
test.add_packable_account(
msrm_token::ID,
u32::MAX as u64,
&Mint {
is_initialized: true,
mint_authority: COption::Some(Pubkey::new_unique()),
decimals: 6,
..Mint::default()
},
&spl_token::id(),
);
// Add mints in loop
for mint_index in 0..num_mints {
let mint_pk: Pubkey;
if mints[mint_index].pubkey.is_none() {
mint_pk = Pubkey::new_unique();
} else {
mint_pk = mints[mint_index].pubkey.unwrap();
}
test.add_packable_account(
mint_pk,
u32::MAX as u64,
&Mint {
is_initialized: true,
mint_authority: COption::Some(Pubkey::new_unique()),
decimals: mints[mint_index].decimals,
..Mint::default()
},
&spl_token::id(),
);
mints[mint_index].pubkey = Some(mint_pk);
}
// add users in loop
let mut users = Vec::new();
let mut token_accounts = Vec::new();
for _ in 0..num_users {
let user_key = Keypair::new();
test.add_account(
user_key.pubkey(),
solana_sdk::account::Account::new(
u32::MAX as u64,
0,
&solana_sdk::system_program::id(),
),
);
// give every user 10^18 (< 2^60) of every token
// ~~ 1 trillion in case of 6 decimals
for mint_index in 0..num_mints {
let token_key = Pubkey::new_unique();
test.add_packable_account(
token_key,
u32::MAX as u64,
&spl_token::state::Account {
mint: mints[mint_index].pubkey.unwrap(),
owner: user_key.pubkey(),
amount: 1_000_000_000_000_000_000,
state: spl_token::state::AccountState::Initialized,
..spl_token::state::Account::default()
},
&spl_token::id(),
);
token_accounts.push(token_key);
}
users.push(user_key);
}
let mut context = test.start_with_context().await;
let rent = context.banks_client.get_rent().await.unwrap();
mints = mints[..num_mints].to_vec();
Self {
context,
rent,
mango_program_id,
serum_program_id,
num_mints,
quote_index,
quote_mint,
mints,
num_users,
users,
token_accounts,
consume_perp_events_count: config.consume_perp_events_count,
}
}
#[allow(dead_code)]
pub async fn process_transaction(
&mut self,
instructions: &[Instruction],
signers: Option<&[&Keypair]>,
) -> Result<(), TransportError> {
let mut transaction =
Transaction::new_with_payer(&instructions, Some(&self.context.payer.pubkey()));
let mut all_signers = vec![&self.context.payer];
if let Some(signers) = signers {
all_signers.extend_from_slice(signers);
}
// This fails when warping is involved - https://gitmemory.com/issue/solana-labs/solana/18201/868325078
// let recent_blockhash = self.context.banks_client.get_recent_blockhash().await.unwrap();
transaction.sign(&all_signers, self.context.last_blockhash);
self.context
.banks_client
.process_transaction_with_commitment(transaction, CommitmentLevel::Processed)
.await
}
#[allow(dead_code)]
pub async fn get_account(&mut self, address: Pubkey) -> solana_sdk::account::Account {
return self.context.banks_client.get_account(address).await.unwrap().unwrap();
}
#[allow(dead_code)]
pub fn get_payer_pk(&mut self) -> Pubkey {
return self.context.payer.pubkey();
}
#[allow(dead_code)]
pub async fn get_lamport_balance(&mut self, address: Pubkey) -> u64 {
self.context.banks_client.get_account(address).await.unwrap().unwrap().lamports()
}
#[allow(dead_code)]
pub async fn get_token_balance(&mut self, address: Pubkey) -> u64 {
let token = self.context.banks_client.get_account(address).await.unwrap().unwrap();
return spl_token::state::Account::unpack(&token.data[..]).unwrap().amount;
}
#[allow(dead_code)]
pub async fn get_oo_info(
&mut self,
mango_group_cookie: &MangoGroupCookie,
user_index: usize,
mint_index: usize,
) -> (I80F48, I80F48, I80F48, I80F48) {
let mango_account = mango_group_cookie.mango_accounts[user_index].mango_account;
let mut oo = self.get_account(mango_account.spot_open_orders[0]).await;
let clock = self.get_clock().await;
let acc = solana_program::account_info::AccountInfo::new(
&mango_account.spot_open_orders[mint_index],
false,
false,
&mut oo.lamports,
&mut oo.data,
&mango_group_cookie.mango_accounts[user_index].address,
false,
clock.epoch,
);
let open_orders = load_open_orders(&acc).unwrap();
let (quote_free, quote_locked, base_free, base_locked) = split_open_orders(&open_orders);
return (quote_free, quote_locked, base_free, base_locked);
}
#[allow(dead_code)]
pub async fn create_account(&mut self, size: usize, owner: &Pubkey) -> Pubkey {
let keypair = Keypair::new();
let rent = self.rent.minimum_balance(size);
let instructions = [system_instruction::create_account(
&self.context.payer.pubkey(),
&keypair.pubkey(),
rent as u64,
size as u64,
owner,
)];
self.process_transaction(&instructions, Some(&[&keypair])).await.unwrap();
return keypair.pubkey();
}
#[allow(dead_code)]
pub async fn create_mint(&mut self, mint_authority: &Pubkey) -> Pubkey {
let keypair = Keypair::new();
let rent = self.rent.minimum_balance(Mint::LEN);
let instructions = [
system_instruction::create_account(
&self.context.payer.pubkey(),
&keypair.pubkey(),
rent,
Mint::LEN as u64,
&spl_token::id(),
),
spl_token::instruction::initialize_mint(
&spl_token::id(),
&keypair.pubkey(),
&mint_authority,
None,
0,
)
.unwrap(),
];
self.process_transaction(&instructions, Some(&[&keypair])).await.unwrap();
return keypair.pubkey();
}
#[allow(dead_code)]
pub async fn create_token_account(&mut self, owner: &Pubkey, mint: &Pubkey) -> Pubkey {
let keypair = Keypair::new();
let rent = self.rent.minimum_balance(spl_token::state::Account::LEN);
let instructions = [
system_instruction::create_account(
&self.context.payer.pubkey(),
&keypair.pubkey(),
rent,
spl_token::state::Account::LEN as u64,
&spl_token::id(),
),
spl_token::instruction::initialize_account(
&spl_token::id(),
&keypair.pubkey(),
mint,
owner,
)
.unwrap(),
];
self.process_transaction(&instructions, Some(&[&keypair])).await.unwrap();
return keypair.pubkey();
}
pub async fn load_account<T: Loadable>(&mut self, acc_pk: Pubkey) -> T {
let acc = self.context.banks_client.get_account(acc_pk).await.unwrap().unwrap();
// let acc_info: AccountInfo = (&acc_pk, &mut acc).into();
return *T::load_from_bytes(&acc.data).unwrap();
}
#[allow(dead_code)]
pub async fn get_bincode_account<T: serde::de::DeserializeOwned>(
&mut self,
address: &Pubkey,
) -> T {
self.context
.banks_client
.get_account(*address)
.await
.unwrap()
.map(|a| deserialize::<T>(&a.data.borrow()).unwrap())
.expect(format!("GET-TEST-ACCOUNT-ERROR: Account {}", address).as_str())
}
#[allow(dead_code)]
pub async fn get_clock(&mut self) -> Clock {
self.get_bincode_account::<Clock>(&sysvar::clock::id()).await
}
#[allow(dead_code)]
pub async fn advance_clock_by_slots(&mut self, slots: u64) {
let mut clock: Clock = self.get_clock().await;
println!("clock slot before: {}", clock.slot);
self.context.warp_to_slot(clock.slot + slots).unwrap();
clock = self.get_clock().await;
println!("clock slot after: {}", clock.slot);
}
#[allow(dead_code)]
pub async fn advance_clock_past_timestamp(&mut self, unix_timestamp: UnixTimestamp) {
let mut clock: Clock = self.get_clock().await;
let mut n = 1;
while clock.unix_timestamp <= unix_timestamp {
// Since the exact time is not deterministic keep wrapping by arbitrary 400 slots until we pass the requested timestamp
self.context.warp_to_slot(clock.slot + 400).unwrap();
n = n + 1;
clock = self.get_clock().await;
}
}
#[allow(dead_code)]
pub async fn advance_clock_by_min_timespan(&mut self, time_span: u64) {
let clock: Clock = self.get_clock().await;
self.advance_clock_past_timestamp(clock.unix_timestamp + (time_span as i64)).await;
}
#[allow(dead_code)]
pub async fn advance_clock(&mut self) {
let clock: Clock = self.get_clock().await;
self.context.warp_to_slot(clock.slot + 2).unwrap();
}
#[allow(dead_code)]
pub async fn with_mango_cache(&mut self, mango_group: &MangoGroup) -> (Pubkey, MangoCache) {
let mango_cache = self.load_account::<MangoCache>(mango_group.mango_cache).await;
return (mango_group.mango_cache, mango_cache);
}
#[allow(dead_code)]
pub fn with_mint(&mut self, mint_index: usize) -> MintCookie {
return self.mints[mint_index];
}
#[allow(dead_code)]
pub fn with_user_token_account(&mut self, user_index: usize, mint_index: usize) -> Pubkey {
return self.token_accounts[(user_index * self.num_mints) + mint_index];
}
#[allow(dead_code)]
pub async fn with_mango_account_deposit(
&mut self,
mango_account_pk: &Pubkey,
mint_index: usize,
) -> u64 {
// self.mints last token index will not always be QUOTE_INDEX hence the check
let actual_mint_index =
if mint_index == self.quote_index { QUOTE_INDEX } else { mint_index };
let mango_account = self.load_account::<MangoAccount>(*mango_account_pk).await;
// TODO - make this use cached root bank deposit index instead
return (mango_account.deposits[actual_mint_index] * INDEX_START).to_num();
}
#[allow(dead_code)]
pub fn with_oracle_price(&mut self, base_mint: &MintCookie, price: f64) -> I80F48 {
return I80F48::from_num(price) * I80F48::from_num(self.quote_mint.unit)
/ I80F48::from_num(base_mint.unit);
}
#[allow(dead_code)]
pub async fn set_oracle(
&mut self,
mango_group: &MangoGroup,
mango_group_pk: &Pubkey,
oracle_pk: &Pubkey,
oracle_price: I80F48,
) {
let mango_program_id = self.mango_program_id;
let instructions = [
mango::instruction::set_oracle(
&mango_program_id,
&mango_group_pk,
&oracle_pk,
&self.context.payer.pubkey(),
oracle_price,
)
.unwrap(),
cache_prices(
&mango_program_id,
&mango_group_pk,
&mango_group.mango_cache,
&[*oracle_pk],
)
.unwrap(),
];
self.process_transaction(&instructions, None).await.unwrap();
}
#[allow(dead_code)]
pub fn base_size_number_to_lots(&mut self, mint: &MintCookie, quantity: f64) -> u64 {
return ((quantity * mint.unit) / mint.base_lot) as u64;
}
#[allow(dead_code)]
pub fn quote_size_number_to_lots(&mut self, mint: &MintCookie, price: f64, size: f64) -> u64 {
let limit_price = self.price_number_to_lots(&mint, price);
let max_coin_qty = self.base_size_number_to_lots(&mint, size);
return mint.quote_lot as u64 * limit_price * max_coin_qty;
}
#[allow(dead_code)]
pub fn price_number_to_lots(&mut self, mint: &MintCookie, price: f64) -> u64 {
return ((price * self.quote_mint.unit * mint.base_lot) / (mint.unit * mint.quote_lot))
as u64;
}
#[allow(dead_code)]
pub fn to_native(&mut self, mint: &MintCookie, size: f64) -> I80F48 {
return I80F48::from_num(mint.unit * size);
}
#[allow(dead_code)]
pub fn to_native_fixedint(&mut self, mint: &MintCookie, size: I80F48) -> I80F48 {
return I80F48::from_num(mint.unit) * size;
}
#[allow(dead_code)]
pub async fn with_root_bank(
&mut self,
mango_group: &MangoGroup,
mint_index: usize,
) -> (Pubkey, RootBank) {
// self.mints last token index will not always be QUOTE_INDEX hence the check
let actual_mint_index =
if mint_index == self.quote_index { QUOTE_INDEX } else { mint_index };
let root_bank_pk = mango_group.tokens[actual_mint_index].root_bank;
let root_bank = self.load_account::<RootBank>(root_bank_pk).await;
return (root_bank_pk, root_bank);
}
#[allow(dead_code)]
pub async fn with_node_bank(
&mut self,
root_bank: &RootBank,
bank_index: usize,
) -> (Pubkey, NodeBank) {
let node_bank_pk = root_bank.node_banks[bank_index];
let node_bank = self.load_account::<NodeBank>(node_bank_pk).await;
return (node_bank_pk, node_bank);
}
#[allow(dead_code)]
pub async fn place_perp_order(
&mut self,
mango_group_cookie: &MangoGroupCookie,
perp_market_cookie: &PerpMarketCookie,
user_index: usize,
order_side: Side,
order_size: u64,
order_price: u64,
order_id: u64,
order_type: OrderType,
reduce_only: bool,
) {
let mango_program_id = self.mango_program_id;
let mango_group = mango_group_cookie.mango_group;
let mango_group_pk = mango_group_cookie.address;
let mango_account = mango_group_cookie.mango_accounts[user_index].mango_account;
let mango_account_pk = mango_group_cookie.mango_accounts[user_index].address;
let perp_market = perp_market_cookie.perp_market;
let perp_market_pk = perp_market_cookie.address;
let user = Keypair::from_base58_string(&self.users[user_index].to_base58_string());
let instructions = [place_perp_order(
&mango_program_id,
&mango_group_pk,
&mango_account_pk,
&user.pubkey(),
&mango_group.mango_cache,
&perp_market_pk,
&perp_market.bids,
&perp_market.asks,
&perp_market.event_queue,
None,
&mango_account.spot_open_orders,
order_side,
order_price as i64,
order_size as i64,
order_id,
order_type,
reduce_only,
)
.unwrap()];
self.process_transaction(&instructions, Some(&[&user])).await.unwrap();
}
#[allow(dead_code)]
pub async fn place_perp_order2(
&mut self,
mango_group_cookie: &MangoGroupCookie,
perp_market_cookie: &PerpMarketCookie,
user_index: usize,
order_side: Side,
order_base_size: u64,
order_quote_size: u64,
order_price: u64,
order_id: u64,
order_type: OrderType,
reduce_only: bool,
expiry_timestamp: Option<u64>,
limit: u8,
) {
let mango_program_id = self.mango_program_id;
let mango_group = mango_group_cookie.mango_group;
let mango_group_pk = mango_group_cookie.address;
let mango_account = mango_group_cookie.mango_accounts[user_index].mango_account;
let mango_account_pk = mango_group_cookie.mango_accounts[user_index].address;
let perp_market = perp_market_cookie.perp_market;
let perp_market_pk = perp_market_cookie.address;
let user = Keypair::from_base58_string(&self.users[user_index].to_base58_string());
let open_orders_pks: Vec<Pubkey> = mango_account
.spot_open_orders
.iter()
.enumerate()
.filter_map(|(i, &pk)| if mango_account.in_margin_basket[i] { Some(pk) } else { None })
.collect();
let instructions = [place_perp_order2(
&mango_program_id,
&mango_group_pk,
&mango_account_pk,
&user.pubkey(),
&mango_group.mango_cache,
&perp_market_pk,
&perp_market.bids,
&perp_market.asks,
&perp_market.event_queue,
None,
&open_orders_pks,
order_side,
order_price as i64,
order_base_size as i64,
order_quote_size.min(i64::MAX as u64) as i64,
order_id,
order_type,
reduce_only,
expiry_timestamp,
limit,
)
.unwrap()];
self.process_transaction(&instructions, Some(&[&user])).await.unwrap();
}
#[allow(dead_code)]
pub async fn consume_perp_events(
&mut self,
mango_group_cookie: &MangoGroupCookie,
perp_market_cookie: &PerpMarketCookie,
mango_account_pks: &mut Vec<Pubkey>,
) {
let mango_program_id = self.mango_program_id;
let mango_group = mango_group_cookie.mango_group;
let mango_group_pk = mango_group_cookie.address;
let perp_market = perp_market_cookie.perp_market;
let perp_market_pk = perp_market_cookie.address;
let instructions = [consume_events(
&mango_program_id,
&mango_group_pk,
&mango_group.mango_cache,
&perp_market_pk,
&perp_market.event_queue,
&mut mango_account_pks[..],
self.consume_perp_events_count,
)
.unwrap()];
self.process_transaction(&instructions, None).await.unwrap();
}
#[allow(dead_code)]
pub async fn force_cancel_perp_orders(
&mut self,
mango_group_cookie: &MangoGroupCookie,
perp_market_cookie: &PerpMarketCookie,
user_index: usize,
) {
let mango_program_id = self.mango_program_id;
let mango_group = mango_group_cookie.mango_group;
let mango_group_pk = mango_group_cookie.address;
let perp_market = perp_market_cookie.perp_market;
let perp_market_pk = perp_market_cookie.address;
let mango_account = mango_group_cookie.mango_accounts[user_index].mango_account;
let mango_account_pk = mango_group_cookie.mango_accounts[user_index].address;
let instructions = [force_cancel_perp_orders(
&mango_program_id,
&mango_group_pk,
&mango_group.mango_cache,
&perp_market_pk,
&perp_market.bids,
&perp_market.asks,
&mango_account_pk,
&mango_account.spot_open_orders,
20,
)
.unwrap()];
self.process_transaction(&instructions, None).await.unwrap();
}
// pub fn get_pnl(
// &mut self,
// mango_group_cookie: &MangoGroupCookie,
// perp_market_cookie: &PerpMarketCookie,
// user_index: usize,
// ) {
// let mango_cache = mango_group_cookie.mango_cache;
// let mint = perp_market_cookie.mint;
// let mango_account = mango_group_cookie.mango_accounts[user_index].mango_account;
// let perp_account = mango_account.perp_accounts[mint.index];
// let price = mango_cache.price_cache[mint.index].price;
// return I80F48::from_num(perp_account.base_position) * I80F48::from_num(mint.base_lot) *
// price +
// I80F48::from_num(perp_account.quote_position);
// }
#[allow(dead_code)]
pub async fn settle_perp_funds(
&mut self,
mango_group_cookie: &MangoGroupCookie,
perp_market_cookie: &PerpMarketCookie,
user_a_index: usize,
user_b_index: usize,
) {
let mango_program_id = self.mango_program_id;
let mango_group = mango_group_cookie.mango_group;
let mango_group_pk = mango_group_cookie.address;
let mango_account_a_pk = mango_group_cookie.mango_accounts[user_a_index].address;
let mango_account_b_pk = mango_group_cookie.mango_accounts[user_b_index].address;
let mango_cache_pk = mango_group.mango_cache;
let market_index = perp_market_cookie.mint.index;
let (root_bank_pk, root_bank) = self.with_root_bank(&mango_group, self.quote_index).await;
let (node_bank_pk, _node_bank) = self.with_node_bank(&root_bank, 0).await;
let instructions = [settle_pnl(
&mango_program_id,
&mango_group_pk,
&mango_account_a_pk,
&mango_account_b_pk,
&mango_cache_pk,
&root_bank_pk,
&node_bank_pk,
market_index,
)
.unwrap()];
self.process_transaction(&instructions, None).await.unwrap();
}
#[allow(dead_code)]
pub fn create_dex_account(&mut self, unpadded_len: usize) -> (Keypair, Instruction) {
let serum_program_id = self.serum_program_id;
let key = Keypair::new();
let len = unpadded_len + 12;
let rent = self.rent.minimum_balance(len);
let create_account_instr = solana_sdk::system_instruction::create_account(
&self.context.payer.pubkey(),
&key.pubkey(),
rent,
len as u64,
&serum_program_id,
);
return (key, create_account_instr);
}
fn gen_listing_params(
&mut self,
_coin_mint: &Pubkey,
_pc_mint: &Pubkey,
) -> (ListingKeys, Vec<Instruction>) {
let serum_program_id = self.serum_program_id;
// let payer_pk = &self.context.payer.pubkey();
let (market_key, create_market) = self.create_dex_account(376);
let (req_q_key, create_req_q) = self.create_dex_account(640);
let (event_q_key, create_event_q) = self.create_dex_account(1 << 20);
let (bids_key, create_bids) = self.create_dex_account(1 << 16);
let (asks_key, create_asks) = self.create_dex_account(1 << 16);
let (vault_signer_pk, vault_signer_nonce) =
create_signer_key_and_nonce(&serum_program_id, &market_key.pubkey());
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];
return (info, instructions);
}
#[allow(dead_code)]
pub async fn list_spot_market(&mut self, base_index: usize) -> SpotMarketCookie {
let serum_program_id = self.serum_program_id;
let coin_mint = self.mints[base_index].pubkey.unwrap();
let pc_mint = self.mints[self.quote_index].pubkey.unwrap();
let (listing_keys, mut instructions) = self.gen_listing_params(&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;
let coin_vault = self.create_token_account(&vault_signer_pk, &coin_mint).await;
let pc_vault = self.create_token_account(&listing_keys.vault_signer_pk, &pc_mint).await;
let init_market_instruction = serum_dex::instruction::initialize_market(
&market_key.pubkey(),
&serum_program_id,
&coin_mint,
&pc_mint,
&coin_vault,
&pc_vault,
None,
None,
None,
&bids_key.pubkey(),
&asks_key.pubkey(),
&req_q_key.pubkey(),
&event_q_key.pubkey(),
self.mints[base_index].base_lot as u64,
self.mints[base_index].quote_lot as u64,
vault_signer_nonce,
100,
)
.unwrap();
instructions.push(init_market_instruction);
let signers = vec![
&market_key,
&req_q_key,
&event_q_key,
&bids_key,
&asks_key,
&req_q_key,
&event_q_key,
];
self.process_transaction(&instructions, Some(&signers)).await.unwrap();
SpotMarketCookie {
market: market_key.pubkey(),
req_q: req_q_key.pubkey(),
event_q: event_q_key.pubkey(),
bids: bids_key.pubkey(),
asks: asks_key.pubkey(),
coin_vault: coin_vault,
pc_vault: pc_vault,
vault_signer_key: vault_signer_pk,
mint: self.with_mint(base_index),
}
}
#[allow(dead_code)]
pub async fn consume_spot_events(
&mut self,
spot_market_cookie: &SpotMarketCookie,
open_orders: Vec<&Pubkey>,
user_index: usize,
) {
let serum_program_id = self.serum_program_id;
let coin_fee_receivable_account =
self.with_user_token_account(user_index, spot_market_cookie.mint.index);
let pc_fee_receivable_account = self.with_user_token_account(user_index, self.quote_index);
for open_order in open_orders {
let instructions = [serum_dex::instruction::consume_events(
&serum_program_id,
vec![open_order],
&spot_market_cookie.market,
&spot_market_cookie.event_q,
&coin_fee_receivable_account,
&pc_fee_receivable_account,
5,
)
.unwrap()];
self.process_transaction(&instructions, None).await.unwrap();
}
}
#[allow(dead_code)]
pub async fn init_spot_open_orders(
&mut self,
mango_group_pk: &Pubkey,
mango_group: &MangoGroup,
mango_account_pk: &Pubkey,
mango_account: &MangoAccount,
user_index: usize,
market_index: usize,
) -> Pubkey {
let (orders_key, create_account_instr) =
self.create_dex_account(size_of::<serum_dex::state::OpenOrders>());
let open_orders_pk = orders_key.pubkey();
let init_spot_open_orders_instruction = init_spot_open_orders(
&self.mango_program_id,
mango_group_pk,
mango_account_pk,
&mango_account.owner,
&self.serum_program_id,
&open_orders_pk,
&mango_group.spot_markets[market_index].spot_market,
&mango_group.signer_key,
)
.unwrap();
let instructions = vec![create_account_instr, init_spot_open_orders_instruction];
let user = Keypair::from_base58_string(&self.users[user_index].to_base58_string());
let signers = vec![&user, &orders_key];
self.process_transaction(&instructions, Some(&signers)).await.unwrap();
open_orders_pk
}
#[allow(dead_code)]
pub async fn create_mango_account(
&mut self,
mango_group_pk: &Pubkey,
user_index: usize,
account_num: u64,
payer: Option<&Keypair>,
) -> Pubkey {
let owner_key = &self.users[user_index];
let owner_pk = owner_key.pubkey();
let seeds: &[&[u8]] =
&[&mango_group_pk.as_ref(), &owner_pk.as_ref(), &account_num.to_le_bytes()];
let (mango_account_pk, _) = Pubkey::find_program_address(seeds, &self.mango_program_id);
let mut instruction = create_mango_account(
&self.mango_program_id,
mango_group_pk,
&mango_account_pk,
&owner_pk,
&solana_sdk::system_program::id(),
&payer.map(|k| k.pubkey()).unwrap_or(owner_pk),
account_num,
)
.unwrap();
// Allow testing the compatibility case with no payer
if payer.is_none() {
instruction.accounts.pop();
instruction.accounts[2].is_writable = true; // owner pays lamports
}
let instructions = vec![instruction];
let owner_key_c = Keypair::from_base58_string(&owner_key.to_base58_string());
let mut signers = vec![&owner_key_c];
if let Some(payer_key) = payer {
signers.push(payer_key);
}
self.process_transaction(&instructions, Some(&signers)).await.unwrap();
mango_account_pk
}
#[allow(dead_code)]
pub async fn create_spot_open_orders(
&mut self,
mango_group_pk: &Pubkey,
mango_group: &MangoGroup,
mango_account_pk: &Pubkey,
user_index: usize,
market_index: usize,
payer: Option<&Keypair>,
) -> Pubkey {
let open_orders_seeds: &[&[u8]] =
&[&mango_account_pk.as_ref(), &market_index.to_le_bytes(), b"OpenOrders"];
let (open_orders_pk, _) =
Pubkey::find_program_address(open_orders_seeds, &self.mango_program_id);
let owner_key = &self.users[user_index];
let owner_pk = owner_key.pubkey();
let mut instruction = create_spot_open_orders(
&self.mango_program_id,
mango_group_pk,
mango_account_pk,
&owner_pk,
&self.serum_program_id,
&open_orders_pk,
&mango_group.spot_markets[market_index].spot_market,
&mango_group.signer_key,
&payer.map(|k| k.pubkey()).unwrap_or(owner_pk),
)
.unwrap();
// Allow testing the compatibility case with no payer
if payer.is_none() {
instruction.accounts.pop();
instruction.accounts[2].is_writable = true; // owner pays lamports
}
let instructions = vec![instruction];
let owner_key_c = Keypair::from_bytes(&owner_key.to_bytes()).unwrap();
let mut signers = vec![&owner_key_c];
if let Some(payer_key) = payer {
signers.push(payer_key);
}
self.process_transaction(&instructions, Some(&signers)).await.unwrap();
open_orders_pk
}
#[allow(dead_code)]
pub async fn init_open_orders(&mut self) -> Pubkey {
let (orders_key, instruction) =
self.create_dex_account(size_of::<serum_dex::state::OpenOrders>());
let mut instructions = Vec::new();
let orders_keypair = orders_key;
instructions.push(instruction);
let orders_pk = orders_keypair.pubkey();
self.process_transaction(&instructions, Some(&[&orders_keypair])).await.unwrap();
return orders_pk;
}
#[allow(dead_code)]
pub async fn add_oracles_to_mango_group(&mut self, mango_group_pk: &Pubkey) -> Vec<Pubkey> {
let mango_program_id = self.mango_program_id;
let admin_pk = self.context.payer.pubkey();
let mut oracle_pks = Vec::new();
let mut instructions = Vec::new();
for _ in 0..self.quote_index {
let oracle_pk = self.create_account(size_of::<StubOracle>(), &mango_program_id).await;
instructions.push(
add_oracle(&mango_program_id, &mango_group_pk, &oracle_pk, &admin_pk).unwrap(),
);
oracle_pks.push(oracle_pk);
}
self.process_transaction(&instructions, None).await.unwrap();
return oracle_pks;
}
#[allow(dead_code)]
pub async fn cache_all_perp_markets(
&mut self,
mango_group: &MangoGroup,
mango_group_pk: &Pubkey,
perp_market_pks: &[Pubkey],
) {
let mango_program_id = self.mango_program_id;
let instructions = [cache_perp_markets(
&mango_program_id,
&mango_group_pk,
&mango_group.mango_cache,
&perp_market_pks,
)
.unwrap()];
self.process_transaction(&instructions, None).await.unwrap();
}
// pub async fn cache_all_root_banks(
// &mut self,
// mango_group: &MangoGroup,
// mango_group_pk: &Pubkey,
// ) {
// let mango_program_id = self.mango_program_id;
// let mut root_bank_pks = Vec::new();
// for token in mango_group.tokens.iter() {
// if token.root_bank != Pubkey::default() {
// root_bank_pks.push(token.root_bank);
// }
// }
//
// let instructions = [cache_root_banks(
// &mango_program_id,
// &mango_group_pk,
// &mango_group.mango_cache,
// &root_bank_pks,
// )
// .unwrap()];
// self.process_transaction(&instructions, None).await.unwrap();
// }
pub async fn cache_all_prices(
&mut self,
mango_group: &MangoGroup,
mango_group_pk: &Pubkey,
oracle_pks: &[Pubkey],
) {
let mango_program_id = self.mango_program_id;
let instructions = [cache_prices(
&mango_program_id,
&mango_group_pk,
&mango_group.mango_cache,
&oracle_pks,
)
.unwrap()];
self.process_transaction(&instructions, None).await.unwrap();
}
pub async fn update_all_root_banks(
&mut self,
mango_group_cookie: &MangoGroupCookie,
mango_group_pk: &Pubkey,
) {
let mango_program_id = self.mango_program_id;
let mut instructions = vec![];
let mango_group = &mango_group_cookie.mango_group;
println!("{} {}", self.num_mints, mango_group_cookie.root_banks.len());
for i in 0..self.num_mints {
let index = if i == self.num_mints - 1 { QUOTE_INDEX } else { i };
instructions.push(
update_root_bank(
&mango_program_id,
&mango_group_pk,
&mango_group.mango_cache,
&mango_group.tokens[index].root_bank,
&[mango_group_cookie.root_banks[i].node_banks[0]],
)
.unwrap(),
)
}
self.process_transaction(&instructions, None).await.unwrap();
}
pub async fn update_all_funding(&mut self, mango_group_cookie: &MangoGroupCookie) {
let mango_group = &mango_group_cookie.mango_group;
let mut instructions = vec![];
for pmc in mango_group_cookie.perp_markets.iter() {
let perp_market = &pmc.perp_market;
instructions.push(
update_funding(
&self.mango_program_id,
&mango_group_cookie.address,
&mango_group.mango_cache,
&pmc.address,
&perp_market.bids,
&perp_market.asks,
)
.unwrap(),
)
}
self.process_transaction(&instructions, None).await.unwrap();
}
// pub async fn update_funding(
// &mut self,
// mango_group_cookie: &MangoGroupCookie,
// perp_market_cookie: &PerpMarketCookie,
// ) {
// let mango_group = mango_group_cookie.mango_group;
// let mango_group_pk = mango_group_cookie.address;
// let mango_program_id = self.mango_program_id;
// let perp_market = perp_market_cookie.perp_market;
// let perp_market_pk = perp_market_cookie.address;
//
// let instructions = [update_funding(
// &mango_program_id,
// &mango_group_pk,
// &mango_group.mango_cache,
// &perp_market_pk,
// &perp_market.bids,
// &perp_market.asks,
// )
// .unwrap()];
// self.process_transaction(&instructions, None).await.unwrap();
// }
#[allow(dead_code)]
pub async fn place_spot_order(
&mut self,
mango_group_cookie: &MangoGroupCookie,
spot_market_cookie: &SpotMarketCookie,
user_index: usize,
order: NewOrderInstructionV3,
) {
let mango_program_id = self.mango_program_id;
let serum_program_id = self.serum_program_id;
let mango_group = mango_group_cookie.mango_group;
let mango_group_pk = mango_group_cookie.address;
let mango_account = mango_group_cookie.mango_accounts[user_index].mango_account;
let mango_account_pk = mango_group_cookie.mango_accounts[user_index].address;
let mint_index = spot_market_cookie.mint.index;
let user = Keypair::from_base58_string(&self.users[user_index].to_base58_string());
let (signer_pk, _signer_nonce) =
create_signer_key_and_nonce(&mango_program_id, &mango_group_pk);
let (mint_root_bank_pk, mint_root_bank) =
self.with_root_bank(&mango_group, mint_index).await;
let (mint_node_bank_pk, mint_node_bank) = self.with_node_bank(&mint_root_bank, 0).await;
let (quote_root_bank_pk, quote_root_bank) =
self.with_root_bank(&mango_group, self.quote_index).await;
let (quote_node_bank_pk, quote_node_bank) = self.with_node_bank(&quote_root_bank, 0).await;
// Only pass in open orders if in margin basket or current market index, and
// the only writable account should be OpenOrders for current market index
let mut open_orders_pks = Vec::new();
for x in 0..mango_account.spot_open_orders.len() {
if x == mint_index && mango_account.spot_open_orders[x] == Pubkey::default() {
open_orders_pks.push(
self.create_spot_open_orders(
&mango_group_pk,
&mango_group,
&mango_account_pk,
user_index,
x,
None,
)
.await,
);
} else {
open_orders_pks.push(mango_account.spot_open_orders[x]);
}
}
let (dex_signer_pk, _dex_signer_nonce) =
create_signer_key_and_nonce(&serum_program_id, &spot_market_cookie.market);
let instructions = [mango::instruction::place_spot_order(
&mango_program_id,
&mango_group_pk,
&mango_account_pk,
&user.pubkey(),
&mango_group.mango_cache,
&serum_program_id,
&spot_market_cookie.market,
&spot_market_cookie.bids,
&spot_market_cookie.asks,
&spot_market_cookie.req_q,
&spot_market_cookie.event_q,
&spot_market_cookie.coin_vault,
&spot_market_cookie.pc_vault,
&mint_root_bank_pk,
&mint_node_bank_pk,
&mint_node_bank.vault,
&quote_root_bank_pk,
&quote_node_bank_pk,
&quote_node_bank.vault,
&signer_pk,
&dex_signer_pk,
&mango_group.msrm_vault,
&open_orders_pks, // oo ais
mint_index,
order,
)
.unwrap()];
let signers = vec![&user];
self.process_transaction(&instructions, Some(&signers)).await.unwrap();
}
#[allow(dead_code)]
pub async fn place_spot_order_with_delegate(
&mut self,
mango_group_cookie: &MangoGroupCookie,
spot_market_cookie: &SpotMarketCookie,
user_index: usize,
delegate_user_index: usize,
order: NewOrderInstructionV3,
) -> Result<(), TransportError> {
let mango_program_id = self.mango_program_id;
let serum_program_id = self.serum_program_id;
let mango_group = mango_group_cookie.mango_group;
let mango_group_pk = mango_group_cookie.address;
let mango_account = mango_group_cookie.mango_accounts[user_index].mango_account;
let mango_account_pk = mango_group_cookie.mango_accounts[user_index].address;
let mint_index = spot_market_cookie.mint.index;
let delegate_user =
Keypair::from_base58_string(&self.users[delegate_user_index].to_base58_string());
let (signer_pk, _signer_nonce) =
create_signer_key_and_nonce(&mango_program_id, &mango_group_pk);
let (mint_root_bank_pk, mint_root_bank) =
self.with_root_bank(&mango_group, mint_index).await;
let (mint_node_bank_pk, mint_node_bank) = self.with_node_bank(&mint_root_bank, 0).await;
let (quote_root_bank_pk, quote_root_bank) =
self.with_root_bank(&mango_group, self.quote_index).await;
let (quote_node_bank_pk, quote_node_bank) = self.with_node_bank(&quote_root_bank, 0).await;
// Only pass in open orders if in margin basket or current market index, and
// the only writable account should be OpenOrders for current market index
let mut open_orders_pks = Vec::new();
for x in 0..mango_account.spot_open_orders.len() {
if x == mint_index && mango_account.spot_open_orders[x] == Pubkey::default() {
open_orders_pks.push(
self.create_spot_open_orders(
&mango_group_pk,
&mango_group,
&mango_account_pk,
user_index,
x,
None,
)
.await,
);
} else {
open_orders_pks.push(mango_account.spot_open_orders[x]);
}
}
let (dex_signer_pk, _dex_signer_nonce) =
create_signer_key_and_nonce(&serum_program_id, &spot_market_cookie.market);
let instructions = [mango::instruction::place_spot_order(
&mango_program_id,
&mango_group_pk,
&mango_account_pk,
&delegate_user.pubkey(),
&mango_group.mango_cache,
&serum_program_id,
&spot_market_cookie.market,
&spot_market_cookie.bids,
&spot_market_cookie.asks,
&spot_market_cookie.req_q,
&spot_market_cookie.event_q,
&spot_market_cookie.coin_vault,
&spot_market_cookie.pc_vault,
&mint_root_bank_pk,
&mint_node_bank_pk,
&mint_node_bank.vault,
&quote_root_bank_pk,
&quote_node_bank_pk,
&quote_node_bank.vault,
&signer_pk,
&dex_signer_pk,
&mango_group.msrm_vault,
&open_orders_pks, // oo ais
mint_index,
order,
)
.unwrap()];
let signers = vec![&delegate_user];
self.process_transaction(&instructions, Some(&signers)).await
}
#[allow(dead_code)]
pub async fn cancel_all_spot_orders(
&mut self,
mango_group_cookie: &MangoGroupCookie,
spot_market_cookie: &SpotMarketCookie,
user_index: usize,
) {
let mango_program_id = self.mango_program_id;
let mango_group = mango_group_cookie.mango_group;
let mango_group_pk = mango_group_cookie.address;
let mango_account = mango_group_cookie.mango_accounts[user_index].mango_account;
let mango_account_pk = mango_group_cookie.mango_accounts[user_index].address;
let user = Keypair::from_base58_string(&self.users[user_index].to_base58_string());
let mint_index = spot_market_cookie.mint.index;
let (signer_pk, _signer_nonce) =
create_signer_key_and_nonce(&mango_program_id, &mango_group_pk);
let (base_root_bank_pk, base_root_bank) =
self.with_root_bank(&mango_group, mint_index).await;
let (base_node_bank_pk, base_node_bank) = self.with_node_bank(&base_root_bank, 0).await;
let (quote_root_bank_pk, quote_root_bank) =
self.with_root_bank(&mango_group, self.quote_index).await;
let (quote_node_bank_pk, quote_node_bank) = self.with_node_bank(&quote_root_bank, 0).await;
let (dex_signer_pk, _dex_signer_nonce) =
create_signer_key_and_nonce(&self.serum_program_id, &spot_market_cookie.market);
let instructions = [cancel_all_spot_orders(
&mango_program_id,
&mango_group_pk,
&mango_group.mango_cache,
&mango_account_pk,
&user.pubkey(),
&base_root_bank_pk,
&base_node_bank_pk,
&base_node_bank.vault,
&quote_root_bank_pk,
&quote_node_bank_pk,
&quote_node_bank.vault,
&spot_market_cookie.market,
&spot_market_cookie.bids,
&spot_market_cookie.asks,
&mango_account.spot_open_orders[mint_index],
&signer_pk,
&spot_market_cookie.event_q,
&spot_market_cookie.coin_vault,
&spot_market_cookie.pc_vault,
&dex_signer_pk,
&mango_group.dex_program_id,
u8::MAX,
)
.unwrap()];
self.process_transaction(&instructions, Some(&[&user])).await.unwrap();
}
#[allow(dead_code)]
pub async fn settle_spot_funds(
&mut self,
mango_group_cookie: &MangoGroupCookie,
spot_market_cookie: &SpotMarketCookie,
user_index: usize,
) {
let mango_program_id = self.mango_program_id;
let serum_program_id = self.serum_program_id;
let mango_group = mango_group_cookie.mango_group;
let mango_group_pk = mango_group_cookie.address;
let mango_account = mango_group_cookie.mango_accounts[user_index].mango_account;
let mango_account_pk = mango_group_cookie.mango_accounts[user_index].address;
let mint_index = spot_market_cookie.mint.index;
let user = Keypair::from_base58_string(&self.users[user_index].to_base58_string());
let (signer_pk, _signer_nonce) =
create_signer_key_and_nonce(&mango_program_id, &mango_group_pk);
let (base_root_bank_pk, base_root_bank) =
self.with_root_bank(&mango_group, mint_index).await;
let (base_node_bank_pk, base_node_bank) = self.with_node_bank(&base_root_bank, 0).await;
let (quote_root_bank_pk, quote_root_bank) =
self.with_root_bank(&mango_group, self.quote_index).await;
let (quote_node_bank_pk, quote_node_bank) = self.with_node_bank(&quote_root_bank, 0).await;
let (dex_signer_pk, _dex_signer_nonce) =
create_signer_key_and_nonce(&serum_program_id, &spot_market_cookie.market);
let instructions = [mango::instruction::settle_funds(
&mango_program_id,
&mango_group_pk,
&mango_group.mango_cache,
&user.pubkey(),
&mango_account_pk,
&serum_program_id,
&spot_market_cookie.market,
&mango_account.spot_open_orders[mint_index],
&signer_pk,
&spot_market_cookie.coin_vault,
&spot_market_cookie.pc_vault,
&base_root_bank_pk,
&base_node_bank_pk,
&quote_root_bank_pk,
&quote_node_bank_pk,
&base_node_bank.vault,
&quote_node_bank.vault,
&dex_signer_pk,
)
.unwrap()];
let signers = vec![&user];
self.process_transaction(&instructions, Some(&signers)).await.unwrap();
}
#[allow(dead_code)]
pub async fn perform_deposit(
&mut self,
mango_group_cookie: &MangoGroupCookie,
user_index: usize,
mint_index: usize,
amount: u64,
) {
let mango_program_id = self.mango_program_id;
let mango_group = mango_group_cookie.mango_group;
let mango_group_pk = mango_group_cookie.address;
let mango_account_pk = mango_group_cookie.mango_accounts[user_index].address;
let user = Keypair::from_base58_string(&self.users[user_index].to_base58_string());
let user_token_account = self.with_user_token_account(user_index, mint_index);
let (root_bank_pk, root_bank) = self.with_root_bank(&mango_group, mint_index).await;
let (node_bank_pk, node_bank) = self.with_node_bank(&root_bank, 0).await;
let instructions = [deposit(
&mango_program_id,
&mango_group_pk,
&mango_account_pk,
&user.pubkey(),
&mango_group.mango_cache,
&root_bank_pk,
&node_bank_pk,
&node_bank.vault,
&user_token_account,
amount,
)
.unwrap()];
self.process_transaction(&instructions, Some(&[&user])).await.unwrap();
}
#[allow(dead_code)]
pub async fn perform_withdraw(
&mut self,
mango_group_cookie: &MangoGroupCookie,
user_index: usize,
mint_index: usize,
quantity: u64,
allow_borrow: bool,
) {
let mango_program_id = self.mango_program_id;
let mango_group = mango_group_cookie.mango_group;
let mango_group_pk = mango_group_cookie.address;
let mango_account = mango_group_cookie.mango_accounts[user_index].mango_account;
let mango_account_pk = mango_group_cookie.mango_accounts[user_index].address;
let user = Keypair::from_base58_string(&self.users[user_index].to_base58_string());
let user_token_account = self.with_user_token_account(user_index, mint_index);
let (signer_pk, _signer_nonce) =
create_signer_key_and_nonce(&mango_program_id, &mango_group_pk);
let (root_bank_pk, root_bank) = self.with_root_bank(&mango_group, mint_index).await;
let (node_bank_pk, node_bank) = self.with_node_bank(&root_bank, 0).await; // Note: not sure if nb_index is ever anything else than 0
let instructions = [withdraw(
&mango_program_id,
&mango_group_pk,
&mango_account_pk,
&user.pubkey(),
&mango_group.mango_cache,
&root_bank_pk,
&node_bank_pk,
&node_bank.vault,
&user_token_account,
&signer_pk,
&mango_account.spot_open_orders,
quantity,
allow_borrow,
)
.unwrap()];
self.process_transaction(&instructions, Some(&[&user])).await.unwrap();
}
#[allow(dead_code)]
pub async fn perform_withdraw_with_delegate(
&mut self,
mango_group_cookie: &MangoGroupCookie,
user_index: usize,
delegate_user_index: usize,
mint_index: usize,
quantity: u64,
allow_borrow: bool,
) -> Result<(), TransportError> {
let mango_program_id = self.mango_program_id;
let mango_group = mango_group_cookie.mango_group;
let mango_group_pk = mango_group_cookie.address;
let mango_account = mango_group_cookie.mango_accounts[user_index].mango_account;
let mango_account_pk = mango_group_cookie.mango_accounts[user_index].address;
let delegate_user =
Keypair::from_base58_string(&self.users[delegate_user_index].to_base58_string());
let user_token_account = self.with_user_token_account(user_index, mint_index);
let (signer_pk, _signer_nonce) =
create_signer_key_and_nonce(&mango_program_id, &mango_group_pk);
let (root_bank_pk, root_bank) = self.with_root_bank(&mango_group, mint_index).await;
let (node_bank_pk, node_bank) = self.with_node_bank(&root_bank, 0).await; // Note: not sure if nb_index is ever anything else than 0
let instructions = [withdraw(
&mango_program_id,
&mango_group_pk,
&mango_account_pk,
&delegate_user.pubkey(),
&mango_group.mango_cache,
&root_bank_pk,
&node_bank_pk,
&node_bank.vault,
&user_token_account,
&signer_pk,
&mango_account.spot_open_orders,
quantity,
allow_borrow,
)
.unwrap()];
self.process_transaction(&instructions, Some(&[&delegate_user])).await
}
#[allow(dead_code)]
pub async fn perform_set_delegate(
&mut self,
mango_group_cookie: &MangoGroupCookie,
user_index: usize,
delegate_user_index: usize,
) {
let mango_program_id = self.mango_program_id;
let mango_group_pk = mango_group_cookie.address;
let mango_account_pk = mango_group_cookie.mango_accounts[user_index].address;
let user = Keypair::from_base58_string(&self.users[user_index].to_base58_string());
let delegate =
Keypair::from_base58_string(&self.users[delegate_user_index].to_base58_string());
let instructions = [set_delegate(
&mango_program_id,
&mango_group_pk,
&mango_account_pk,
&user.pubkey(),
&delegate.pubkey(),
)
.unwrap()];
self.process_transaction(&instructions, Some(&[&user])).await.unwrap();
}
#[allow(dead_code)]
pub async fn perform_reset_delegate(
&mut self,
mango_group_cookie: &MangoGroupCookie,
user_index: usize,
) {
let mango_program_id = self.mango_program_id;
let mango_group_pk = mango_group_cookie.address;
let mango_account_pk = mango_group_cookie.mango_accounts[user_index].address;
let user = Keypair::from_base58_string(&self.users[user_index].to_base58_string());
let instructions = [set_delegate(
&mango_program_id,
&mango_group_pk,
&mango_account_pk,
&user.pubkey(),
&Pubkey::default(),
)
.unwrap()];
self.process_transaction(&instructions, Some(&[&user])).await.unwrap();
}
#[allow(dead_code)]
pub async fn perform_liquidate_token_and_token(
&mut self,
mango_group_cookie: &mut MangoGroupCookie,
liqee_index: usize,
liqor_index: usize,
asset_index: usize,
liab_index: usize,
max_liab_transfer: I80F48,
) {
let mango_program_id = self.mango_program_id;
let mango_group = mango_group_cookie.mango_group;
let mango_group_pk = mango_group_cookie.address;
let liqee_mango_account = mango_group_cookie.mango_accounts[liqee_index].mango_account;
let liqee_mango_account_pk = mango_group_cookie.mango_accounts[liqee_index].address;
let liqor_mango_account = mango_group_cookie.mango_accounts[liqor_index].mango_account;
let liqor_mango_account_pk = mango_group_cookie.mango_accounts[liqor_index].address;
let liqor = Keypair::from_base58_string(&self.users[liqor_index].to_base58_string());
let (asset_root_bank_pk, asset_root_bank) =
self.with_root_bank(&mango_group, asset_index).await;
let (asset_node_bank_pk, _asset_node_bank) = self.with_node_bank(&asset_root_bank, 0).await;
let (liab_root_bank_pk, liab_root_bank) =
self.with_root_bank(&mango_group, liab_index).await;
let (liab_node_bank_pk, _liab_node_bank) = self.with_node_bank(&liab_root_bank, 0).await;
let instructions = vec![mango::instruction::liquidate_token_and_token(
&mango_program_id,
&mango_group_pk,
&mango_group.mango_cache,
&liqee_mango_account_pk,
&liqor_mango_account_pk,
&liqor.pubkey(),
&asset_root_bank_pk,
&asset_node_bank_pk,
&liab_root_bank_pk,
&liab_node_bank_pk,
&liqee_mango_account.spot_open_orders,
&liqor_mango_account.spot_open_orders,
max_liab_transfer,
)
.unwrap()];
self.process_transaction(&instructions, Some(&[&liqor])).await.unwrap();
mango_group_cookie.mango_accounts[liqee_index].mango_account =
self.load_account::<MangoAccount>(liqee_mango_account_pk).await;
mango_group_cookie.mango_accounts[liqor_index].mango_account =
self.load_account::<MangoAccount>(liqor_mango_account_pk).await;
}
#[allow(dead_code)]
pub async fn perform_liquidate_token_and_perp(
&mut self,
mango_group_cookie: &mut MangoGroupCookie,
liqee_index: usize,
liqor_index: usize,
asset_type: AssetType,
asset_index: usize,
liab_type: AssetType,
liab_index: usize,
max_liab_transfer: I80F48,
) {
let mango_program_id = self.mango_program_id;
let mango_group = mango_group_cookie.mango_group;
let mango_group_pk = mango_group_cookie.address;
let liqee_mango_account = mango_group_cookie.mango_accounts[liqee_index].mango_account;
let liqee_mango_account_pk = mango_group_cookie.mango_accounts[liqee_index].address;
let liqor_mango_account = mango_group_cookie.mango_accounts[liqor_index].mango_account;
let liqor_mango_account_pk = mango_group_cookie.mango_accounts[liqor_index].address;
let liqor = Keypair::from_base58_string(&self.users[liqor_index].to_base58_string());
let (root_bank_pk, root_bank) = self.with_root_bank(&mango_group, asset_index).await;
let (node_bank_pk, _node_bank) = self.with_node_bank(&root_bank, 0).await;
let instructions = vec![mango::instruction::liquidate_token_and_perp(
&mango_program_id,
&mango_group_pk,
&mango_group.mango_cache,
&liqee_mango_account_pk,
&liqor_mango_account_pk,
&liqor.pubkey(),
&root_bank_pk,
&node_bank_pk,
&liqee_mango_account.spot_open_orders,
&liqor_mango_account.spot_open_orders,
asset_type,
asset_index,
liab_type,
liab_index,
max_liab_transfer,
)
.unwrap()];
self.process_transaction(&instructions, Some(&[&liqor])).await.unwrap();
mango_group_cookie.mango_accounts[liqee_index].mango_account =
self.load_account::<MangoAccount>(liqee_mango_account_pk).await;
mango_group_cookie.mango_accounts[liqor_index].mango_account =
self.load_account::<MangoAccount>(liqor_mango_account_pk).await;
}
#[allow(dead_code)]
pub async fn perform_liquidate_perp_market(
&mut self,
mango_group_cookie: &mut MangoGroupCookie,
mint_index: usize,
liqee_index: usize,
liqor_index: usize,
base_transfer_request: i64,
) {
let mango_program_id = self.mango_program_id;
let mango_group = mango_group_cookie.mango_group;
let mango_group_pk = mango_group_cookie.address;
let liqee_mango_account = mango_group_cookie.mango_accounts[liqee_index].mango_account;
let liqee_mango_account_pk = mango_group_cookie.mango_accounts[liqee_index].address;
let liqor_mango_account = mango_group_cookie.mango_accounts[liqor_index].mango_account;
let liqor_mango_account_pk = mango_group_cookie.mango_accounts[liqor_index].address;
let liqor = Keypair::from_base58_string(&self.users[liqor_index].to_base58_string());
let instructions = vec![mango::instruction::liquidate_perp_market(
&mango_program_id,
&mango_group_pk,
&mango_group.mango_cache,
&mango_group.perp_markets[mint_index].perp_market,
&mango_group_cookie.perp_markets[mint_index].perp_market.event_queue,
&liqee_mango_account_pk,
&liqor_mango_account_pk,
&liqor.pubkey(),
&liqee_mango_account.spot_open_orders,
&liqor_mango_account.spot_open_orders,
base_transfer_request,
)
.unwrap()];
self.process_transaction(&instructions, Some(&[&liqor])).await.unwrap();
mango_group_cookie.mango_accounts[liqee_index].mango_account =
self.load_account::<MangoAccount>(liqee_mango_account_pk).await;
mango_group_cookie.mango_accounts[liqor_index].mango_account =
self.load_account::<MangoAccount>(liqor_mango_account_pk).await;
}
}
fn process_serum_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
Ok(serum_dex::state::State::process(program_id, accounts, instruction_data)?)
}