use std::cmp::max;
use alloy_primitives::{aliases::U24, keccak256, Address, FixedBytes, Keccak256, U256, U8};
use num_bigint::BigUint;
use tycho_core::Bytes;
use crate::encoding::{errors::EncodingError, models::Solution};
/// 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)
}
/// Encodes the input data for a function call to the given function selector.
pub fn encode_input(selector: &str, mut encoded_args: Vec) -> Vec {
let mut hasher = Keccak256::new();
hasher.update(selector.as_bytes());
let selector_bytes = &hasher.finalize()[..4];
let mut call_data = selector_bytes.to_vec();
// Remove extra prefix if present (32 bytes for dynamic data)
// Alloy encoding is including a prefix for dynamic data indicating the offset or length
// but at this point we don't want that
if encoded_args.len() > 32 &&
encoded_args[..32] ==
[0u8; 31]
.into_iter()
.chain([32].to_vec())
.collect::>()
{
encoded_args = encoded_args[32..].to_vec();
}
call_data.extend(encoded_args);
call_data
}
/// 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 minimum amount out for a solution to pass when executed on-chain.
///
/// The minimum amount is calculated based on the expected amount and the slippage percentage, if
/// passed. If this information is not passed, the user-passed checked amount will be used.
/// If both the slippage and minimum user-passed checked amount are passed, the maximum of the two
/// will be used.
/// If neither are passed, the minimum amount will be zero.
pub fn get_min_amount_for_solution(solution: Solution) -> BigUint {
let mut min_amount_out = solution
.checked_amount
.unwrap_or(BigUint::ZERO);
if let (Some(expected_amount), Some(slippage)) =
(solution.expected_amount.as_ref(), solution.slippage)
{
let one_hundred = BigUint::from(100u32);
let slippage_percent = BigUint::from((slippage * 100.0) as u32);
let multiplier = &one_hundred - slippage_percent;
let expected_amount_with_slippage = (expected_amount * &multiplier) / &one_hundred;
min_amount_out = max(min_amount_out, expected_amount_with_slippage);
}
min_amount_out
}
/// Gets the position of a token in a list of tokens.
pub fn get_token_position(tokens: Vec, token: Bytes) -> Result {
let position = U8::from(
tokens
.iter()
.position(|t| *t == token)
.ok_or_else(|| {
EncodingError::InvalidInput(format!("Token {:?} not found in tokens array", token))
})?,
);
Ok(position)
}
/// Pads a byte slice to a fixed size array with leading zeros.
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)
}
/// Encodes a function selector to a fixed size array of 4 bytes.
pub fn encode_function_selector(selector: &str) -> FixedBytes<4> {
let hash = keccak256(selector.as_bytes());
FixedBytes::<4>::from([hash[0], hash[1], hash[2], hash[3]])
}