diff --git a/.claude/CODEBASE.md b/.claude/CODEBASE.md index 52cd2cae..56ff0b26 100644 --- a/.claude/CODEBASE.md +++ b/.claude/CODEBASE.md @@ -97,7 +97,7 @@ See `docs/ARCHITECTURE.md` for the full architecture diagram and detailed compon | `HTTP_HOST` | HTTP bind address (default: `0.0.0.0`) | | `HTTP_PORT` | API port (default: `3000`) | | `WORKER_POOLS_CONFIG` | Worker pools config file (default: `worker_pools.toml`) | -| `BLACKLIST_CONFIG` | Blacklist config file (default: `blacklist.toml`) | +| `BLOCKLIST_CONFIG` | Blocklist config file (default: `blocklist.toml`) | | `RUST_LOG` | Tracing filter (e.g. `info,fynd=debug`) | ### CLI Commands @@ -112,7 +112,7 @@ See `docs/ARCHITECTURE.md` for the full architecture diagram and detailed compon | File | Purpose | |---|---| | `worker_pools.toml` | Worker pool definitions: algorithm, num_workers, hop limits, timeout | -| `blacklist.toml` | Pool IDs to exclude from routing | +| `blocklist.toml` | Pool IDs to exclude from routing | ## Testing diff --git a/Cargo.lock b/Cargo.lock index 769530c8..4b9ae568 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7468,9 +7468,9 @@ dependencies = [ [[package]] name = "tycho-client" -version = "0.151.0" +version = "0.153.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f13e4bff5abe0b8e18c519e604af2df7c897260b0b5250ceddb26eaf4ded19" +checksum = "93ba71c393d7236d79055c72c0d6101822a0a64487ede90cb9b023ebaaa05af3" dependencies = [ "async-trait", "backoff", @@ -7487,6 +7487,7 @@ dependencies = [ "time", "tokio", "tokio-tungstenite 0.20.1", + "toml", "tracing", "tracing-appender", "tracing-subscriber 0.3.23", @@ -7497,9 +7498,9 @@ dependencies = [ [[package]] name = "tycho-common" -version = "0.151.0" +version = "0.153.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7027b8b0f32772c4d8fcad1772d77152672d706f139004438a3c8d5f6f22252e" +checksum = "11988023320d8623bd339d4239297fbd0a42e0307d9c46a1fd9f6afbf4b7b6c0" dependencies = [ "anyhow", "async-trait", @@ -7524,9 +7525,9 @@ dependencies = [ [[package]] name = "tycho-ethereum" -version = "0.151.0" +version = "0.153.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c34b23220362591dbe418f7cabae6d1a5d963212ccedcd629134a0c83937269" +checksum = "a1bbf96296aeb6b6739c169be162a5abb2a9f6fc78fe45f6f9b3b0779a8b6092" dependencies = [ "alloy", "async-trait", @@ -7567,9 +7568,9 @@ dependencies = [ [[package]] name = "tycho-simulation" -version = "0.251.1" +version = "0.254.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00857ca975c4c63f4bfa8c3ba7ef034b1a91979650867d5da03cb92cb101a7e" +checksum = "a45273d02955b2144e0f5405c14539c030352cae61acad85b5ac71ff27635959" dependencies = [ "alloy", "alloy-chains", @@ -7604,6 +7605,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-tungstenite 0.28.0", + "toml", "tracing", "tycho-client", "tycho-common", diff --git a/README.md b/README.md index a492ac83..c8ff90f5 100644 --- a/README.md +++ b/README.md @@ -220,7 +220,7 @@ solving. | `--min-tvl` | - | `10.0` | Minimum pool TVL in native token | | `--worker-router-timeout-ms` | - | `100` | Default solve timeout | | `-w, --worker-pools-config` | `WORKER_POOLS_CONFIG` | `worker_pools.toml` | Worker pools config | -| `--blacklist-config` | `BLACKLIST_CONFIG` | `blacklist.toml` | Blacklist config | +| `--blocklist-config` | `BLOCKLIST_CONFIG` | `blocklist.toml` | Path to blocklist TOML config file | | `--gas-price-stale-threshold-secs` | - | *(disabled)* | Health returns 503 when gas price exceeds this age | See `--help` for the full list. @@ -260,12 +260,19 @@ pools within the timeout. Each order gets 2 candidate solutions — one from each pool — and the best is selected. -### Blacklist (blacklist.toml) +### Blocklist Config -Exclude specific pools from routing: +By default, Fynd loads `blocklist.toml` from the working directory. The default excludes components with known +simulation issues (e.g., rebasing tokens). Override with `--blocklist-config`: + +```bash +cargo run --release serve -- --blocklist-config my_blocklist.toml +``` + +The config file uses a `[blocklist]` section with component IDs to exclude: ```toml -[blacklist] +[blocklist] components = [ "0x86d257cdb7bc9c0df10e84c8709697f92770b335", ] diff --git a/blacklist.toml b/blocklist.toml similarity index 57% rename from blacklist.toml rename to blocklist.toml index 317221ef..2a539cbb 100644 --- a/blacklist.toml +++ b/blocklist.toml @@ -1,7 +1,7 @@ -# Component blacklist configuration +# Component blocklist configuration # Exclude specific pools from routing due to simulation issues -[blacklist] +[blocklist] # Component IDs to exclude (pool addresses) components = [ # AMPL pools - AMPL is a rebasing token that breaks simulation assumptions @@ -17,4 +17,15 @@ components = [ "0x69accb968b19a53790f43e57558f5e443a91af22", # Fluid syrupUSDC/USDC — ERC-4626 vault token, simulation can't track accumulating rate "0x79eea4a1be86c43a9a9c4384b0b28a07af24ae29", + # Curve weETH/WETH (StableSwapNG) — MissingAccount error, oracle deps not in simulated state + "0xdb74dfdd3bb46be8ce6c33dc9d82777bcfc3ded5", + # "Dollars" (USD, 0xd233d1f6) token pools — fake stablecoin with mispriced reserves + # UniswapV2 LINK/"Dollars" + "0x81a8bd7f2b29cee72aae18da9b4637acf4bc125a", + # UniswapV2 MKR/"Dollars" + "0xa16a3cbc92d77b720a851d33a91890c4fbfb0299", + # UniswapV2 DAI/"Dollars" (last trade Sep 2020) + "0x1d1126cc2c77384448913b41fbf308563aae1f16", + # UniswapV2 WETH/"Dollars" (1938 WETH locked, mispriced) + "0x582e3da39948c6339433008703211ad2c13eb2ac", ] diff --git a/docker-compose.yml b/docker-compose.yml index 42d4594d..8c83609f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,13 +8,13 @@ services: - "9898:9898" volumes: - ./worker_pools.toml:/etc/fynd/worker_pools.toml:ro - - ./blacklist.toml:/etc/fynd/blacklist.toml:ro + - ./blocklist.toml:/etc/fynd/blocklist.toml:ro environment: - RPC_URL=${RPC_URL} - TYCHO_API_KEY=${TYCHO_API_KEY:-} - TYCHO_URL=${TYCHO_URL:-tycho-fynd-ethereum.propellerheads.xyz} - WORKER_POOLS_CONFIG=/etc/fynd/worker_pools.toml - - BLACKLIST_CONFIG=/etc/fynd/blacklist.toml + - BLOCKLIST_CONFIG=/etc/fynd/blocklist.toml - RUST_LOG=fynd=info - OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo:4317 depends_on: diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e0d31062..956c7d10 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -239,7 +239,7 @@ Provides `extract_subset()` for creating filtered snapshots that algorithms can **Crate:** `fynd-core` **Location:** `fynd-core/src/feed/tycho_feed.rs` -Background task that connects to Tycho's WebSocket API, processes component/state updates, updates SharedMarketData, and broadcasts `MarketEvent`s. Applies TVL filtering with hysteresis (components are added at `min_tvl` and removed at `min_tvl / tvl_buffer_ratio`), token recency filtering (`traded_n_days_ago`), blacklisting, and token quality filtering. +Background task that connects to Tycho's WebSocket API, processes component/state updates, updates SharedMarketData, and broadcasts `MarketEvent`s. Applies TVL filtering with hysteresis (components are added at `min_tvl` and removed at `min_tvl / tvl_buffer_ratio`), token recency filtering (`traded_n_days_ago`), blocklisting, and token quality filtering. *** diff --git a/docs/guides/server-configuration.md b/docs/guides/server-configuration.md index 0273e887..d99a2097 100644 --- a/docs/guides/server-configuration.md +++ b/docs/guides/server-configuration.md @@ -4,7 +4,7 @@ icon: server # Server Configuration -Reference for all Fynd server flags, worker pool tuning, blacklisting, logging, and monitoring. +Reference for all Fynd server flags, worker pool tuning, blocklist configuration, logging, and monitoring. ## Run options @@ -87,7 +87,7 @@ Run `cargo run --release -- serve --help` for the full list. | `--worker-router-timeout-ms` | — | `100` | Default solve timeout (ms) | | `--worker-router-min-responses` | — | `0` | Early return threshold (0 = wait for all pools) | | `-w, --worker-pools-config` | `WORKER_POOLS_CONFIG` | `worker_pools.toml` | Worker pools config file path | -| `--blacklist-config` | `BLACKLIST_CONFIG` | `blacklist.toml` | Blacklist config file path | +| `--blocklist-config` | `BLOCKLIST_CONFIG` | `blocklist.toml` | Path to blocklist TOML config file. Components listed here are excluded from the Tycho stream. | | `--disable-tls` | — | `false` | Disable TLS for Tycho connection | | `--min-token-quality` | — | `100` | Minimum [token quality](https://docs.propellerheads.xyz/tycho/overview/concepts#token) filter | | `--gas-refresh-interval-secs` | — | `30` | Gas price refresh interval | @@ -151,12 +151,18 @@ To use a custom config file: cargo run --release -- serve -w my_worker_pools.toml ``` -## Blacklist (`blacklist.toml`) +## Blocklist config -Exclude specific components from routing, useful for components with known simulation issues (e.g., [rebasing tokens on UniswapV3 pools](https://docs.uniswap.org/concepts/protocol/integration-issues)): +By default, Fynd loads `blocklist.toml` from the working directory. The default excludes components with known simulation issues (e.g., [rebasing tokens on UniswapV3 pools](https://docs.uniswap.org/concepts/protocol/integration-issues)). Override with `--blocklist-config`: + +```bash +cargo run --release -- serve --blocklist-config my_blocklist.toml +``` + +The config file uses a `[blocklist]` section listing component IDs to exclude: ```toml -[blacklist] +[blocklist] components = [ "0x86d257cdb7bc9c0df10e84c8709697f92770b335", ] diff --git a/fynd-core/src/feed/mod.rs b/fynd-core/src/feed/mod.rs index 872c2e62..fd8cdaa0 100644 --- a/fynd-core/src/feed/mod.rs +++ b/fynd-core/src/feed/mod.rs @@ -44,8 +44,8 @@ pub(crate) struct TychoFeedConfig { pub(crate) reconnect_delay: Duration, /// Only include tokens traded within this many days. pub(crate) traded_n_days_ago: Option, - /// Component IDs to exclude from routing. - pub(crate) blacklisted_components: HashSet, + /// Component IDs to exclude from the Tycho stream. + pub(crate) blocklisted_components: HashSet, } impl TychoFeedConfig { @@ -69,7 +69,7 @@ impl TychoFeedConfig { tvl_buffer_ratio: 1.1, gas_refresh_interval: Duration::from_secs(30), reconnect_delay: Duration::from_secs(5), - blacklisted_components: HashSet::new(), + blocklisted_components: HashSet::new(), } } @@ -98,8 +98,8 @@ impl TychoFeedConfig { self } - pub(crate) fn blacklisted_components(mut self, components: HashSet) -> Self { - self.blacklisted_components = components; + pub(crate) fn blocklisted_components(mut self, components: HashSet) -> Self { + self.blocklisted_components = components; self } } diff --git a/fynd-core/src/feed/tycho_feed.rs b/fynd-core/src/feed/tycho_feed.rs index 7077125b..535c65e3 100644 --- a/fynd-core/src/feed/tycho_feed.rs +++ b/fynd-core/src/feed/tycho_feed.rs @@ -5,10 +5,7 @@ //! - Updates SharedMarketData (exclusive write access) //! - Broadcasts MarketEvents to Solvers -use std::{ - collections::{HashMap, HashSet}, - sync::Arc, -}; +use std::{collections::HashSet, sync::Arc}; use tokio::{ sync::{broadcast, mpsc, oneshot, RwLock}, @@ -133,6 +130,11 @@ impl TychoFeed { ComponentFilter::with_tvl_range( self.config.min_tvl / self.config.tvl_buffer_ratio, self.config.min_tvl, + ) + .blocklist( + self.config + .blocklisted_components + .clone(), ), &self.config.protocols, )? @@ -264,40 +266,6 @@ impl TychoFeed { .. } = msg; - // Filter blacklisted components before processing - let original_count = added_components.len(); - let added_components: HashMap<_, _> = added_components - .into_iter() - .filter(|(id, _component)| { - !self - .config - .blacklisted_components - .contains(&id.to_string()) - }) - .collect(); - - // Also filter state updates for blacklisted components - let updated_or_new_states: HashMap<_, _> = updated_or_new_states - .into_iter() - .filter(|(id, _)| { - !self - .config - .blacklisted_components - .contains(id) - }) - .collect(); - - // Filter removed_components to avoid emitting events for components we never tracked - let removed_components: HashMap<_, _> = removed_components - .into_iter() - .filter(|(id, _)| { - !self - .config - .blacklisted_components - .contains(id) - }) - .collect(); - let updated_components_ids: HashSet<_> = updated_or_new_states .keys() .filter(|id| !added_components.contains_key(id.as_str())) // TODO: Should we still emit as updated if the component is new? @@ -321,9 +289,8 @@ impl TychoFeed { .max_by_key(|b| b.number()); info!( - "received block/timestamp {} with {} new components ({} after blacklist filter), {} removed, {} updated", + "received block/timestamp {} with {} new components, {} removed, {} updated", msg.block_number_or_timestamp, - original_count, added_components.len(), removed_components.len(), updated_or_new_states.len() @@ -1128,285 +1095,4 @@ mod tests { feed_handle.abort(); } - - // Helper function to create a test config with blacklisted components - fn create_test_config_with_blacklist(blacklisted: Vec<&str>) -> TychoFeedConfig { - TychoFeedConfig::new( - "ws://test.tycho.io".to_string(), - Chain::Ethereum, - Some("test_api_key".to_string()), - false, - vec!["uniswap_v2".to_string()], - 10.0, - ) - .blacklisted_components( - blacklisted - .into_iter() - .map(|s| s.to_string()) - .collect(), - ) - } - - #[tokio::test] - async fn test_blacklist_filters_added_components() { - let blacklisted_id = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - let allowed_id = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; - - let config = create_test_config_with_blacklist(vec![blacklisted_id]); - let market_data = new_shared_market_data(); - let feed = TychoFeed::new(config, market_data.clone()); - let mut event_rx = feed.subscribe(); - - let token1 = create_test_token("0x1111111111111111111111111111111111111111", "TKN1"); - let token2 = create_test_token("0x2222222222222222222222222222222222222222", "TKN2"); - - // Add both a blacklisted and an allowed component - let mut new_pairs = HashMap::new(); - new_pairs.insert( - blacklisted_id.to_string(), - create_test_component(blacklisted_id, vec![token1.clone(), token2.clone()]), - ); - new_pairs.insert( - allowed_id.to_string(), - create_test_component(allowed_id, vec![token1.clone(), token2.clone()]), - ); - - let update = Update::new(12345, HashMap::new(), new_pairs); - feed.handle_tycho_message(update) - .await - .expect("Failed to handle message"); - - // Verify blacklisted component was NOT added to market data - let data = market_data.read().await; - assert!( - data.get_component(blacklisted_id) - .is_none(), - "Blacklisted component should NOT be in market data" - ); - assert!( - data.get_component(allowed_id).is_some(), - "Allowed component should be in market data" - ); - drop(data); - - // Verify event only contains the allowed component - let event = event_rx - .try_recv() - .expect("Should receive event"); - match event { - MarketEvent::MarketUpdated { added_components, .. } => { - assert!( - !added_components.contains_key(blacklisted_id), - "Event should NOT contain blacklisted component" - ); - assert!( - added_components.contains_key(allowed_id), - "Event should contain allowed component" - ); - assert_eq!(added_components.len(), 1, "Only one component should be added"); - } - } - } - - #[tokio::test] - async fn test_blacklist_filters_state_updates() { - let blacklisted_id = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - let allowed_id = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; - - let config = create_test_config_with_blacklist(vec![blacklisted_id]); - let market_data = new_shared_market_data(); - let feed = TychoFeed::new(config, market_data.clone()); - let mut event_rx = feed.subscribe(); - - let token1 = create_test_token("0x1111111111111111111111111111111111111111", "TKN1"); - let token2 = create_test_token("0x2222222222222222222222222222222222222222", "TKN2"); - - // First, add the allowed component (blacklisted one should never be added) - let mut new_pairs = HashMap::new(); - new_pairs.insert( - allowed_id.to_string(), - create_test_component(allowed_id, vec![token1.clone(), token2.clone()]), - ); - - let mut initial_states = HashMap::new(); - initial_states.insert( - allowed_id.to_string(), - Box::new(FeedMockProtocolSim::new(1.0)) as Box, - ); - - let update = Update::new(12345, initial_states, new_pairs); - feed.handle_tycho_message(update) - .await - .expect("Failed to add component"); - - // Consume the initial event - let _ = event_rx.try_recv(); - - // Now send state updates for both blacklisted and allowed components - let mut states = HashMap::new(); - states.insert( - blacklisted_id.to_string(), - Box::new(FeedMockProtocolSim::new(99.0)) as Box, - ); - states.insert( - allowed_id.to_string(), - Box::new(FeedMockProtocolSim::new(2.0)) as Box, - ); - - let update = Update::new(12346, states, HashMap::new()); - feed.handle_tycho_message(update) - .await - .expect("Failed to handle state update"); - - // Verify blacklisted component state was NOT saved - let data = market_data.read().await; - assert!( - data.get_simulation_state(blacklisted_id) - .is_none(), - "Blacklisted component state should NOT be in market data" - ); - assert_eq!( - data.get_simulation_state(allowed_id) - .expect("Allowed component state should exist") - .fee(), - 2.0, - "Allowed component state should be updated" - ); - drop(data); - - // Verify event only contains the allowed component update - let event = event_rx - .try_recv() - .expect("Should receive event"); - match event { - MarketEvent::MarketUpdated { updated_components, .. } => { - assert!( - !updated_components.contains(&blacklisted_id.to_string()), - "Event should NOT contain blacklisted component update" - ); - assert!( - updated_components.contains(&allowed_id.to_string()), - "Event should contain allowed component update" - ); - } - } - } - - #[tokio::test] - async fn test_blacklist_filters_removed_components() { - let blacklisted_id = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - let allowed_id = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; - - let config = create_test_config_with_blacklist(vec![blacklisted_id]); - let market_data = new_shared_market_data(); - let feed = TychoFeed::new(config, market_data.clone()); - let mut event_rx = feed.subscribe(); - - let token1 = create_test_token("0x1111111111111111111111111111111111111111", "TKN1"); - let token2 = create_test_token("0x2222222222222222222222222222222222222222", "TKN2"); - - // First, add only the allowed component - let mut new_pairs = HashMap::new(); - new_pairs.insert( - allowed_id.to_string(), - create_test_component(allowed_id, vec![token1.clone(), token2.clone()]), - ); - - let update = Update::new(12345, HashMap::new(), new_pairs); - feed.handle_tycho_message(update) - .await - .expect("Failed to add component"); - - // Consume the initial add event - let _ = event_rx.try_recv(); - - // Now send removal for both (blacklisted was never added, but could come from stream) - let mut removed_pairs = HashMap::new(); - removed_pairs.insert( - blacklisted_id.to_string(), - create_test_component(blacklisted_id, vec![token1.clone(), token2.clone()]), - ); - removed_pairs.insert( - allowed_id.to_string(), - create_test_component(allowed_id, vec![token1.clone(), token2.clone()]), - ); - - let update = - Update::new(12346, HashMap::new(), HashMap::new()).set_removed_pairs(removed_pairs); - feed.handle_tycho_message(update) - .await - .expect("Failed to handle removal"); - - // Verify the allowed component was removed - let data = market_data.read().await; - assert!(data.get_component(allowed_id).is_none(), "Allowed component should be removed"); - drop(data); - - // Verify event only contains the allowed component removal - let event = event_rx - .try_recv() - .expect("Should receive event"); - match event { - MarketEvent::MarketUpdated { removed_components, .. } => { - assert!( - !removed_components.contains(&blacklisted_id.to_string()), - "Event should NOT contain blacklisted component removal" - ); - assert!( - removed_components.contains(&allowed_id.to_string()), - "Event should contain allowed component removal" - ); - assert_eq!( - removed_components.len(), - 1, - "Only one component should be in removed list" - ); - } - } - } - - #[tokio::test] - async fn test_blacklist_no_event_when_all_filtered() { - let blacklisted_id = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - - let config = create_test_config_with_blacklist(vec![blacklisted_id]); - let market_data = new_shared_market_data(); - let feed = TychoFeed::new(config, market_data.clone()); - let mut event_rx = feed.subscribe(); - - let token1 = create_test_token("0x1111111111111111111111111111111111111111", "TKN1"); - let token2 = create_test_token("0x2222222222222222222222222222222222222222", "TKN2"); - - // Add only blacklisted component - let mut new_pairs = HashMap::new(); - new_pairs.insert( - blacklisted_id.to_string(), - create_test_component(blacklisted_id, vec![token1.clone(), token2.clone()]), - ); - - let update = Update::new(12345, HashMap::new(), new_pairs); - feed.handle_tycho_message(update) - .await - .expect("Failed to handle message"); - - // Verify NO event was broadcast since everything was filtered - match event_rx.try_recv() { - Err(tokio::sync::broadcast::error::TryRecvError::Empty) => { - // Expected - no event should be broadcast when all components are filtered - } - Ok(event) => panic!( - "Should NOT broadcast event when all components are blacklisted, got: {:?}", - event - ), - Err(e) => panic!("Unexpected error: {:?}", e), - } - - // Verify component was NOT added to market data - let data = market_data.read().await; - assert!( - data.get_component(blacklisted_id) - .is_none(), - "Blacklisted component should NOT be in market data" - ); - } } diff --git a/fynd-core/src/solver.rs b/fynd-core/src/solver.rs index 5fe12fa4..3ff09006 100644 --- a/fynd-core/src/solver.rs +++ b/fynd-core/src/solver.rs @@ -289,7 +289,7 @@ pub struct FyndBuilder { tvl_buffer_ratio: f64, gas_refresh_interval: Duration, reconnect_delay: Duration, - blacklisted_components: HashSet, + blocklisted_components: HashSet, router_timeout: Duration, router_min_responses: usize, encoder: Option, @@ -318,7 +318,7 @@ impl FyndBuilder { tvl_buffer_ratio: defaults::TVL_BUFFER_RATIO, gas_refresh_interval: defaults::GAS_REFRESH_INTERVAL, reconnect_delay: defaults::RECONNECT_DELAY, - blacklisted_components: HashSet::new(), + blocklisted_components: HashSet::new(), router_timeout: DEFAULT_ROUTER_TIMEOUT, router_min_responses: defaults::ROUTER_MIN_RESPONSES, encoder: None, @@ -380,9 +380,9 @@ impl FyndBuilder { self } - /// Replaces the set of component addresses that are excluded from routing. - pub fn blacklisted_components(mut self, components: HashSet) -> Self { - self.blacklisted_components = components; + /// Sets component IDs to exclude from the Tycho stream. + pub fn blocklisted_components(mut self, components: HashSet) -> Self { + self.blocklisted_components = components; self } @@ -486,7 +486,7 @@ impl FyndBuilder { .reconnect_delay(self.reconnect_delay) .min_token_quality(self.min_token_quality) .traded_n_days_ago(self.traded_n_days_ago) - .blacklisted_components(self.blacklisted_components); + .blocklisted_components(self.blocklisted_components); let ethereum_client = EthereumRpcClient::new(self.rpc_url.as_str()) .map_err(|e| SolverBuildError::RpcClient(e.to_string()))?; diff --git a/fynd-rpc/CLAUDE.md b/fynd-rpc/CLAUDE.md index b6d4b8f8..cf19d589 100644 --- a/fynd-rpc/CLAUDE.md +++ b/fynd-rpc/CLAUDE.md @@ -8,7 +8,7 @@ infrastructure. | Module | Description | |---|---| | `builder.rs` | `FyndRPCBuilder` wraps `FyndBuilder`, adds HTTP server config. `FyndRPC` struct runs the server with graceful shutdown | -| `config.rs` | `WorkerPoolsConfig` (TOML loader), `BlacklistConfig`, `defaults` module re-exporting `fynd-core` defaults + HTTP-specific ones | +| `config.rs` | `WorkerPoolsConfig` (TOML loader), `BlocklistConfig`, `defaults` module re-exporting `fynd-core` defaults + HTTP-specific ones | | `protocols.rs` | `fetch_protocol_systems()` — paginated Tycho RPC call to discover available protocols | | `api/` | HTTP endpoint handlers and OpenAPI documentation | diff --git a/fynd-rpc/src/builder.rs b/fynd-rpc/src/builder.rs index 1742118f..94ecf928 100644 --- a/fynd-rpc/src/builder.rs +++ b/fynd-rpc/src/builder.rs @@ -9,7 +9,7 @@ use tycho_simulation::tycho_common::models::Chain; use crate::{ api::{configure_app, AppState, HealthTracker}, - config::{defaults, BlacklistConfig, PoolConfig}, + config::{defaults, BlocklistConfig, PoolConfig}, }; /// Builder that assembles Fynd and returns a running server handle. @@ -138,11 +138,11 @@ impl FyndRPCBuilder { self } - /// Sets the blacklist configuration for filtering components. - pub fn blacklist(mut self, blacklist: BlacklistConfig) -> Self { + /// Sets the blocklist configuration for filtering components. + pub fn blocklist(mut self, blocklist: BlocklistConfig) -> Self { self.fynd_builder = self .fynd_builder - .blacklisted_components(blacklist.into_components()); + .blocklisted_components(blocklist.into_components()); self } diff --git a/fynd-rpc/src/config.rs b/fynd-rpc/src/config.rs index 36c8364c..b39b1875 100644 --- a/fynd-rpc/src/config.rs +++ b/fynd-rpc/src/config.rs @@ -65,23 +65,21 @@ impl WorkerPoolsConfig { } } -/// Blacklist configuration for filtering components. -/// -/// Components in this config will be excluded from routing. +/// Blocklist configuration for excluding components from the Tycho stream. #[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct BlacklistConfig { +pub struct BlocklistConfig { /// Component IDs to exclude (e.g., pool addresses with simulation issues). #[serde(default)] components: HashSet, } -/// The default blacklist configuration embedded at compile time. +/// The default blocklist configuration embedded at compile time. /// -/// Keep in sync with the repo-root `blacklist.toml` (the user-facing example config). +/// Keep in sync with the repo-root `blocklist.toml` (the user-facing example config). /// Cannot use `include_str!` here because `cargo publish` verifies the crate in isolation, /// and the file lives outside the `fynd-rpc` package directory. -const DEFAULT_BLACKLIST_TOML: &str = r#" -[blacklist] +const DEFAULT_BLOCKLIST_TOML: &str = r#" +[blocklist] components = [ # AMPL pools - AMPL is a rebasing token that breaks simulation assumptions # UniswapV3 AMPL/WETH @@ -95,43 +93,54 @@ components = [ "0x69accb968b19a53790f43e57558f5e443a91af22", # Fluid syrupUSDC/USDC — ERC-4626 vault token, simulation can't track accumulating rate "0x79eea4a1be86c43a9a9c4384b0b28a07af24ae29", + # Curve weETH/WETH (StableSwapNG) — MissingAccount error, oracle deps not in simulated state + "0xdb74dfdd3bb46be8ce6c33dc9d82777bcfc3ded5", + # "Dollars" (USD, 0xd233d1f6) token pools — fake stablecoin with mispriced reserves + # UniswapV2 LINK/"Dollars" + "0x81a8bd7f2b29cee72aae18da9b4637acf4bc125a", + # UniswapV2 MKR/"Dollars" + "0xa16a3cbc92d77b720a851d33a91890c4fbfb0299", + # UniswapV2 DAI/"Dollars" (last trade Sep 2020) + "0x1d1126cc2c77384448913b41fbf308563aae1f16", + # UniswapV2 WETH/"Dollars" (1938 WETH locked, mispriced) + "0x582e3da39948c6339433008703211ad2c13eb2ac", ] "#; -impl BlacklistConfig { - /// Returns the built-in default blacklist embedded in the binary. +impl BlocklistConfig { + /// Returns the built-in default blocklist embedded in the binary. /// - /// This is the repo-root `blacklist.toml` baked in at compile time. + /// This is the repo-root `blocklist.toml` baked in at compile time. pub fn builtin_default() -> Self { #[derive(Deserialize)] struct Wrapper { - blacklist: BlacklistConfig, + blocklist: BlocklistConfig, } let wrapper: Wrapper = - toml::from_str(DEFAULT_BLACKLIST_TOML).expect("built-in blacklist.toml is valid TOML"); - wrapper.blacklist + toml::from_str(DEFAULT_BLOCKLIST_TOML).expect("built-in blocklist.toml is valid TOML"); + wrapper.blocklist } - /// Load blacklist configuration from a TOML file. + /// Load blocklist configuration from a TOML file. /// - /// The TOML file should have a `[blacklist]` section: + /// The TOML file should have a `[blocklist]` section: /// ```toml - /// [blacklist] + /// [blocklist] /// components = ["0x86d257cdb7bc9c0df10e84c8709697f92770b335"] /// ``` pub fn load_from_file(path: impl AsRef) -> Result { let path = path.as_ref(); let contents = fs::read_to_string(path) - .with_context(|| format!("failed to read blacklist config {}", path.display()))?; + .with_context(|| format!("failed to read blocklist config {}", path.display()))?; #[derive(Deserialize)] struct Wrapper { - blacklist: BlacklistConfig, + blocklist: BlocklistConfig, } let wrapper: Wrapper = toml::from_str(&contents) - .with_context(|| format!("failed to parse blacklist config {}", path.display()))?; - Ok(wrapper.blacklist) + .with_context(|| format!("failed to parse blocklist config {}", path.display()))?; + Ok(wrapper.blocklist) } pub(crate) fn into_components(self) -> HashSet { @@ -150,11 +159,11 @@ mod tests { } #[test] - fn test_blacklist_builtin_default_does_not_panic() { - let config = BlacklistConfig::builtin_default(); + fn test_blocklist_builtin_default_does_not_panic() { + let config = BlocklistConfig::builtin_default(); assert!( !config.components.is_empty(), - "built-in blacklist must have at least one component" + "built-in blocklist must have at least one component" ); } diff --git a/src/cli.rs b/src/cli.rs index 1a7ed766..383e4a84 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -100,9 +100,10 @@ pub struct ServeArgs { #[arg(short, long, env, default_value = "worker_pools.toml")] pub worker_pools_config: PathBuf, - /// Path to blacklist TOML config file (optional) - #[arg(long, env, default_value = "blacklist.toml")] - pub blacklist_config: Option, + /// Path to blocklist TOML config file. Components listed here are excluded from the + /// Tycho stream. + #[arg(long, env, default_value = "blocklist.toml")] + pub blocklist_config: Option, /// Gas price staleness threshold in seconds. Health returns 503 when exceeded. /// Disabled by default. diff --git a/src/main.rs b/src/main.rs index 11efd0e7..7ea80533 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,7 @@ use anyhow::anyhow; use clap::Parser; use fynd_rpc::{ builder::{parse_chain, FyndRPCBuilder}, - config::{defaults, BlacklistConfig, WorkerPoolsConfig}, + config::{defaults, BlocklistConfig, WorkerPoolsConfig}, protocols::fetch_protocol_systems, }; mod cli; @@ -281,21 +281,21 @@ async fn setup_solver(args: &cli::ServeArgs) -> Result