tycho_execution/encoding/evm/
utils.rs1use std::{
2 env,
3 fs::OpenOptions,
4 io::{BufRead, BufReader, Write},
5 sync::{Arc, Mutex},
6};
7
8use alloy::{
9 primitives::{aliases::U24, Address, U256, U8},
10 providers::{
11 fillers::{BlobGasFiller, ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller},
12 ProviderBuilder, RootProvider,
13 },
14 sol_types::SolValue,
15};
16use num_bigint::BigUint;
17use once_cell::sync::Lazy;
18use tokio::runtime::{Handle, Runtime};
19use tycho_common::Bytes;
20
21use crate::encoding::{errors::EncodingError, models::Swap};
22
23pub fn bytes_to_address(address: &Bytes) -> Result<Address, EncodingError> {
28 if address.len() == 20 {
29 Ok(Address::from_slice(address))
30 } else {
31 Err(EncodingError::InvalidInput(format!("Invalid address: {address}",)))
32 }
33}
34
35pub fn biguint_to_u256(value: &BigUint) -> U256 {
37 let bytes = value.to_bytes_be();
38 U256::from_be_slice(&bytes)
39}
40
41pub(crate) fn percentage_to_uint24(decimal: f64) -> U24 {
44 const MAX_UINT24: u32 = 16_777_215; let scaled = (decimal / 1.0) * (MAX_UINT24 as f64);
47 U24::from(scaled.round())
48}
49
50pub(crate) fn get_token_position(tokens: &Vec<&Bytes>, token: &Bytes) -> Result<U8, EncodingError> {
52 let position = U8::from(
53 tokens
54 .iter()
55 .position(|t| *t == token)
56 .ok_or_else(|| {
57 EncodingError::InvalidInput(format!("Token {token} not found in tokens array"))
58 })?,
59 );
60 Ok(position)
61}
62
63pub(crate) fn pad_or_truncate_to_size<const N: usize>(
67 input: &[u8],
68) -> Result<[u8; N], EncodingError> {
69 let mut result = [0u8; N];
70
71 if input.len() <= N {
72 let start = N - input.len();
74 result[start..].copy_from_slice(input);
75 } else {
76 let start = input.len() - N;
78 result.copy_from_slice(&input[start..]);
79 }
80
81 Ok(result)
82}
83
84pub(crate) fn get_static_attribute(
86 swap: &Swap,
87 attribute_name: &str,
88) -> Result<Vec<u8>, EncodingError> {
89 Ok(swap
90 .component()
91 .static_attributes
92 .get(attribute_name)
93 .ok_or_else(|| EncodingError::FatalError(format!("Attribute {attribute_name} not found")))?
94 .to_vec())
95}
96
97#[derive(Clone)]
103pub(crate) struct SafeRuntime(Option<Arc<Runtime>>);
104
105impl Drop for SafeRuntime {
106 fn drop(&mut self) {
107 if let Some(rt) = self.0.take() {
108 if tokio::runtime::Handle::try_current().is_ok() {
109 std::thread::spawn(move || drop(rt));
110 }
111 }
112 }
113}
114
115pub(crate) fn create_encoding_runtime() -> Result<(Handle, SafeRuntime), EncodingError> {
124 let rt = Arc::new(
125 tokio::runtime::Builder::new_multi_thread()
126 .worker_threads(1)
127 .enable_all()
128 .build()
129 .map_err(|_| {
130 EncodingError::FatalError("Failed to create encoding runtime".to_string())
131 })?,
132 );
133 let handle = rt.handle().clone();
134 Ok((handle, SafeRuntime(Some(rt))))
135}
136
137pub(crate) fn on_blocking_thread<F, T>(f: F) -> Result<T, EncodingError>
143where
144 F: FnOnce() -> T + Send,
145 T: Send,
146{
147 std::thread::scope(|s| {
148 s.spawn(f)
149 .join()
150 .map_err(|_| EncodingError::FatalError("blocking thread panicked".to_string()))
151 })
152}
153
154pub(crate) type EVMProvider = Arc<
155 FillProvider<
156 JoinFill<
157 alloy::providers::Identity,
158 JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>,
159 >,
160 RootProvider,
161 >,
162>;
163
164pub(crate) async fn get_client() -> Result<EVMProvider, EncodingError> {
166 dotenvy::dotenv().ok();
167 let eth_rpc_url = env::var("RPC_URL")
168 .map_err(|_| EncodingError::FatalError("Missing RPC_URL in environment".to_string()))?;
169 let client = ProviderBuilder::new()
170 .connect(ð_rpc_url)
171 .await
172 .map_err(|_| EncodingError::FatalError("Failed to build provider".to_string()))?;
173 Ok(Arc::new(client))
174}
175
176pub(crate) fn ple_encode(action_data_array: Vec<Vec<u8>>) -> Vec<u8> {
181 let mut encoded_action_data: Vec<u8> = Vec::new();
182
183 for action_data in action_data_array {
184 let args = (encoded_action_data, action_data.len() as u16, action_data);
185 encoded_action_data = args.abi_encode_packed();
186 }
187
188 encoded_action_data
189}
190
191static CALLDATA_WRITE_MUTEX: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
192pub fn write_calldata_to_file(test_identifier: &str, hex_calldata: &str) {
195 let _lock = CALLDATA_WRITE_MUTEX
196 .lock()
197 .expect("Couldn't acquire lock");
198
199 let file_path = "foundry/test/assets/calldata.txt";
200 let file = OpenOptions::new()
201 .read(true)
202 .open(file_path)
203 .expect("Failed to open calldata file for reading");
204 let reader = BufReader::new(file);
205
206 let mut lines = Vec::new();
207 let mut found = false;
208 for line in reader.lines().map_while(Result::ok) {
209 let mut parts = line.splitn(2, ':'); let key = parts.next().unwrap_or("");
211 if key == test_identifier {
212 lines.push(format!("{test_identifier}:{hex_calldata}"));
213 found = true;
214 } else {
215 lines.push(line);
216 }
217 }
218
219 if !found {
221 lines.push(format!("{test_identifier}:{hex_calldata}"));
222 }
223
224 let mut file = OpenOptions::new()
226 .write(true)
227 .truncate(true)
228 .open(file_path)
229 .expect("Failed to open calldata file for writing");
230
231 for line in lines {
232 writeln!(file, "{line}").expect("Failed to write calldata");
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
241 fn test_pad_or_truncate_to_size() {
242 let input = hex::decode("0110").unwrap();
244 let result = pad_or_truncate_to_size::<3>(&input).unwrap();
245 assert_eq!(hex::encode(result), "000110");
246
247 let input_long = hex::decode("00800000").unwrap();
249 let result_truncated = pad_or_truncate_to_size::<3>(&input_long).unwrap();
250 assert_eq!(hex::encode(result_truncated), "800000");
251 }
252}