tycho_execution/encoding/evm/
utils.rs1use std::{
2 env,
3 fs::OpenOptions,
4 io::{BufRead, BufReader, Write},
5 sync::{
6 atomic::{AtomicUsize, Ordering},
7 Arc, Mutex,
8 },
9};
10
11use alloy::{
12 primitives::{aliases::U24, Address, U256, U8},
13 providers::{
14 fillers::{BlobGasFiller, ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller},
15 ProviderBuilder, RootProvider,
16 },
17 sol_types::SolValue,
18};
19use num_bigint::BigUint;
20use once_cell::sync::Lazy;
21use tokio::runtime::{Handle, Runtime};
22use tycho_common::Bytes;
23
24use crate::encoding::{errors::EncodingError, evm::constants::ROUTER_ETH_ADDRESS, models::Swap};
25
26pub fn convert_to_router_token(addr: Address) -> Address {
30 if addr == Address::ZERO {
31 Address::from_slice(&ROUTER_ETH_ADDRESS)
32 } else {
33 addr
34 }
35}
36
37pub fn bytes_to_address(address: &Bytes) -> Result<Address, EncodingError> {
42 if address.len() == 20 {
43 Ok(Address::from_slice(address))
44 } else {
45 Err(EncodingError::InvalidInput(format!("Invalid address: {address}",)))
46 }
47}
48
49pub fn biguint_to_u256(value: &BigUint) -> U256 {
51 let bytes = value.to_bytes_be();
52 U256::from_be_slice(&bytes)
53}
54
55pub(crate) fn percentage_to_uint24(decimal: f64) -> U24 {
58 const MAX_UINT24: u32 = 16_777_215; let scaled = (decimal / 1.0) * (MAX_UINT24 as f64);
61 U24::from(scaled.round())
62}
63
64pub(crate) fn get_token_position(tokens: &Vec<&Bytes>, token: &Bytes) -> Result<U8, EncodingError> {
66 let position = U8::from(
67 tokens
68 .iter()
69 .position(|t| *t == token)
70 .ok_or_else(|| {
71 EncodingError::InvalidInput(format!("Token {token} not found in tokens array"))
72 })?,
73 );
74 Ok(position)
75}
76
77pub(crate) fn pad_or_truncate_to_size<const N: usize>(
81 input: &[u8],
82) -> Result<[u8; N], EncodingError> {
83 let mut result = [0u8; N];
84
85 if input.len() <= N {
86 let start = N - input.len();
88 result[start..].copy_from_slice(input);
89 } else {
90 let start = input.len() - N;
92 result.copy_from_slice(&input[start..]);
93 }
94
95 Ok(result)
96}
97
98pub(crate) fn get_static_attribute(
100 swap: &Swap,
101 attribute_name: &str,
102) -> Result<Vec<u8>, EncodingError> {
103 Ok(swap
104 .component()
105 .static_attributes
106 .get(attribute_name)
107 .ok_or_else(|| EncodingError::FatalError(format!("Attribute {attribute_name} not found")))?
108 .to_vec())
109}
110
111#[derive(Clone)]
117pub(crate) struct SafeRuntime(Option<Arc<Runtime>>);
118
119impl Drop for SafeRuntime {
120 fn drop(&mut self) {
121 if let Some(rt) = self.0.take() {
122 if tokio::runtime::Handle::try_current().is_ok() {
123 std::thread::spawn(move || drop(rt));
124 }
125 }
126 }
127}
128
129pub(crate) fn create_encoding_runtime() -> Result<(Handle, SafeRuntime), EncodingError> {
138 let rt = Arc::new(
139 tokio::runtime::Builder::new_multi_thread()
140 .worker_threads(1)
141 .enable_all()
142 .build()
143 .map_err(|_| {
144 EncodingError::FatalError("Failed to create encoding runtime".to_string())
145 })?,
146 );
147 let handle = rt.handle().clone();
148 Ok((handle, SafeRuntime(Some(rt))))
149}
150
151fn panic_message(payload: &(dyn std::any::Any + Send)) -> &str {
153 if let Some(message) = payload.downcast_ref::<&str>() {
154 message
155 } else if let Some(message) = payload.downcast_ref::<String>() {
156 message
157 } else {
158 "non-string panic payload"
159 }
160}
161
162pub(crate) fn on_blocking_thread<F, T>(f: F) -> Result<T, EncodingError>
168where
169 F: FnOnce() -> T + Send,
170 T: Send,
171{
172 std::thread::scope(|s| {
173 s.spawn(f).join().map_err(|payload| {
174 EncodingError::FatalError(format!(
175 "blocking thread panicked: {}",
176 panic_message(&*payload)
177 ))
178 })
179 })
180}
181
182const MAX_ENCODING_THREADS: usize = 32;
187
188pub(crate) fn map_on_threads<T, R, F>(items: &[T], f: F) -> Result<Vec<R>, EncodingError>
196where
197 T: Sync,
198 R: Send,
199 F: Fn(&T) -> Result<R, EncodingError> + Sync,
200{
201 if let [item] = items {
202 return Ok(vec![f(item)?]);
203 }
204 let next_index = AtomicUsize::new(0);
205 let workers = items.len().min(MAX_ENCODING_THREADS);
206 std::thread::scope(|scope| {
207 let mut handles = Vec::with_capacity(workers);
208 for _ in 0..workers {
209 handles.push(scope.spawn(|| {
210 let mut worker_results = Vec::new();
211 loop {
212 let index = next_index.fetch_add(1, Ordering::Relaxed);
213 let Some(item) = items.get(index) else {
214 return worker_results;
215 };
216 worker_results.push((index, f(item)));
217 }
218 }));
219 }
220 let mut results: Vec<Option<R>> = Vec::new();
221 results.resize_with(items.len(), || None);
222 let mut first_error: Option<(usize, EncodingError)> = None;
223 for handle in handles {
224 let worker_results = handle.join().map_err(|payload| {
225 EncodingError::FatalError(format!(
226 "encoding thread panicked: {}",
227 panic_message(&*payload)
228 ))
229 })?;
230 for (index, result) in worker_results {
231 match result {
232 Ok(value) => results[index] = Some(value),
233 Err(error) => {
234 if first_error
235 .as_ref()
236 .is_none_or(|(first_index, _)| index < *first_index)
237 {
238 first_error = Some((index, error));
239 }
240 }
241 }
242 }
243 }
244 if let Some((_, error)) = first_error {
245 return Err(error);
246 }
247 let mut ordered = Vec::with_capacity(items.len());
248 for result in results {
249 ordered.push(result.ok_or_else(|| {
250 EncodingError::FatalError("encoding thread dropped a result".to_string())
251 })?);
252 }
253 Ok(ordered)
254 })
255}
256
257pub(crate) type EVMProvider = Arc<
258 FillProvider<
259 JoinFill<
260 alloy::providers::Identity,
261 JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>,
262 >,
263 RootProvider,
264 >,
265>;
266
267pub(crate) async fn get_client() -> Result<EVMProvider, EncodingError> {
269 dotenvy::dotenv().ok();
270 let eth_rpc_url = env::var("RPC_URL")
271 .map_err(|_| EncodingError::FatalError("Missing RPC_URL in environment".to_string()))?;
272 let client = ProviderBuilder::new()
273 .connect(ð_rpc_url)
274 .await
275 .map_err(|_| EncodingError::FatalError("Failed to build provider".to_string()))?;
276 Ok(Arc::new(client))
277}
278
279pub(crate) fn ple_encode(action_data_array: Vec<Vec<u8>>) -> Vec<u8> {
284 let mut encoded_action_data: Vec<u8> = Vec::new();
285
286 for action_data in action_data_array {
287 let args = (encoded_action_data, action_data.len() as u16, action_data);
288 encoded_action_data = args.abi_encode_packed();
289 }
290
291 encoded_action_data
292}
293
294static CALLDATA_WRITE_MUTEX: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
295pub fn write_calldata_to_file(test_identifier: &str, hex_calldata: &str) {
298 let _lock = CALLDATA_WRITE_MUTEX
299 .lock()
300 .expect("Couldn't acquire lock");
301
302 let file_path = "contracts/test/assets/calldata.txt";
303 let file = OpenOptions::new()
304 .read(true)
305 .open(file_path)
306 .expect("Failed to open calldata file for reading");
307 let reader = BufReader::new(file);
308
309 let mut lines = Vec::new();
310 let mut found = false;
311 for line in reader.lines().map_while(Result::ok) {
312 let mut parts = line.splitn(2, ':'); let key = parts.next().unwrap_or("");
314 if key == test_identifier {
315 lines.push(format!("{test_identifier}:{hex_calldata}"));
316 found = true;
317 } else {
318 lines.push(line);
319 }
320 }
321
322 if !found {
324 lines.push(format!("{test_identifier}:{hex_calldata}"));
325 }
326
327 let mut file = OpenOptions::new()
329 .write(true)
330 .truncate(true)
331 .open(file_path)
332 .expect("Failed to open calldata file for writing");
333
334 for line in lines {
335 writeln!(file, "{line}").expect("Failed to write calldata");
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342
343 #[test]
344 fn test_map_on_threads_keeps_input_order_above_the_thread_cap() {
345 let items: Vec<usize> = (0..(MAX_ENCODING_THREADS * 3 + 1)).collect();
346
347 let results = map_on_threads(&items, |item| Ok(*item * 2)).unwrap();
348
349 let expected: Vec<usize> = items
350 .iter()
351 .map(|item| item * 2)
352 .collect();
353 assert_eq!(results, expected);
354 }
355
356 #[test]
357 fn test_map_on_threads_returns_the_earliest_error() {
358 let items: Vec<usize> = (0..(MAX_ENCODING_THREADS * 2)).collect();
359
360 let result: Result<Vec<usize>, EncodingError> = map_on_threads(&items, |item| {
361 if *item >= 3 {
362 Err(EncodingError::InvalidInput(format!("item {item} is broken")))
363 } else {
364 Ok(*item)
365 }
366 });
367
368 let Err(EncodingError::InvalidInput(message)) = result else {
369 panic!("expected an InvalidInput error, got {result:?}");
370 };
371 assert_eq!(message, "item 3 is broken");
372 }
373
374 #[test]
375 fn test_map_on_threads_reports_the_panic_message() {
376 let items = vec![1, 2];
377
378 let result: Result<Vec<i32>, EncodingError> = map_on_threads(&items, |item| {
379 assert_ne!(*item, 2, "item 2 is broken");
380 Ok(*item)
381 });
382
383 let Err(EncodingError::FatalError(message)) = result else {
384 panic!("expected a FatalError, got {result:?}");
385 };
386 assert!(message.contains("item 2 is broken"), "{message}");
387 }
388
389 #[test]
390 fn test_pad_or_truncate_to_size() {
391 let input = hex::decode("0110").unwrap();
393 let result = pad_or_truncate_to_size::<3>(&input).unwrap();
394 assert_eq!(hex::encode(result), "000110");
395
396 let input_long = hex::decode("00800000").unwrap();
398 let result_truncated = pad_or_truncate_to_size::<3>(&input_long).unwrap();
399 assert_eq!(hex::encode(result_truncated), "800000");
400 }
401}