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 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 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 fn pad_or_truncate_to_size<const N: usize>(input: &[u8]) -> Result<[u8; N], EncodingError> {
67 let mut result = [0u8; N];
68
69 if input.len() <= N {
70 let start = N - input.len();
72 result[start..].copy_from_slice(input);
73 } else {
74 let start = input.len() - N;
76 result.copy_from_slice(&input[start..]);
77 }
78
79 Ok(result)
80}
81
82pub fn get_static_attribute(swap: &Swap, attribute_name: &str) -> Result<Vec<u8>, EncodingError> {
84 Ok(swap
85 .component
86 .static_attributes
87 .get(attribute_name)
88 .ok_or_else(|| EncodingError::FatalError(format!("Attribute {attribute_name} not found")))?
89 .to_vec())
90}
91
92pub fn get_runtime() -> Result<(Handle, Option<Arc<Runtime>>), EncodingError> {
96 match Handle::try_current() {
97 Ok(h) => Ok((h, None)),
98 Err(_) => {
99 let rt = Arc::new(Runtime::new().map_err(|_| {
100 EncodingError::FatalError("Failed to create a new tokio runtime".to_string())
101 })?);
102 Ok((rt.handle().clone(), Some(rt)))
103 }
104 }
105}
106
107pub type EVMProvider = Arc<
108 FillProvider<
109 JoinFill<
110 alloy::providers::Identity,
111 JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>,
112 >,
113 RootProvider,
114 >,
115>;
116
117pub async fn get_client() -> Result<EVMProvider, EncodingError> {
119 dotenv::dotenv().ok();
120 let eth_rpc_url = env::var("RPC_URL")
121 .map_err(|_| EncodingError::FatalError("Missing RPC_URL in environment".to_string()))?;
122 let client = ProviderBuilder::new()
123 .connect(ð_rpc_url)
124 .await
125 .map_err(|_| EncodingError::FatalError("Failed to build provider".to_string()))?;
126 Ok(Arc::new(client))
127}
128
129pub fn ple_encode(action_data_array: Vec<Vec<u8>>) -> Vec<u8> {
134 let mut encoded_action_data: Vec<u8> = Vec::new();
135
136 for action_data in action_data_array {
137 let args = (encoded_action_data, action_data.len() as u16, action_data);
138 encoded_action_data = args.abi_encode_packed();
139 }
140
141 encoded_action_data
142}
143
144static CALLDATA_WRITE_MUTEX: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
145pub fn write_calldata_to_file(test_identifier: &str, hex_calldata: &str) {
148 let _lock = CALLDATA_WRITE_MUTEX
149 .lock()
150 .expect("Couldn't acquire lock");
151
152 let file_path = "foundry/test/assets/calldata.txt";
153 let file = OpenOptions::new()
154 .read(true)
155 .open(file_path)
156 .expect("Failed to open calldata file for reading");
157 let reader = BufReader::new(file);
158
159 let mut lines = Vec::new();
160 let mut found = false;
161 for line in reader.lines().map_while(Result::ok) {
162 let mut parts = line.splitn(2, ':'); let key = parts.next().unwrap_or("");
164 if key == test_identifier {
165 lines.push(format!("{test_identifier}:{hex_calldata}"));
166 found = true;
167 } else {
168 lines.push(line);
169 }
170 }
171
172 if !found {
174 lines.push(format!("{test_identifier}:{hex_calldata}"));
175 }
176
177 let mut file = OpenOptions::new()
179 .write(true)
180 .truncate(true)
181 .open(file_path)
182 .expect("Failed to open calldata file for writing");
183
184 for line in lines {
185 writeln!(file, "{line}").expect("Failed to write calldata");
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn test_pad_or_truncate_to_size() {
195 let input = hex::decode("0110").unwrap();
197 let result = pad_or_truncate_to_size::<3>(&input).unwrap();
198 assert_eq!(hex::encode(result), "000110");
199
200 let input_long = hex::decode("00800000").unwrap();
202 let result_truncated = pad_or_truncate_to_size::<3>(&input_long).unwrap();
203 assert_eq!(hex::encode(result_truncated), "800000");
204 }
205}