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, evm::constants::ROUTER_ETH_ADDRESS, models::Swap};
22
23pub fn convert_to_router_token(addr: Address) -> Address {
27 if addr == Address::ZERO {
28 Address::from_slice(&ROUTER_ETH_ADDRESS)
29 } else {
30 addr
31 }
32}
33
34pub fn bytes_to_address(address: &Bytes) -> Result<Address, EncodingError> {
39 if address.len() == 20 {
40 Ok(Address::from_slice(address))
41 } else {
42 Err(EncodingError::InvalidInput(format!("Invalid address: {address}",)))
43 }
44}
45
46pub fn biguint_to_u256(value: &BigUint) -> U256 {
48 let bytes = value.to_bytes_be();
49 U256::from_be_slice(&bytes)
50}
51
52pub(crate) fn percentage_to_uint24(decimal: f64) -> U24 {
55 const MAX_UINT24: u32 = 16_777_215; let scaled = (decimal / 1.0) * (MAX_UINT24 as f64);
58 U24::from(scaled.round())
59}
60
61pub(crate) fn get_token_position(tokens: &Vec<&Bytes>, token: &Bytes) -> Result<U8, EncodingError> {
63 let position = U8::from(
64 tokens
65 .iter()
66 .position(|t| *t == token)
67 .ok_or_else(|| {
68 EncodingError::InvalidInput(format!("Token {token} not found in tokens array"))
69 })?,
70 );
71 Ok(position)
72}
73
74pub(crate) fn pad_or_truncate_to_size<const N: usize>(
78 input: &[u8],
79) -> Result<[u8; N], EncodingError> {
80 let mut result = [0u8; N];
81
82 if input.len() <= N {
83 let start = N - input.len();
85 result[start..].copy_from_slice(input);
86 } else {
87 let start = input.len() - N;
89 result.copy_from_slice(&input[start..]);
90 }
91
92 Ok(result)
93}
94
95pub(crate) fn get_static_attribute(
97 swap: &Swap,
98 attribute_name: &str,
99) -> Result<Vec<u8>, EncodingError> {
100 Ok(swap
101 .component()
102 .static_attributes
103 .get(attribute_name)
104 .ok_or_else(|| EncodingError::FatalError(format!("Attribute {attribute_name} not found")))?
105 .to_vec())
106}
107
108#[derive(Clone)]
114pub(crate) struct SafeRuntime(Option<Arc<Runtime>>);
115
116impl Drop for SafeRuntime {
117 fn drop(&mut self) {
118 if let Some(rt) = self.0.take() {
119 if tokio::runtime::Handle::try_current().is_ok() {
120 std::thread::spawn(move || drop(rt));
121 }
122 }
123 }
124}
125
126pub(crate) fn create_encoding_runtime() -> Result<(Handle, SafeRuntime), EncodingError> {
135 let rt = Arc::new(
136 tokio::runtime::Builder::new_multi_thread()
137 .worker_threads(1)
138 .enable_all()
139 .build()
140 .map_err(|_| {
141 EncodingError::FatalError("Failed to create encoding runtime".to_string())
142 })?,
143 );
144 let handle = rt.handle().clone();
145 Ok((handle, SafeRuntime(Some(rt))))
146}
147
148pub(crate) fn on_blocking_thread<F, T>(f: F) -> Result<T, EncodingError>
154where
155 F: FnOnce() -> T + Send,
156 T: Send,
157{
158 std::thread::scope(|s| {
159 s.spawn(f)
160 .join()
161 .map_err(|_| EncodingError::FatalError("blocking thread panicked".to_string()))
162 })
163}
164
165pub(crate) type EVMProvider = Arc<
166 FillProvider<
167 JoinFill<
168 alloy::providers::Identity,
169 JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>,
170 >,
171 RootProvider,
172 >,
173>;
174
175pub(crate) async fn get_client() -> Result<EVMProvider, EncodingError> {
177 dotenvy::dotenv().ok();
178 let eth_rpc_url = env::var("RPC_URL")
179 .map_err(|_| EncodingError::FatalError("Missing RPC_URL in environment".to_string()))?;
180 let client = ProviderBuilder::new()
181 .connect(ð_rpc_url)
182 .await
183 .map_err(|_| EncodingError::FatalError("Failed to build provider".to_string()))?;
184 Ok(Arc::new(client))
185}
186
187pub(crate) fn ple_encode(action_data_array: Vec<Vec<u8>>) -> Vec<u8> {
192 let mut encoded_action_data: Vec<u8> = Vec::new();
193
194 for action_data in action_data_array {
195 let args = (encoded_action_data, action_data.len() as u16, action_data);
196 encoded_action_data = args.abi_encode_packed();
197 }
198
199 encoded_action_data
200}
201
202static CALLDATA_WRITE_MUTEX: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
203pub fn write_calldata_to_file(test_identifier: &str, hex_calldata: &str) {
206 let _lock = CALLDATA_WRITE_MUTEX
207 .lock()
208 .expect("Couldn't acquire lock");
209
210 let file_path = "contracts/test/assets/calldata.txt";
211 let file = OpenOptions::new()
212 .read(true)
213 .open(file_path)
214 .expect("Failed to open calldata file for reading");
215 let reader = BufReader::new(file);
216
217 let mut lines = Vec::new();
218 let mut found = false;
219 for line in reader.lines().map_while(Result::ok) {
220 let mut parts = line.splitn(2, ':'); let key = parts.next().unwrap_or("");
222 if key == test_identifier {
223 lines.push(format!("{test_identifier}:{hex_calldata}"));
224 found = true;
225 } else {
226 lines.push(line);
227 }
228 }
229
230 if !found {
232 lines.push(format!("{test_identifier}:{hex_calldata}"));
233 }
234
235 let mut file = OpenOptions::new()
237 .write(true)
238 .truncate(true)
239 .open(file_path)
240 .expect("Failed to open calldata file for writing");
241
242 for line in lines {
243 writeln!(file, "{line}").expect("Failed to write calldata");
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_pad_or_truncate_to_size() {
253 let input = hex::decode("0110").unwrap();
255 let result = pad_or_truncate_to_size::<3>(&input).unwrap();
256 assert_eq!(hex::encode(result), "000110");
257
258 let input_long = hex::decode("00800000").unwrap();
260 let result_truncated = pad_or_truncate_to_size::<3>(&input_long).unwrap();
261 assert_eq!(hex::encode(result_truncated), "800000");
262 }
263}