diff --git a/Cargo.lock b/Cargo.lock index b0cd8db..8543c62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3865,6 +3865,26 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +[[package]] +name = "litesvm" +version = "0.1.0" +source = "git+https://github.com/blockworks-foundation/litesvm.git?branch=v0.1.0+solana_1.7#4c884606289e484090be42a08b79dc3bc5f449f7" +dependencies = [ + "bincode", + "indexmap 2.2.6", + "itertools 0.12.1", + "log 0.4.21", + "solana-address-lookup-table-program", + "solana-bpf-loader-program", + "solana-compute-budget-program", + "solana-loader-v4-program", + "solana-program", + "solana-program-runtime", + "solana-sdk", + "solana-system-program", + "thiserror", +] + [[package]] name = "lock_api" version = "0.3.4" @@ -6763,11 +6783,13 @@ dependencies = [ "bincode", "bonfida-test-utils", "env_logger", + "litesvm", "log 0.4.21", "router-test-lib", "sha2 0.10.8", "solana-address-lookup-table-program", "solana-program", + "solana-program-runtime", "solana-program-test", "solana-sdk", "spl-associated-token-account 1.1.3", @@ -6775,6 +6797,8 @@ dependencies = [ "spl-token-2022 1.0.0", "test-case", "tokio", + "tracing", + "tracing-subscriber", ] [[package]] diff --git a/lib/dex-infinity/src/infinity.rs b/lib/dex-infinity/src/infinity.rs index 2917876..9f4012c 100644 --- a/lib/dex-infinity/src/infinity.rs +++ b/lib/dex-infinity/src/infinity.rs @@ -68,6 +68,14 @@ impl DexInterface for InfinityDex { let lst_mint = lst_data.sol_val_calc.lst_mint(); let account_metas = lst_data.sol_val_calc.ix_accounts(); let num_accounts_for_tx = account_metas.len(); + let Ok((lst_state, lst_data)) = amm.find_ready_lst(lst_mint) else { + continue; + }; + + if lst_state.is_input_disabled != 0 { + continue; + } + for pk in lst_data.sol_val_calc.get_accounts_to_update() { let edges = vec![ Arc::new(InfinityEdgeIdentifier { diff --git a/lib/router-lib/src/test_tools/generate_dex_rpc_dump.rs b/lib/router-lib/src/test_tools/generate_dex_rpc_dump.rs index e3720da..1133986 100644 --- a/lib/router-lib/src/test_tools/generate_dex_rpc_dump.rs +++ b/lib/router-lib/src/test_tools/generate_dex_rpc_dump.rs @@ -126,7 +126,7 @@ pub async fn run_dump_mainnet_data_with_custom_amount( } let accounts = rpc_client.get_multiple_accounts(&accounts_needed).await?; - for (_, account) in accounts { + for (_pk, account) in accounts { // get buffer for upgradable programs if account.owner == solana_sdk::bpf_loader_upgradeable::ID { let state = bincode::deserialize::(&account.data).unwrap(); @@ -260,7 +260,7 @@ pub async fn run_dump_swap_ix_with_custom_amount( dump.accounts .insert(id.input_mint(), account.account.clone()); let account = chain_data_reader - .account(&id.input_mint()) + .account(&id.output_mint()) .expect("missing mint"); dump.accounts .insert(id.output_mint(), account.account.clone()); @@ -307,9 +307,6 @@ pub async fn run_dump_swap_ix_with_custom_amount( instruction: bincode::serialize(&swap_exact_out_ix.instruction).unwrap(), is_exact_out: true, }); - - // add exact out accounts - let chain_data_reader = chain_data.read().unwrap(); for account in swap_exact_out_ix.instruction.accounts { if let Ok(acc) = chain_data_reader.account(&account.pubkey) { dump.accounts.insert(account.pubkey, acc.account.clone()); @@ -351,9 +348,9 @@ pub async fn run_dump_swap_ix_with_custom_amount( debug!("program : {program:?}"); } - for (pk, program) in &dump.accounts { + for (pk, account_data) in &dump.accounts { let mut hasher = Sha256::new(); - hasher.update(program.data()); + hasher.update(account_data.data()); let result = hasher.finalize(); let base64 = base64::encode(result); debug!("account : {pk:?} dump : {base64:?}"); diff --git a/programs/simulator/Cargo.toml b/programs/simulator/Cargo.toml index ce37c56..98502f9 100644 --- a/programs/simulator/Cargo.toml +++ b/programs/simulator/Cargo.toml @@ -23,6 +23,7 @@ test-case = "*" tokio = "1.37.0" solana-address-lookup-table-program = "1.17" solana-program-test = "1.17" +solana-program-runtime = "1.17" solana-sdk = "1.17" spl-token = { version = "^3.0.0", features = ["no-entrypoint"] } spl-token-2022 = { version = "1.0.0", features = ["no-entrypoint"] } @@ -33,6 +34,9 @@ env_logger = "0.9.0" bincode = "1.3.3" sha2 = "0.10.8" base64 = "0.12.3" +litesvm = { git = "https://github.com/blockworks-foundation/litesvm.git", branch = "v0.1.0+solana_1.7" } +tracing = "0.1.37" +tracing-subscriber = "0.3.16" [profile.test] inherits = "release" \ No newline at end of file diff --git a/programs/simulator/tests/cases/test_swap_from_dump.rs b/programs/simulator/tests/cases/test_swap_from_dump.rs index 6974809..d0ecc91 100644 --- a/programs/simulator/tests/cases/test_swap_from_dump.rs +++ b/programs/simulator/tests/cases/test_swap_from_dump.rs @@ -1,30 +1,30 @@ -use anyhow::{Context, Error}; -use bonfida_test_utils::error::TestError; -use bonfida_test_utils::ProgramTestContextExt; -use log::{debug, error, info, warn}; +use anyhow::Error; +use litesvm::LiteSVM; +use log::{error, info, warn}; use router_test_lib::execution_dump::{ExecutionDump, ExecutionItem}; use router_test_lib::{execution_dump, serialize}; -use solana_program::clock::{Clock, Epoch}; +use sha2::Digest; +use sha2::Sha256; +use solana_program::clock::Clock; use solana_program::instruction::Instruction; use solana_program::program_pack::Pack; use solana_program::program_stubs::{set_syscall_stubs, SyscallStubs}; use solana_program::pubkey::Pubkey; use solana_program::sysvar::SysvarId; -use solana_program_test::BanksClientError; -use solana_program_test::{ProgramTest, ProgramTestContext}; use solana_sdk::account::{Account, AccountSharedData, ReadableAccount}; -use solana_sdk::epoch_info::EpochInfo; +use solana_sdk::bpf_loader_upgradeable::UpgradeableLoaderState; +use solana_sdk::message::{Message, VersionedMessage}; use solana_sdk::signature::Keypair; use solana_sdk::signer::Signer; -use solana_sdk::transaction::Transaction; -use spl_associated_token_account::get_associated_token_address; +use solana_sdk::transaction::VersionedTransaction; +use spl_associated_token_account::{ + get_associated_token_address, get_associated_token_address_with_program_id, +}; use spl_token::state::AccountState; use spl_token_2022::state::AccountState as AccountState2022; use std::collections::HashMap; -use std::process::exit; +use std::path::PathBuf; use std::str::FromStr; -// use sha2::Sha256; -// use sha2::Digest; struct TestLogSyscallStubs; impl SyscallStubs for TestLogSyscallStubs { @@ -73,13 +73,15 @@ async fn test_quote_match_swap_for_infinity() -> anyhow::Result<()> { } async fn run_all_swap_from_dump(dump_name: &str) -> Result, Error> { + tracing_subscriber::fmt::init(); + let mut skip_count = option_env!("SKIP_COUNT") .map(|x| u32::from_str(x).unwrap_or(0)) .unwrap_or(0); let mut stop_at = u32::MAX; let skip_ixs_index = vec![]; - let mut run_lot_size = option_env!("RUN_LOT_SIZE") + let run_lot_size = option_env!("RUN_LOT_SIZE") .map(|x| u32::from_str(x).unwrap_or(500)) .unwrap_or(500); @@ -126,7 +128,7 @@ async fn run_all_swap_from_dump(dump_name: &str) -> Result, Er let instruction = deserialize_instruction("e.instruction)?; let programs = data.programs.iter().copied().collect(); - let mut ctx = setup_test_chain(&programs, &clock, &data).await; + let mut ctx = setup_test_chain(&programs, &clock, &data, &instruction)?; create_wallet(&mut ctx, wallet.pubkey()); @@ -145,28 +147,31 @@ async fn run_all_swap_from_dump(dump_name: &str) -> Result, Er quote.input_mint, initial_in_balance, input_mint_is_2022, - ) - .await?; + )?; set_balance( &mut ctx, wallet.pubkey(), quote.output_mint, initial_out_balance, output_mint_is_2022, - ) - .await?; + )?; for meta in &instruction.accounts { - let Ok(Some(account)) = ctx.banks_client.get_account(meta.pubkey).await else { + let Some(account) = ctx.get_account(&meta.pubkey) else { log::warn!("missing account : {:?}", meta.pubkey); continue; }; + // keep code to test hashses - // let mut hasher = Sha256::new(); - // hasher.update(account.data()); - // let result = hasher.finalize(); - // let base64 = base64::encode(result); - // log::debug!("account : {:?} dump : {base64:?} executable : {}", meta.pubkey, account.executable()); + let mut hasher = Sha256::new(); + hasher.update(account.data()); + let result = hasher.finalize(); + let base64 = base64::encode(result); + log::debug!( + "account : {:?} dump : {base64:?} executable : {}", + meta.pubkey, + account.executable() + ); } if let Some(cus) = simulate_cu_usage(&mut ctx, &wallet, &instruction).await { @@ -278,7 +283,7 @@ async fn debug_print_ix( success: &mut i32, index: &mut u32, quote: &ExecutionItem, - ctx: &mut ProgramTestContext, + ctx: &mut LiteSVM, instruction: &Instruction, input_mint_is_2022: bool, output_mint_is_2022: bool, @@ -310,13 +315,8 @@ async fn debug_print_ix( for acc in &instruction.accounts { let account = ctx - .banks_client - .get_account(acc.pubkey) - .await - .map(|x| { - x.map(|y| (y.executable, y.owner.to_string())) - .unwrap_or((false, "???".to_string())) - }) + .get_account(&acc.pubkey) + .map(|x| (x.executable, x.owner.to_string())) .unwrap_or((false, "???".to_string())); warn!( @@ -341,14 +341,53 @@ fn deserialize_instruction(swap_ix: &Vec) -> anyhow::Result { Ok(instruction) } -async fn initialize_accounts( - program_test: &mut ProgramTest, +fn initialize_accounts( + program_test: &mut LiteSVM, dump: &ExecutionDump, + accounts_list: &Vec, ) -> anyhow::Result<()> { - println!("initializing accounts : {:?}", dump.accounts.len()); - for (pk, account) in &dump.accounts { - println!("Setting data for {}", pk); - program_test.add_account( + log::debug!("initializing accounts : {:?}", dump.accounts.len()); + for pk in accounts_list { + let Some(account) = dump.accounts.get(pk) else { + continue; + }; + if *account.owner() == solana_sdk::bpf_loader_upgradeable::ID { + log::debug!("{pk:?} has upgradable loader"); + let state = bincode::deserialize::(&account.data()).unwrap(); + if let UpgradeableLoaderState::Program { + programdata_address, + } = state + { + // load buffer accounts first + match dump.accounts.get(&programdata_address) { + Some(program_buffer) => { + log::debug!("loading buffer: {programdata_address:?}"); + program_test.set_account( + programdata_address, + solana_sdk::account::Account { + lamports: program_buffer.lamports(), + owner: *program_buffer.owner(), + data: program_buffer.data().to_vec(), + rent_epoch: program_buffer.rent_epoch(), + executable: program_buffer.executable(), + }, + )?; + } + None => { + error!("{programdata_address:?} is not there"); + } + } + } + } + log::debug!( + "Setting data for {} with owner {} and is executable {}", + pk, + account.owner(), + account.executable() + ); + + log::debug!("Setting data for {}", pk); + program_test.set_account( *pk, solana_sdk::account::Account { lamports: account.lamports(), @@ -357,110 +396,109 @@ async fn initialize_accounts( rent_epoch: account.rent_epoch(), executable: account.executable(), }, - ); + )?; } Ok(()) } async fn simulate_cu_usage( - ctx: &mut ProgramTestContext, + ctx: &mut LiteSVM, owner: &Keypair, instruction: &Instruction, ) -> Option { - let mut transaction = - Transaction::new_with_payer(&[instruction.clone()], Some(&ctx.payer.pubkey())); + let tx = VersionedTransaction::try_new( + VersionedMessage::Legacy(Message::new(&[instruction.clone()], Some(&owner.pubkey()))), + &[owner], + ) + .unwrap(); - transaction.sign(&[&ctx.payer, owner], ctx.last_blockhash); - let sim = ctx - .banks_client - .simulate_transaction(transaction.clone()) - .await; + let sim = ctx.simulate_transaction(tx); match sim { Ok(sim) => { - log::debug!("{:?}", sim.result); - let simulation_details = sim.simulation_details.unwrap(); - let cus = simulation_details.units_consumed; - if sim.result.is_some() && sim.result.clone().unwrap().is_ok() { - log::debug!("units consumed : {}", cus); + let cus = sim.compute_units_consumed; + log::debug!("----logs"); + for log in sim.logs { + log::debug!("{log:?}"); + } + if cus > 0 { Some(cus) - } else if sim.result.is_some() && sim.result.clone().unwrap().is_err() { - log::debug!("simluation failed : {:?}", sim.result.unwrap()); - log::debug!("----logs"); - for log in simulation_details.logs { - log::debug!("{log:?}"); - } - None } else { None } } Err(e) => { - log::warn!("Error simulating : {}", e); + log::warn!("Error simulating : {:?}", e); None } } } -async fn swap( - ctx: &mut ProgramTestContext, - owner: &Keypair, - instruction: &Instruction, -) -> anyhow::Result<()> { - ctx.get_new_latest_blockhash().await?; - - log::info!("swapping"); - let result = ctx - .sign_send_instructions(&[instruction.clone()], &[&owner]) - .await; +async fn swap(ctx: &mut LiteSVM, owner: &Keypair, instruction: &Instruction) -> anyhow::Result<()> { + let tx = VersionedTransaction::try_new( + VersionedMessage::Legacy(Message::new(&[instruction.clone()], Some(&owner.pubkey()))), + &[owner], + ) + .unwrap(); + let result = ctx.send_transaction(tx); match result { - Ok(()) => Ok(()), - Err(e) => Err(anyhow::format_err!("Failed to swap {:?}", e)), + Ok(_) => Ok(()), + Err(e) => { + log::error!("------------- LOGS ------------------"); + for log in &e.meta.logs { + log::error!("{log:?}"); + } + Err(anyhow::format_err!("Failed to swap {:?}", e.err)) + } } } async fn get_balance( - ctx: &mut ProgramTestContext, + ctx: &mut LiteSVM, owner: Pubkey, mint: Pubkey, is_2022: bool, ) -> anyhow::Result { let ata_address = get_associated_token_address(&owner, &mint); + let Some(ata) = ctx.get_account(&ata_address) else { + return Ok(0); + }; + if is_2022 { - let Ok(ata) = ctx.banks_client.get_account(ata_address).await else { - return Ok(0); - }; - - let Some(ata) = ata else { - return Ok(0); - }; - let ata = spl_token_2022::state::Account::unpack(&ata.data); if let Ok(ata) = ata { return Ok(ata.amount); } }; - if let Ok(ata) = ctx.get_token_account(ata_address).await { + if let Ok(ata) = spl_token::state::Account::unpack(&ata.data) { Ok(ata.amount) } else { Ok(0u64) } } -async fn set_balance( - ctx: &mut ProgramTestContext, +fn set_balance( + ctx: &mut LiteSVM, owner: Pubkey, mint: Pubkey, amount: u64, is_2022: bool, ) -> anyhow::Result<()> { - let ata_address = get_associated_token_address(&owner, &mint); + let token_program_id = if is_2022 { + spl_token_2022::ID + } else { + spl_token::ID + }; + + let ata_address = + get_associated_token_address_with_program_id(&owner, &mint, &token_program_id); + let mut data = vec![0u8; 165]; if is_2022 { - let mut data = vec![0u8; 165]; + // TODO: to properly setup extensions, this is not sufficient let account = spl_token_2022::state::Account { mint, owner, @@ -472,101 +510,83 @@ async fn set_balance( close_authority: Default::default(), }; account.pack_into_slice(data.as_mut_slice()); - - ctx.set_account( - &ata_address, - &AccountSharedData::from(Account { - lamports: 1_000_000_000, - data: data, - owner: spl_token_2022::ID, - executable: false, - rent_epoch: 0, - }), - ); - - return Ok(()); - } - - let mut data = vec![0u8; 165]; - let account = spl_token::state::Account { - mint, - owner, - amount, - delegate: Default::default(), - state: AccountState::Initialized, - is_native: Default::default(), - delegated_amount: 0, - close_authority: Default::default(), + } else { + let account = spl_token::state::Account { + mint, + owner, + amount, + delegate: Default::default(), + state: AccountState::Initialized, + is_native: Default::default(), + delegated_amount: 0, + close_authority: Default::default(), + }; + account.pack_into_slice(data.as_mut_slice()); }; - account.pack_into_slice(data.as_mut_slice()); ctx.set_account( - &ata_address, - &AccountSharedData::from(Account { + ata_address, + Account { lamports: 1_000_000_000, data: data, - owner: spl_token::ID, + owner: token_program_id, executable: false, - rent_epoch: 0, - }), - ); + rent_epoch: u64::MAX, + }, + )?; Ok(()) } -fn create_wallet(ctx: &mut ProgramTestContext, address: Pubkey) { - ctx.set_account( - &address, - &AccountSharedData::from(Account { - lamports: 1_000_000_000, - data: vec![], - owner: address, - executable: false, - rent_epoch: 0, - }), - ); +fn create_wallet(ctx: &mut LiteSVM, address: Pubkey) { + let _ = ctx.airdrop(&address, 1_000_000_000); } -async fn setup_test_chain( +pub fn find_file(filename: &str) -> Option { + for dir in default_shared_object_dirs() { + let candidate = dir.join(filename); + if candidate.exists() { + return Some(candidate); + } + } + None +} + +fn default_shared_object_dirs() -> Vec { + let mut search_path = vec![]; + if let Ok(bpf_out_dir) = std::env::var("BPF_OUT_DIR") { + search_path.push(PathBuf::from(bpf_out_dir)); + } else if let Ok(bpf_out_dir) = std::env::var("SBF_OUT_DIR") { + search_path.push(PathBuf::from(bpf_out_dir)); + } + search_path.push(PathBuf::from("tests/fixtures")); + if let Ok(dir) = std::env::current_dir() { + search_path.push(dir); + } + log::trace!("SBF .so search path: {:?}", search_path); + search_path +} + +fn setup_test_chain( programs: &Vec, clock: &Clock, dump: &ExecutionDump, -) -> ProgramTestContext { - // We need to intercept logs to capture program log output - let log_filter = "solana_rbpf=trace,\ - solana_runtime::message_processor=debug,\ - solana_runtime::system_instruction_processor=trace,\ - solana_program_test=info,\ - solana_metrics::metrics=warn,\ - tarpc=error,\ - info"; - let env_logger = - env_logger::Builder::from_env(env_logger::Env::new().default_filter_or(log_filter)) - .format_timestamp_nanos() - .build(); - let _ = log::set_boxed_logger(Box::new(env_logger)); + instruction: &Instruction, +) -> anyhow::Result { + let mut program_test = LiteSVM::new(); + program_test.set_sysvar(clock); + let mut accounts_list = programs.clone(); + accounts_list.extend(instruction.accounts.iter().map(|x| x.pubkey)); - let mut program_test = ProgramTest::default(); - - initialize_accounts(&mut program_test, dump).await.unwrap(); - - program_test.prefer_bpf(true); - for &key in programs { - program_test.add_program(key.to_string().as_str(), key, None); - } - program_test.add_program("autobahn_executor", autobahn_executor::ID, None); + initialize_accounts(&mut program_test, dump, &accounts_list)?; + let path = find_file(format!("autobahn_executor.so").as_str()).unwrap(); + log::debug!("Adding program: {:?} at {path:?}", autobahn_executor::ID); + program_test.add_program_from_file(autobahn_executor::ID, path)?; // TODO: make this dynamic based on routes - program_test.set_compute_max_units(1_400_000); + let mut cb = solana_program_runtime::compute_budget::ComputeBudget::default(); + cb.compute_unit_limit = 1_400_000; + program_test.set_compute_budget(cb); - let mut program_test_context = program_test.start_with_context().await; - - // Set clock - program_test_context.set_sysvar(clock); - - info!("Setting clock to: {}", clock.unix_timestamp); - - program_test_context.warp_to_slot(40).unwrap(); - - program_test_context + Ok(program_test) }