#![cfg(test)]
use super::*;
use crate::{
common::mainnet_sim::{self, fixtures},
instruction::{
stonkfun_quote_route::build_quote_route,
utils::{meteora_dlmm as dl, raydium_clmm as cl, whirlpool as wp},
},
swqos::TradeType,
};
use solana_account_decoder::UiAccountEncoding;
use solana_commitment_config::CommitmentConfig;
use solana_rpc_client_api::config::{
RpcSimulateTransactionAccountsConfig, RpcSimulateTransactionConfig,
};
use solana_sdk::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
signer::Signer,
};
use solana_transaction_status_client_types::UiTransactionEncoding;
async fn prepare(
pool: Pubkey,
) -> (crate::common::SolanaRpcClient, SubscriptionAccountCache, CacheReadContext, u64) {
let rpc = mainnet_sim::rpc_client();
let a = rpc.get_account(&pool).await.expect("snapshot pool");
let mut keys = vec![pool, solana_sdk::sysvar::clock::ID];
if a.owner == wp::PROGRAM_ID {
let p = wp::decode_whirlpool(&a.data).unwrap();
keys.extend([p.token_mint_a, p.token_mint_b, wp::oracle(&pool)]);
for offset in -6..=6 {
keys.push(wp::tick_array_pda(
&pool,
wp::get_start_tick_index(p.tick_current_index, p.tick_spacing, offset),
));
}
} else if a.owner == cl::PROGRAM_ID {
let p = cl::decode_pool_state(&a.data).unwrap();
keys.extend([
p.amm_config,
p.token_mint_0,
p.token_mint_1,
cl::tick_array_bitmap_extension(&pool),
]);
let start = cl::get_array_start_index(p.tick_current, p.tick_spacing);
for offset in -6..=6 {
keys.push(cl::tick_array_pda(&pool, start + offset * cl::tick_count(p.tick_spacing)));
}
} else {
let p = dl::decode_lb_pair(&a.data).unwrap();
keys.extend([p.token_x_mint, p.token_y_mint, dl::bitmap_extension_pda(&pool)]);
let start = dl::bin_id_to_bin_array_index(p.active_id);
for offset in -6..=6 {
keys.push(dl::bin_array_pda(&pool, start + offset));
}
}
let response = rpc
.get_multiple_accounts_with_commitment(&keys, CommitmentConfig::processed())
.await
.expect("background snapshot batch");
let mut cache = SubscriptionAccountCache::default();
let mut epoch = 0;
let mut timestamp = 0;
for (key, account) in keys.into_iter().zip(response.value) {
if let Some(account) = account {
if key == solana_sdk::sysvar::clock::ID {
epoch = u64::from_le_bytes(account.data[16..24].try_into().unwrap());
timestamp = i64::from_le_bytes(account.data[32..40].try_into().unwrap()) as u64;
}
cache
.update(
key,
CachedAccount {
owner: account.owner,
data: account.data,
slot: response.context.slot,
write_version: 0,
},
)
.unwrap();
}
}
let ctx = CacheReadContext { slot: response.context.slot, epoch, maximum_slot_age: 0 };
(rpc, cache, ctx, timestamp)
}
async fn check(pool: Pubkey, reverse: bool) {
if !mainnet_sim::enabled() {
return;
}
let (rpc, cache, ctx, time) = prepare(pool).await;
let (input, output, amount) = if reverse {
(fixtures::USDC_MINT, crate::constants::WSOL_TOKEN_ACCOUNT, 100_000)
} else {
(crate::constants::WSOL_TOKEN_ACCOUNT, fixtures::USDC_MINT, 1_000_000)
};
let hint = PoolTradeHint { pool, input_mint: input, output_mint: output };
let q = cache
.quote_exact_in(
hint,
CachedQuoteRequest {
amount_in: amount,
slippage_basis_points: 100,
unix_timestamp: time,
maximum_arrays: 6,
},
ctx,
)
.unwrap();
let route = StonkFunQuoteRoute::buy(vec![q.hop]);
let wallet = mainnet_sim::create_wallet();
let mut params = mainnet_sim::swap_params(
wallet.clone(),
TradeType::Buy,
input,
output,
amount,
100,
DexParamEnum::StonkFunQuoteRoute(route.clone()),
);
params.simulate = false;
params.wait_tx_confirmed = false;
params.rpc = None;
params.create_input_mint_ata = false;
params.close_input_mint_ata = false;
params.close_output_mint_ata = false;
params = params.without_rpc().unwrap();
let user_input =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
&wallet.pubkey(),
&input,
&crate::constants::TOKEN_PROGRAM,
params.open_seed_optimize,
);
let mut funding = Vec::new();
if reverse {
crate::instruction::token_account_setup::push_create_user_token_account(
&mut funding,
&wallet.pubkey(),
&input,
&crate::constants::TOKEN_PROGRAM,
params.open_seed_optimize,
);
let funding_pool = if pool == fixtures::ORCA_WHIRLPOOL_SOL_USDC {
fixtures::RAYDIUM_CLMM_SOL_USDC
} else {
fixtures::ORCA_WHIRLPOOL_SOL_USDC
};
let funding_state = rpc.get_account(&funding_pool).await.expect("test funding pool");
let source = if funding_state.owner == wp::PROGRAM_ID {
wp::decode_whirlpool(&funding_state.data).unwrap().token_vault_b
} else {
cl::decode_pool_state(&funding_state.data).unwrap().token_vault_1
};
let holder = rpc.get_account(&source).await.expect("test source");
assert_eq!(&holder.data[..32], input.as_ref());
let authority = Pubkey::new_from_array(holder.data[32..64].try_into().unwrap());
let mut data = vec![3];
data.extend_from_slice(&amount.to_le_bytes());
funding.push(Instruction::new_with_bytes(
crate::constants::TOKEN_PROGRAM,
&data,
vec![
AccountMeta::new(source, false),
AccountMeta::new(user_input, false),
AccountMeta::new_readonly(authority, true),
],
));
} else {
crate::instruction::token_account_setup::push_create_or_wrap_user_token_account(
&mut funding,
&wallet.pubkey(),
&input,
&crate::constants::TOKEN_PROGRAM,
amount,
params.open_seed_optimize,
);
}
let business = build_quote_route(¶ms, &route, true).await.unwrap();
funding.extend(business);
let funder = mainnet_sim::pick_funder(&rpc).await;
let hash = rpc.get_latest_blockhash().await.unwrap();
let tx = mainnet_sim::build_sim_tx(funder, &wallet, funding, hash, &[]);
let user_output =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
&wallet.pubkey(),
&output,
&crate::constants::TOKEN_PROGRAM,
params.open_seed_optimize,
);
let response = rpc
.simulate_transaction_with_config(
&tx,
RpcSimulateTransactionConfig {
sig_verify: false,
replace_recent_blockhash: true,
commitment: Some(CommitmentConfig::processed()),
encoding: Some(UiTransactionEncoding::Base64),
inner_instructions: true,
accounts: Some(RpcSimulateTransactionAccountsConfig {
encoding: Some(UiAccountEncoding::Base64),
addresses: vec![user_output.to_string()],
}),
..Default::default()
},
)
.await
.expect("simulation transport");
if let Some(err) = response.value.err {
mainnet_sim::print_tail_logs(&response.value.logs.unwrap_or_default(), 30);
panic!("Offline quote simulation failed: {err:?}");
}
let accounts = response.value.accounts.unwrap();
let account = accounts[0].as_ref().unwrap();
let bytes = account.data.decode().unwrap();
let actual = u64::from_le_bytes(bytes[64..72].try_into().unwrap());
assert!(actual >= q.minimum_net_amount_out);
assert!(
actual.abs_diff(q.estimated_net_amount_out) <= q.estimated_net_amount_out / 100 + 2,
"Quote mismatch: estimate={} actual={actual}",
q.estimated_net_amount_out
);
println!(
"cached quote pool={pool} reverse={reverse} predicted={} actual={actual} cu={:?}",
q.estimated_net_amount_out, response.value.units_consumed
);
}
#[tokio::test]
async fn offline_whirlpool_mainnet_forward() {
check(fixtures::ORCA_WHIRLPOOL_SOL_USDC, false).await;
}
#[tokio::test]
async fn offline_clmm_mainnet_forward() {
check(fixtures::RAYDIUM_CLMM_SOL_USDC, false).await;
}
#[tokio::test]
async fn offline_dlmm_mainnet_forward() {
check(fixtures::METEORA_DLMM_SOL_USDC, false).await;
}
#[tokio::test]
async fn offline_whirlpool_mainnet_reverse() {
check(fixtures::ORCA_WHIRLPOOL_SOL_USDC, true).await;
}
#[tokio::test]
async fn offline_clmm_mainnet_reverse() {
check(fixtures::RAYDIUM_CLMM_SOL_USDC, true).await;
}
#[tokio::test]
async fn offline_dlmm_mainnet_reverse() {
check(fixtures::METEORA_DLMM_SOL_USDC, true).await;
}