tycho_simulation/
utils.rs1use std::{
2 collections::{HashMap, HashSet},
3 fs,
4 path::Path,
5};
6
7use tracing::info;
8use tycho_client::{
9 rpc::{AllTokensParams, HttpRPCClientOptions, RPCClient, RPC_CLIENT_CONCURRENCY},
10 HttpRPCClient, RPCError,
11};
12use tycho_common::{
13 models::{token::Token, Chain},
14 simulation::errors::SimulationError,
15 Bytes,
16};
17
18pub fn hexstring_to_vec(hexstring: &str) -> Result<Vec<u8>, SimulationError> {
41 let hexstring_no_prefix =
42 if let Some(stripped) = hexstring.strip_prefix("0x") { stripped } else { hexstring };
43 let bytes = hex::decode(hexstring_no_prefix).map_err(|err| {
44 SimulationError::FatalError(format!("Invalid hex string `{hexstring}`: {err}"))
45 })?;
46 Ok(bytes)
47}
48
49pub async fn load_all_tokens(
66 tycho_url: &str,
67 no_tls: bool,
68 auth_key: Option<&str>,
69 compression: bool,
70 chain: Chain,
71 min_quality: Option<i32>,
72 max_days_since_last_trade: Option<u64>,
73) -> Result<HashMap<Bytes, Token>, SimulationError> {
74 info!("Loading tokens from Tycho...");
75 let rpc_url =
76 if no_tls { format!("http://{tycho_url}") } else { format!("https://{tycho_url}") };
77
78 let rpc_options = HttpRPCClientOptions::new()
79 .with_auth_key(auth_key.map(|s| s.to_string()))
80 .with_compression(compression);
81
82 let rpc_client = HttpRPCClient::new(rpc_url.as_str(), rpc_options)
83 .map_err(|err| map_rpc_error(err, "Failed to create Tycho RPC client"))?;
84
85 let default_min_days = HashMap::from([(Chain::Base, 7_u64), (Chain::Ethereum, 30_u64)]);
87
88 #[allow(clippy::mutable_key_type)]
89 let min_q = min_quality.or(Some(100));
90 let traded = max_days_since_last_trade.or(default_min_days
91 .get(&chain)
92 .or(Some(&42))
93 .copied());
94 let mut token_params = AllTokensParams::new(chain, RPC_CLIENT_CONCURRENCY);
95 if let Some(q) = min_q {
96 token_params = token_params.with_min_quality(q);
97 }
98 if let Some(d) = traded {
99 token_params = token_params.with_traded_n_days_ago(d);
100 }
101 let tokens = rpc_client
102 .get_all_tokens(token_params)
103 .await
104 .map_err(|err| map_rpc_error(err, "Unable to load tokens"))?;
105
106 tokens
107 .into_iter()
108 .map(|token| Ok((token.address.clone(), token)))
109 .collect()
110}
111
112pub fn get_default_url(chain: &Chain) -> Option<String> {
114 match chain {
115 Chain::Ethereum => Some("tycho-beta.propellerheads.xyz".to_string()),
116 Chain::Base => Some("tycho-base-beta.propellerheads.xyz".to_string()),
117 Chain::Unichain => Some("tycho-unichain-beta.propellerheads.xyz".to_string()),
118 Chain::Bsc => Some("tycho-bsc-beta.propellerheads.xyz".to_string()),
119 Chain::Arbitrum => Some("tycho-arbitrum-beta.propellerheads.xyz".to_string()),
120 Chain::Polygon => Some("tycho-polygon-beta.propellerheads.xyz".to_string()),
121 Chain::Robinhood => Some("tycho-robinhood-beta.propellerheads.xyz".to_string()),
122 _ => None,
123 }
124}
125
126pub fn load_blocklist(path: Option<&Path>) -> Result<HashSet<String>, SimulationError> {
132 let Some(path) = path else {
133 return Ok(default_blocklist());
134 };
135 let contents = fs::read_to_string(path).map_err(|e| {
136 SimulationError::FatalError(format!("Failed to read blocklist {:?}: {e}", path))
137 })?;
138 parse_blocklist(&contents).map_err(|e| {
139 SimulationError::FatalError(format!("Failed to parse blocklist {:?}: {e}", path))
140 })
141}
142
143pub fn default_blocklist() -> HashSet<String> {
144 parse_blocklist(include_str!("../blocklist.toml"))
145 .expect("embedded blocklist.toml is valid TOML")
146}
147
148fn parse_blocklist(contents: &str) -> Result<HashSet<String>, toml::de::Error> {
149 #[derive(Default, serde::Deserialize)]
150 struct Blocklist {
151 #[serde(default)]
152 components: HashSet<String>,
153 }
154
155 #[derive(serde::Deserialize)]
156 struct BlocklistConfig {
157 #[serde(default)]
158 blocklist: Blocklist,
159 }
160
161 let config: BlocklistConfig = toml::from_str(contents)?;
162 Ok(config.blocklist.components)
163}
164
165fn map_rpc_error(err: RPCError, context: &str) -> SimulationError {
166 let message = format!("{context}: {err}", err = err,);
167 match err {
168 RPCError::UrlParsing(_, _) | RPCError::FormatRequest(_) => {
169 SimulationError::InvalidInput(message, None)
170 }
171 _ => SimulationError::FatalError(message),
172 }
173}