diff --git a/bin/autobahn-router/src/debug_tools.rs b/bin/autobahn-router/src/debug_tools.rs index 4fec3e1..43f8333 100644 --- a/bin/autobahn-router/src/debug_tools.rs +++ b/bin/autobahn-router/src/debug_tools.rs @@ -71,6 +71,8 @@ pub fn name(mint: &Pubkey) -> String { "BOME".to_string() } else if m == "3S8qX1MsMqRbiwKg2cQyx7nis1oHMgaCuc9c4VfvVdPN" { "MOTHER".to_string() + } else if m == "AKEWE7Bgh87GPp171b4cJPSSZfmZwQ3KaqYqXoKLNAEE" { + "USDC (hyperlane)".to_string() } else { m } diff --git a/bin/autobahn-router/src/ix_builder.rs b/bin/autobahn-router/src/ix_builder.rs index 71841ba..9c828fd 100644 --- a/bin/autobahn-router/src/ix_builder.rs +++ b/bin/autobahn-router/src/ix_builder.rs @@ -2,12 +2,15 @@ use crate::routing_types::{Route, RouteStep}; use crate::swap::Swap; use anchor_lang::Id; use anchor_spl::associated_token::get_associated_token_address; -use anchor_spl::token::Token; +use anchor_spl::token::{spl_token, Token}; +use anchor_spl::token_2022::spl_token_2022; use autobahn_executor::swap_ix::generate_swap_ix_data; -use router_lib::dex::{AccountProviderView, SwapInstruction, SwapMode}; +use router_lib::dex::{AccountProvider, AccountProviderView, SwapInstruction, SwapMode}; use solana_program::instruction::Instruction; use solana_program::pubkey::Pubkey; +use solana_sdk::account::ReadableAccount; use std::str::FromStr; +use std::sync::Arc; const CU_PER_HOP_DEFAULT: u32 = 80_000; const CU_BASE: u32 = 150_000; @@ -26,6 +29,7 @@ pub trait SwapStepInstructionBuilder { pub trait SwapInstructionsBuilder { fn build_ixs( &self, + live_account_provider: Arc, wallet_pk: &Pubkey, route: &Route, wrap_and_unwrap_sol: bool, @@ -81,6 +85,7 @@ impl SwapInstructionsBuilderImpl { impl SwapInstructionsBuilder for SwapInstructionsBuilderImpl { fn build_ixs( &self, + live_account_provider: Arc, wallet_pk: &Pubkey, route: &Route, auto_wrap_sol: bool, @@ -101,7 +106,7 @@ impl SwapInstructionsBuilder for SwapInstructions Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap(); if auto_wrap_sol && route.input_mint == sol_mint { - Self::create_ata(&wallet_pk, &mut setup_instructions, &sol_mint); + Self::create_ata(&wallet_pk, &mut setup_instructions, &sol_mint, false); let wsol_account = get_associated_token_address(wallet_pk, &sol_mint); let in_amount = match swap_mode { @@ -145,13 +150,25 @@ impl SwapInstructionsBuilder for SwapInstructions for step in &swap_instructions { if auto_create_out || (step.out_mint == sol_mint && auto_wrap_sol) { - Self::create_ata(&wallet_pk, &mut setup_instructions, &step.out_mint); + // wrapped SOL is always legacy token not 2022 + let out_is_token_2022 = (step.out_mint != sol_mint) + && spl_token_2022::ID.eq(live_account_provider + .account(&step.out_mint)? + .account + .owner()); + Self::create_ata( + wallet_pk, + &mut setup_instructions, + &step.out_mint, + out_is_token_2022, + ); + cu_estimate += 12_000; } if step.out_mint == sol_mint && auto_wrap_sol { let wsol_account = get_associated_token_address(wallet_pk, &sol_mint); - Self::close_wsol_ata(&wallet_pk, &mut cleanup_instructions, &wsol_account)?; + Self::close_wsol_ata(wallet_pk, &mut cleanup_instructions, &wsol_account)?; cu_estimate += 12_000; } @@ -190,7 +207,7 @@ impl SwapInstructionsBuilder for SwapInstructions impl SwapInstructionsBuilderImpl { fn close_wsol_ata( - wallet_pk: &&Pubkey, + wallet_pk: &Pubkey, cleanup_instructions: &mut Vec, wsol_account: &Pubkey, ) -> anyhow::Result<()> { @@ -204,13 +221,22 @@ impl SwapInstructionsBuilderImpl { Ok(()) } - fn create_ata(wallet_pk: &&Pubkey, setup_instructions: &mut Vec, mint: &Pubkey) { + fn create_ata( + wallet_pk: &Pubkey, + setup_instructions: &mut Vec, + mint: &Pubkey, + token_program_is_2022: bool, + ) { setup_instructions.push( spl_associated_token_account::instruction::create_associated_token_account_idempotent( &wallet_pk, &wallet_pk, &mint, - &Token::id(), + if token_program_is_2022 { + &spl_token_2022::ID + } else { + &spl_token::ID + }, ), ); } diff --git a/bin/autobahn-router/src/main.rs b/bin/autobahn-router/src/main.rs index 020974e..6c6d1b9 100644 --- a/bin/autobahn-router/src/main.rs +++ b/bin/autobahn-router/src/main.rs @@ -302,8 +302,8 @@ async fn main() -> anyhow::Result<()> { dex_invariant::InvariantDex::initialize(&mut router_rpc, HashMap::new()).await?, &mango_data, config.invariant.enabled, - config.invariant.take_all_mints, config.invariant.add_mango_tokens, + config.invariant.take_all_mints, &config.invariant.mints ), ] @@ -440,6 +440,15 @@ async fn main() -> anyhow::Result<()> { ) .collect::>(); + // collect all mints traded so their token program owner can be checked + let mut mints = HashSet::new(); + for d in dexs.iter() { + for e in d.edges_per_pk.values().flatten() { + mints.insert(e.input_mint); + mints.insert(e.output_mint); + } + } + debug_tools::set_global_filters(&filters); info!( @@ -455,6 +464,7 @@ async fn main() -> anyhow::Result<()> { DexSubscriptionMode::Mixed(m) => m.accounts.clone().into_iter(), DexSubscriptionMode::Disabled => HashSet::new().into_iter(), }) + .chain(mints.into_iter()) .collect(); let subscribed_programs = dexs diff --git a/bin/autobahn-router/src/path_warmer.rs b/bin/autobahn-router/src/path_warmer.rs index e7ea662..b51fcfa 100644 --- a/bin/autobahn-router/src/path_warmer.rs +++ b/bin/autobahn-router/src/path_warmer.rs @@ -63,6 +63,7 @@ where }; let sol_mint = Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap(); + let usdc_mint = Pubkey::from_str("AKEWE7Bgh87GPp171b4cJPSSZfmZwQ3KaqYqXoKLNAEE").unwrap(); let config = config.clone(); let start = Instant::now(); let job = tokio::spawn(async move { @@ -83,6 +84,7 @@ where let mut all_mints = token_cache.tokens(); all_mints.insert(sol_mint); + all_mints.insert(usdc_mint); let hot_mints = hot_mints_cache.read().unwrap().get(); let mints = match generate_mints( diff --git a/bin/autobahn-router/src/routing.rs b/bin/autobahn-router/src/routing.rs index 48528d8..5682f0d 100644 --- a/bin/autobahn-router/src/routing.rs +++ b/bin/autobahn-router/src/routing.rs @@ -778,10 +778,11 @@ impl Routing { price_impact, ); - if price_impact > 0.25 { - skipped_bad_price_impact += 1; - continue; - } + // TODO: make this a config variable for reach network + // if price_impact > 0.25 { + // skipped_bad_price_impact += 1; + // continue; + // } match swap_mode { SwapMode::ExactIn => { diff --git a/bin/autobahn-router/src/server/http_server.rs b/bin/autobahn-router/src/server/http_server.rs index 73acbc5..d9df706 100644 --- a/bin/autobahn-router/src/server/http_server.rs +++ b/bin/autobahn-router/src/server/http_server.rs @@ -172,6 +172,7 @@ impl HttpServer { address_lookup_table_addresses.clone(), hash_provider.clone(), alt_provider.clone(), + live_account_provider.clone(), ix_builder.clone(), &route_candidate, Pubkey::new_unique().to_string(), @@ -199,7 +200,7 @@ impl HttpServer { let route: Route = route?; - Self::log_repriced_amount(live_account_provider, reprice_probability, &route); + Self::log_repriced_amount(live_account_provider.clone(), reprice_probability, &route); let other_amount_threshold = if swap_mode == SwapMode::ExactOut { (route.in_amount as f64 * (10_000f64 + input.slippage_bps as f64) / 10_000f64).floor() @@ -279,7 +280,7 @@ impl HttpServer { ) -> Result, AppError> { let route = route_provider.try_from(&input.quote_response)?; - Self::log_repriced_amount(live_account_provider, reprice_probability, &route); + Self::log_repriced_amount(live_account_provider.clone(), reprice_probability, &route); let swap_mode: SwapMode = SwapMode::from_str(&input.quote_response.swap_mode) .map_err(|_| anyhow::Error::msg("Invalid SwapMode"))?; @@ -293,6 +294,7 @@ impl HttpServer { address_lookup_table_addresses, hash_provider, alt_provider, + live_account_provider, ix_builder, &route, input.user_public_key, @@ -352,10 +354,12 @@ impl HttpServer { THashProvider: HashProvider + Send + Sync + 'static, TAltProvider: AltProvider + Send + Sync + 'static, TIxBuilder: SwapInstructionsBuilder + Send + Sync + 'static, + TAccountProvider: AccountProvider + Send + Sync + 'static, >( address_lookup_table_addresses: Vec, hash_provider: Arc, alt_provider: Arc, + live_account_provider: Arc, ix_builder: Arc, route_plan: &Route, wallet_pk: String, @@ -369,6 +373,7 @@ impl HttpServer { let wallet_pk = Pubkey::from_str(&wallet_pk)?; let ixs = ix_builder.build_ixs( + live_account_provider, &wallet_pk, route_plan, wrap_unwrap_sol, @@ -413,11 +418,13 @@ impl HttpServer { async fn swap_ix_handler< TRouteProvider: RouteProvider + Send + Sync + 'static, TAltProvider: AltProvider + Send + Sync + 'static, + TAccountProvider: AccountProvider + Send + Sync + 'static, TIxBuilder: SwapInstructionsBuilder + Send + Sync + 'static, >( address_lookup_table_addresses: Vec, route_provider: Arc, alt_provider: Arc, + live_account_provider: Arc, ix_builder: Arc, Query(_query): Query, Json(input): Json, @@ -434,6 +441,7 @@ impl HttpServer { }; let ixs = ix_builder.build_ixs( + live_account_provider, &wallet_pk, &route_plan, input.wrap_and_unwrap_sol, @@ -628,6 +636,7 @@ impl HttpServer { let alt = address_lookup_tables.clone(); let rp = route_provider.clone(); let altp = alt_provider.clone(); + let lap = live_account_provider.clone(); let ixb = ix_builder.clone(); router = router.route( "/swap-instructions", @@ -637,7 +646,7 @@ impl HttpServer { .with_label_values(&["swap-ix", client_key]) .start_timer(); - let response = Self::swap_ix_handler(alt, rp, altp, ixb, query, form).await; + let response = Self::swap_ix_handler(alt, rp, altp, lap, ixb, query, form).await; match response { Ok(_) => { diff --git a/bin/autobahn-router/src/source/grpc_plugin_source.rs b/bin/autobahn-router/src/source/grpc_plugin_source.rs index 11bbe89..1effc22 100644 --- a/bin/autobahn-router/src/source/grpc_plugin_source.rs +++ b/bin/autobahn-router/src/source/grpc_plugin_source.rs @@ -762,9 +762,11 @@ async fn process_account_updated_from_sources( metrics::GRPC_SNAPSHOT_ACCOUNT_WRITES.inc(); metrics::GRPC_ACCOUNT_WRITE_QUEUE.set(account_write_queue_sender.len() as i64); - if !filters.contains(&account.pubkey) { - continue; - } + // TODO: disabled for eclipse launch, was causing issues with the program id + // subscription for invariant + // if !filters.contains(&account.pubkey) { + // continue; + // } updated_accounts.push(account); } diff --git a/bin/autobahn-router/src/source/mint_accounts_source.rs b/bin/autobahn-router/src/source/mint_accounts_source.rs index 84f9155..75c41cc 100644 --- a/bin/autobahn-router/src/source/mint_accounts_source.rs +++ b/bin/autobahn-router/src/source/mint_accounts_source.rs @@ -1,5 +1,7 @@ use anchor_lang::AccountDeserialize; -use anchor_spl::token::Mint; +use anchor_spl::token::{spl_token, Mint}; +use anchor_spl::token_2022::spl_token_2022; +use anchor_spl::token_2022::spl_token_2022::extension::StateWithExtensions; use futures_util::future::join_all; use itertools::Itertools; use jsonrpc_core_client::transports::http; @@ -7,8 +9,9 @@ use router_feed_lib::solana_rpc_minimal::rpc_accounts_scan::RpcAccountsScanClien use solana_account_decoder::UiAccountEncoding; use solana_client::rpc_config::RpcAccountInfoConfig; use solana_program::pubkey::Pubkey; -use solana_sdk::account::Account; +use solana_sdk::account::{Account, ReadableAccount}; use solana_sdk::commitment_config::CommitmentConfig; +use solana_sdk::program_pack::Pack; use std::collections::{HashMap, HashSet}; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; @@ -43,7 +46,7 @@ pub async fn request_mint_metadata( .unwrap(); let rpc_client = Arc::new(rpc_client); let account_info_config = RpcAccountInfoConfig { - encoding: Some(UiAccountEncoding::Binary), + encoding: Some(UiAccountEncoding::Base64), commitment: Some(CommitmentConfig::finalized()), data_slice: None, min_context_slot: None, @@ -71,18 +74,23 @@ pub async fn request_mint_metadata( for (account_pk, ui_account) in accounts { if let Some(ui_account) = ui_account { let mut account: Account = ui_account.decode().unwrap(); - let data = account.data.as_mut_slice(); - let mint_account = Mint::try_deserialize(&mut &*data).unwrap(); - trace!( - "Mint Account {}: decimals={}", - account_pk.to_string(), - mint_account.decimals - ); + + let decimals = match account.owner { + spl_token::ID => { + let mint = spl_token::state::Mint::unpack(account.data()).unwrap(); + mint.decimals + }, + spl_token_2022::ID => { + let mint = StateWithExtensions::::unpack(&account.data()).unwrap(); + mint.base.decimals + } + _ => panic!("could not parse mint {:?}", account_pk) + }; mint_accounts.insert( account_pk, Token { mint: account_pk, - decimals: mint_account.decimals, + decimals, }, ); count.fetch_add(1, Ordering::Relaxed); diff --git a/bin/autobahn-router/template-config-eclipse.toml b/bin/autobahn-router/template-config-eclipse.toml index 8312cec..2b39554 100644 --- a/bin/autobahn-router/template-config-eclipse.toml +++ b/bin/autobahn-router/template-config-eclipse.toml @@ -47,11 +47,11 @@ add_mango_tokens = false [routing] path_cache_validity_ms = 30000 -path_warming_mode = "ConfiguredMints" -#path_warming_mode = "HotMints" -path_warming_amounts = [100, 1000, 10_000] +path_warming_mode = "All" +path_warming_amounts = [1, 3, 10] path_warming_for_mints = [ - "So11111111111111111111111111111111111111112", # SOL + "So11111111111111111111111111111111111111112", # ETH + "AKEWE7Bgh87GPp171b4cJPSSZfmZwQ3KaqYqXoKLNAEE", # USDC ] path_warming_interval_secs = 5 path_warming_max_accounts = [20, 30, 40, 64] @@ -95,7 +95,8 @@ min_quote_out_to_in_amount_ratio = 0.65 [hot_mints] always_hot_mints = [ - "So11111111111111111111111111111111111111112", # SOL + "So11111111111111111111111111111111111111112", # ETH + "AKEWE7Bgh87GPp171b4cJPSSZfmZwQ3KaqYqXoKLNAEE", # USDC ] keep_latest_count = 50 diff --git a/lib/dex-invariant/src/internal/swap.rs b/lib/dex-invariant/src/internal/swap.rs index dace76f..b969d0f 100644 --- a/lib/dex-invariant/src/internal/swap.rs +++ b/lib/dex-invariant/src/internal/swap.rs @@ -16,7 +16,7 @@ pub struct InvariantSimulationParams { pub sqrt_price_limit: Price, } -#[derive(Clone, Default)] +#[derive(Clone, Default, Debug)] pub struct InvariantSwapResult { pub in_amount: u64, pub out_amount: u64, diff --git a/lib/router-lib/src/price_feeds/birdeye.rs b/lib/router-lib/src/price_feeds/birdeye.rs index 696623b..27eabb7 100644 --- a/lib/router-lib/src/price_feeds/birdeye.rs +++ b/lib/router-lib/src/price_feeds/birdeye.rs @@ -86,6 +86,10 @@ impl BirdeyePriceFeed { mints: &HashSet, sender: broadcast::Sender, ) -> anyhow::Result<()> { + + // TODO: disabled for eclipse launch until traffic is live + return Ok(()); + let http_client = reqwest::Client::new(); let mut chunks: Vec> = vec![]; diff --git a/lib/router-lib/src/price_feeds/price_cache.rs b/lib/router-lib/src/price_feeds/price_cache.rs index 1fef4c5..a909b95 100644 --- a/lib/router-lib/src/price_feeds/price_cache.rs +++ b/lib/router-lib/src/price_feeds/price_cache.rs @@ -1,7 +1,7 @@ use crate::price_feeds::price_feed::PriceUpdate; use dashmap::DashMap; use solana_sdk::pubkey::Pubkey; -use std::sync::Arc; +use std::{str::FromStr, sync::Arc}; use tokio::task::JoinHandle; use tracing::info; @@ -16,6 +16,12 @@ impl PriceCache { mut receiver: tokio::sync::broadcast::Receiver, ) -> (PriceCache, JoinHandle<()>) { let latest_prices = Arc::new(DashMap::new()); + // seed price cache with stable coin prices, as a point of reference + latest_prices.insert( + Pubkey::from_str("AKEWE7Bgh87GPp171b4cJPSSZfmZwQ3KaqYqXoKLNAEE").unwrap(), + 1.0, + ); + let latest_prices_write = latest_prices.clone(); let job = tokio::spawn(async move { diff --git a/programs/autobahn-executor/src/token.rs b/programs/autobahn-executor/src/token.rs index 40c535e..29a1de3 100644 --- a/programs/autobahn-executor/src/token.rs +++ b/programs/autobahn-executor/src/token.rs @@ -16,8 +16,9 @@ pub fn get_balance(account: &AccountInfo) -> Result { Ok(token.amount) } spl_token_2022::ID => { - let token = spl_token_2022::state::Account::unpack(&account.try_borrow_data()?)?; - Ok(token.amount) + let data = account.data.borrow(); + let token = StateWithExtensions::::unpack(&data)?; + Ok(token.base.amount) } _ => Err(ProgramError::IllegalOwner), } @@ -30,9 +31,9 @@ pub fn get_mint(account: &AccountInfo) -> Result { Ok(token.mint) } spl_token_2022::ID => { - let token: spl_token_2022::state::Account = - spl_token_2022::state::Account::unpack(&account.try_borrow_data()?)?; - Ok(token.mint) + let data = account.data.borrow(); + let token = StateWithExtensions::::unpack(&data)?; + Ok(token.base.mint) } _ => Err(ProgramError::IllegalOwner), } @@ -45,9 +46,9 @@ pub fn get_owner(account: &AccountInfo) -> Result { Ok(token.owner) } spl_token_2022::ID => { - let token: spl_token_2022::state::Account = - spl_token_2022::state::Account::unpack(&account.try_borrow_data()?)?; - Ok(token.owner) + let data = account.data.borrow(); + let token = StateWithExtensions::::unpack(&data)?; + Ok(token.base.owner) } _ => Err(ProgramError::IllegalOwner), } @@ -118,7 +119,7 @@ pub fn transfer<'a>( &[], amount, )?; - let transfer_account_infos = [source.clone(), destination.clone(), program.clone()]; + let transfer_account_infos = [source.clone(), destination.clone(), program.clone(), authority.clone()]; if signer_seeds.is_empty() { invoke(&transfer_ix, &transfer_account_infos) } else { @@ -139,7 +140,7 @@ pub fn transfer<'a>( amount, mint_parsed.base.decimals, )?; - let transfer_account_infos = [source.clone(), destination.clone(), program.clone()]; + let transfer_account_infos = [source.clone(), destination.clone(), mint.clone(), program.clone(), authority.clone()]; if signer_seeds.is_empty() { invoke(&transfer_ix, &transfer_account_infos) } else {