use std::{ env, fs::OpenOptions, io::{BufRead, BufReader, Write}, sync::{Arc, Mutex}, }; use alloy::{ primitives::{aliases::U24, Address, U256, U8}, providers::{ fillers::{BlobGasFiller, ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller}, ProviderBuilder, RootProvider, }, sol_types::SolValue, }; use num_bigint::BigUint; use once_cell::sync::Lazy; use tokio::runtime::{Handle, Runtime}; use tycho_common::Bytes; use crate::encoding::{errors::EncodingError, models::Swap}; /// Safely converts a `Bytes` object to an `Address` object. /// /// Checks the length of the `Bytes` before attempting to convert, and returns an `EncodingError` /// if not 20 bytes long. pub fn bytes_to_address(address: &Bytes) -> Result { if address.len() == 20 { Ok(Address::from_slice(address)) } else { Err(EncodingError::InvalidInput(format!("Invalid address: {address}",))) } } /// Converts a general `BigUint` to an EVM-specific `U256` value. pub fn biguint_to_u256(value: &BigUint) -> U256 { let bytes = value.to_bytes_be(); U256::from_be_slice(&bytes) } /// Converts a decimal to a `U24` value. The percentage is a `f64` value between 0 and 1. /// MAX_UINT24 corresponds to 100%. pub fn percentage_to_uint24(decimal: f64) -> U24 { const MAX_UINT24: u32 = 16_777_215; // 2^24 - 1 let scaled = (decimal / 1.0) * (MAX_UINT24 as f64); U24::from(scaled.round()) } /// Gets the position of a token in a list of tokens. pub fn get_token_position(tokens: &Vec<&Bytes>, token: &Bytes) -> Result { let position = U8::from( tokens .iter() .position(|t| *t == token) .ok_or_else(|| { EncodingError::InvalidInput(format!("Token {token} not found in tokens array")) })?, ); Ok(position) } /// Pads a byte slice to a fixed size array of N bytes. pub fn pad_to_fixed_size(input: &[u8]) -> Result<[u8; N], EncodingError> { let mut padded = [0u8; N]; let start = N - input.len(); padded[start..].copy_from_slice(input); Ok(padded) } /// Extracts a static attribute from a swap. pub fn get_static_attribute(swap: &Swap, attribute_name: &str) -> Result, EncodingError> { Ok(swap .component .static_attributes .get(attribute_name) .ok_or_else(|| EncodingError::FatalError(format!("Attribute {attribute_name} not found")))? .to_vec()) } pub fn get_runtime() -> Result<(Handle, Option>), EncodingError> { match Handle::try_current() { Ok(h) => Ok((h, None)), Err(_) => { let rt = Arc::new(Runtime::new().map_err(|_| { EncodingError::FatalError("Failed to create a new tokio runtime".to_string()) })?); Ok((rt.handle().clone(), Some(rt))) } } } pub type EVMProvider = Arc< FillProvider< JoinFill< alloy::providers::Identity, JoinFill>>, >, RootProvider, >, >; /// Gets the client used for interacting with the EVM-compatible network. pub async fn get_client() -> Result { dotenv::dotenv().ok(); let eth_rpc_url = env::var("RPC_URL") .map_err(|_| EncodingError::FatalError("Missing RPC_URL in environment".to_string()))?; let client = ProviderBuilder::new() .connect(ð_rpc_url) .await .map_err(|_| EncodingError::FatalError("Failed to build provider".to_string()))?; Ok(Arc::new(client)) } /// Uses prefix-length encoding to efficient encode action data. /// /// Prefix-length encoding is a data encoding method where the beginning of a data segment /// (the "prefix") contains information about the length of the following data. pub fn ple_encode(action_data_array: Vec>) -> Vec { let mut encoded_action_data: Vec = Vec::new(); for action_data in action_data_array { let args = (encoded_action_data, action_data.len() as u16, action_data); encoded_action_data = args.abi_encode_packed(); } encoded_action_data } static CALLDATA_WRITE_MUTEX: Lazy> = Lazy::new(|| Mutex::new(())); // Function used in tests to write calldata to a file that then is used by the corresponding // solidity tests. pub fn write_calldata_to_file(test_identifier: &str, hex_calldata: &str) { let _lock = CALLDATA_WRITE_MUTEX .lock() .expect("Couldn't acquire lock"); let file_path = "foundry/test/assets/calldata.txt"; let file = OpenOptions::new() .read(true) .open(file_path) .expect("Failed to open calldata file for reading"); let reader = BufReader::new(file); let mut lines = Vec::new(); let mut found = false; for line in reader.lines().map_while(Result::ok) { let mut parts = line.splitn(2, ':'); // split at the : let key = parts.next().unwrap_or(""); if key == test_identifier { lines.push(format!("{test_identifier}:{hex_calldata}")); found = true; } else { lines.push(line); } } // If the test identifier wasn't found, append a new line if !found { lines.push(format!("{test_identifier}:{hex_calldata}")); } // Write the updated contents back to the file let mut file = OpenOptions::new() .write(true) .truncate(true) .open(file_path) .expect("Failed to open calldata file for writing"); for line in lines { writeln!(file, "{line}").expect("Failed to write calldata"); } }