use std::collections::HashMap;
use alloy::primitives::{bytes::Bytes, U256};
use num_bigint::BigUint;
use tycho_common::simulation::errors::SimulationError;
pub fn u256_to_f64(x: U256) -> Result<f64, SimulationError> {
if x == U256::from(0u64) {
return Ok(0.0);
}
let x_bits = x.bit_len();
let n_shifts = 53i32 - x_bits as i32;
let mut exponent = (1023 + 52 - n_shifts) as u64;
let mut significant = if n_shifts >= 0 {
let shift = n_shifts as u32;
let shifted = x << shift;
u64::try_from(shifted).map_err(|_| {
SimulationError::FatalError(format!(
"Unable to convert shifted U256 value `{shifted}` into u64 when converting {x}"
))
})?
} else {
let shift = n_shifts.unsigned_abs();
let lsb = (x >> shift) & U256::from(1u64);
let round_bit = (x >> (shift - 1)) & U256::from(1u64);
let sticky_bit = if shift < 2 {
U256::from(0u64)
} else {
x & ((U256::from(1u64) << (shift - 2)) - U256::from(1u64))
};
let rounded_towards_zero_u256 = x >> shift;
let rounded_towards_zero: u64 = rounded_towards_zero_u256.try_into().map_err(|_| {
SimulationError::FatalError(format!(
"Unable to convert rounded U256 value `{rounded_towards_zero_u256}` into u64 when converting {x}"
))
})?;
if round_bit == U256::from(1u64) {
if sticky_bit == U256::from(0u64) {
if lsb == U256::from(0u64) {
rounded_towards_zero
} else {
rounded_towards_zero + 1
}
} else {
rounded_towards_zero + 1
}
} else {
rounded_towards_zero
}
};
if significant & (1 << 53) > 0 {
significant >>= 1;
exponent += 1;
}
let merged = (exponent << 52) | (significant & 0xFFFFFFFFFFFFFu64);
Ok(f64::from_bits(merged))
}
#[inline]
pub fn u256_to_biguint(value: U256) -> BigUint {
BigUint::from_bytes_le(&value.to_le_bytes::<32>())
}
pub fn biguint_to_u256(value: &BigUint) -> U256 {
assert!(value.bits() <= 256, "BigUint value overflows U256");
let mut limbs = [0u64; 4];
for (i, digit) in value.iter_u64_digits().enumerate() {
limbs[i] = digit;
}
U256::from_limbs(limbs)
}
pub fn bytes_to_u256(bytes: Bytes) -> U256 {
let mut padded_bytes = [0u8; 32];
let start = 32 - bytes.len().min(32);
padded_bytes[start..].copy_from_slice(&bytes[bytes.len().saturating_sub(32)..]);
U256::from_be_slice(&padded_bytes)
}
pub fn map_slots_to_u256(
slots: HashMap<tycho_common::hex_bytes::Bytes, tycho_common::hex_bytes::Bytes>,
) -> HashMap<U256, U256> {
slots
.into_iter()
.map(|(k, v)| (bytes_to_u256(k.into()), bytes_to_u256(v.into())))
.collect()
}
#[cfg(test)]
mod test {
use rstest::rstest;
use super::*;
#[rstest]
#[case::one(U256::from(1u64), 1.0f64)]
#[case::two(U256::from(2), 2.0f64)]
#[case::zero(U256::from(0u64), 0.0f64)]
#[case::two_pow1024(U256::from(2).pow(U256::from(190)), 2.0f64.powi(190))]
#[case::max32(U256::from_limbs([u32::MAX as u64, 0, 0, 0]), u32::MAX as f64)]
#[case::max64(U256::from_limbs([u64::MAX, 0, 0, 0]), u64::MAX as f64)]
#[case::edge_54bits_trailing_zeros(U256::from(2u64.pow(53)), 2u64.pow(53) as f64)]
#[case::edge_54bits_trailing_ones(U256::from(2u64.pow(54) - 1), (2u64.pow(54) - 1) as f64)]
#[case::edge_53bits_trailing_zeros(U256::from(2u64.pow(52)), 2u64.pow(52) as f64)]
#[case::edge_53bits_trailing_ones(U256::from(2u64.pow(53) - 1), (2u64.pow(53) - 1) as f64)]
fn test_convert(#[case] inp: U256, #[case] out: f64) {
let res = u256_to_f64(inp).expect("convert U256 to f64");
assert_eq!(res, out);
}
}