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, 1_u64), (Chain::Unichain, 14_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 _ => None,
121 }
122}
123
124pub fn load_blocklist(path: Option<&Path>) -> Result<HashSet<String>, SimulationError> {
130 let Some(path) = path else {
131 return Ok(default_blocklist());
132 };
133 let contents = fs::read_to_string(path).map_err(|e| {
134 SimulationError::FatalError(format!("Failed to read blocklist {:?}: {e}", path))
135 })?;
136 parse_blocklist(&contents).map_err(|e| {
137 SimulationError::FatalError(format!("Failed to parse blocklist {:?}: {e}", path))
138 })
139}
140
141pub fn default_blocklist() -> HashSet<String> {
142 parse_blocklist(include_str!("../blocklist.toml"))
143 .expect("embedded blocklist.toml is valid TOML")
144}
145
146fn parse_blocklist(contents: &str) -> Result<HashSet<String>, toml::de::Error> {
147 #[derive(Default, serde::Deserialize)]
148 struct Blocklist {
149 #[serde(default)]
150 components: HashSet<String>,
151 }
152
153 #[derive(serde::Deserialize)]
154 struct BlocklistConfig {
155 #[serde(default)]
156 blocklist: Blocklist,
157 }
158
159 let config: BlocklistConfig = toml::from_str(contents)?;
160 Ok(config.blocklist.components)
161}
162
163fn map_rpc_error(err: RPCError, context: &str) -> SimulationError {
164 let message = format!("{context}: {err}", err = err,);
165 match err {
166 RPCError::UrlParsing(_, _) | RPCError::FormatRequest(_) => {
167 SimulationError::InvalidInput(message, None)
168 }
169 _ => SimulationError::FatalError(message),
170 }
171}