tycho_simulation/
utils.rs1use std::{
2 collections::{HashMap, HashSet},
3 fs,
4 path::Path,
5};
6
7use tracing::{info, warn};
8use tycho_client::{
9 rpc::{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 tokens = rpc_client
90 .get_all_tokens(
91 chain.into(),
92 min_quality.or(Some(100)),
93 max_days_since_last_trade.or(default_min_days
94 .get(&chain)
95 .or(Some(&42))
96 .copied()),
97 None,
98 RPC_CLIENT_CONCURRENCY,
99 )
100 .await
101 .map_err(|err| map_rpc_error(err, "Unable to load tokens"))?;
102
103 tokens
104 .into_iter()
105 .map(|token| {
106 let token_clone = token.clone();
107 Token::try_from(token)
108 .map(|converted| (converted.address.clone(), converted))
109 .map_err(|_| {
110 SimulationError::FatalError(format!(
111 "Unable to convert token `{symbol}` at {address} on chain {chain} into ERC20 token",
112 symbol = token_clone.symbol,
113 address = token_clone.address,
114 chain = token_clone.chain,
115 ))
116 })
117 })
118 .collect()
119}
120
121pub fn get_default_url(chain: &Chain) -> Option<String> {
123 match chain {
124 Chain::Ethereum => Some("tycho-beta.propellerheads.xyz".to_string()),
125 Chain::Base => Some("tycho-base-beta.propellerheads.xyz".to_string()),
126 Chain::Unichain => Some("tycho-unichain-beta.propellerheads.xyz".to_string()),
127 _ => None,
128 }
129}
130
131pub fn load_blocklist(path: &Path) -> HashSet<String> {
139 let contents = match fs::read_to_string(path) {
140 Ok(c) => c,
141 Err(_) => return HashSet::new(),
142 };
143
144 #[derive(Default, serde::Deserialize)]
145 struct Blocklist {
146 #[serde(default)]
147 components: HashSet<String>,
148 }
149
150 #[derive(serde::Deserialize)]
151 struct BlocklistConfig {
152 #[serde(default)]
153 blocklist: Blocklist,
154 }
155
156 match toml::from_str::<BlocklistConfig>(&contents) {
157 Ok(config) => config.blocklist.components,
158 Err(e) => {
159 warn!("Failed to parse {}: {e}", path.display());
160 HashSet::new()
161 }
162 }
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}